Skip to content

Language Translation System

The Nursery App supports multiple languages (Kannada, Hindi, Bengali, Tamil, Telugu, Punjabi, Malayalam, Marathi, Assamese, Gujarati) for translating UI text throughout the application.

  • Multi-language support (10+ languages)
  • Dynamic language switching
  • Persistent storage via IndexedDB
  • API-driven updates (planned)
  • Fallback to English if translation unavailable

Current State:

  • Uses hardcoded language_list array in language.js (22,000+ lines)
  • Array contains all translations in memory
  • formatted_language_data.json exists but is NOT currently used

Location:

  • www/scripts/language.js - Contains language_list array (line 8741)
  • www/scripts/formatted_language_data.json - Source file (not loaded)

Translation Function:

// In language.js
function translate(text) {
const currentLanguage = global("current_language").getValue();
const entry = language_list.find(item => item.english === text);
if (entry) {
return entry[currentLanguage.toLowerCase()] || text;
}
return text; // Returns original English if not found
}

Data Structure:

var language_list = [
{
id: 1,
type: "image",
english: "./images/company_logo_english.png",
kannada: "./images/company_logo_kannada.png",
hindi: "./images/company_logo_hindi.png",
// ... other languages
},
{
id: 2,
type: "text",
english: "Nursery",
kannada: "ನರ್ಸರಿ",
hindi: "नर्सरी",
bengali: "নার্সারি",
// ... other languages
}
// ... 26,000+ more entries
];

Limitations:

  • ❌ Requires code deployment to update translations
  • ❌ Large memory footprint (entire array loaded)
  • ❌ No version control for translations
  • ❌ JSON file not being used

Move from hardcoded array to IndexedDB for:

  • ✅ Dynamic updates without app redeployment
  • ✅ API-driven translation updates
  • ✅ Persistent storage across sessions
  • ✅ Version control for translations

Location:

  • www/scripts/functions.js - LanguageDataDBUtilClass (line 22016)

Database Structure:

  • Database Name: NurseryLanguageDB
  • Object Store: language_data
  • Version: 2
  • Primary Key: [language_id, text_id] (composite key)

Record Format:

{
language_id: "kannada", // Language identifier
text_id: 2, // Text entry ID
english_value: "Nursery", // English text
native_value: "ನರ್ಸರಿ" // Translated text
}
// Load from JSON file
await initializeLanguageDataDB("./scripts/formatted_language_data.json");
// Force reload (clears existing data)
await initializeLanguageDataDB("./scripts/formatted_language_data.json", true);
// Store JSON data directly
const jsonData = await fetch("./scripts/formatted_language_data.json").then(r => r.json());
await LanguageDataDB.storeLanguageData(jsonData, true); // true = clear existing
// Get all records for a language
const kannadaRecords = await LanguageDataDB.getLanguageRecords("kannada");
// Get specific translation by language_id and text_id
const translation = await LanguageDataDB.getTranslation("kannada", 2);
// Returns: { language_id: "kannada", text_id: 2, english_value: "Nursery", native_value: "ನರ್ಸರಿ" }
// Get translation by English text
const translated = await LanguageDataDB.getTranslationByEnglish("kannada", "Nursery");
// Returns: "ನರ್ಸರಿ"
// Get total record count
const count = await LanguageDataDB.getRecordCount();
// Clear all data
await LanguageDataDB.clearAll();

App Loads
language_list array loaded in memory
User selects language (e.g., "kannada")
translate("Nursery") called
Searches language_list array
Returns: "ನರ್ಸರಿ"
HTML updated with translated text
App Loads
Check API for language version
If new version exists:
- Download formatted_language_data.json
- Store in IndexedDB
User selects language (e.g., "kannada")
translate("Nursery") called
Query IndexedDB: getTranslationByEnglish("kannada", "Nursery")
Returns: "ನರ್ಸರಿ"
HTML updated with translated text
App Loads
Load current language from IndexedDB into memory cache
User selects language
translate("Nursery") called
Check memory cache first (fast)
If not in cache, query IndexedDB
Update cache with result
Return translated text

Step 1: Check for Updates

// On app startup (login page)
async function checkLanguageVersion() {
const response = await fetch('/api/language/version', {
headers: {
'Authorization': `Bearer ${JWT_token}`
}
});
const data = await response.json();
// Returns: { version: 2, url: "https://.../formatted_language_data.json" }
return data;
}

Step 2: Compare Versions

const storedVersion = localStorage.getItem('language_version') || 1;
const apiVersion = await checkLanguageVersion();
if (apiVersion.version > storedVersion) {
// Update needed
await updateLanguageData(apiVersion.url);
}

Step 3: Download and Store

async function updateLanguageData(jsonUrl) {
try {
// Download new JSON
const response = await fetch(jsonUrl);
const jsonData = await response.json();
// Store in IndexedDB (clears existing)
await LanguageDataDB.storeLanguageData(jsonData, true);
// Update version
localStorage.setItem('language_version', apiVersion.version);
console.log('Language data updated successfully');
} catch (error) {
console.error('Failed to update language data:', error);
// App continues with existing translations
}
}

Step 4: Background Update (Non-Blocking)

// On login page load
function initLoginPage() {
// Show login form immediately
showLoginForm();
// Check for updates in background (non-blocking)
checkLanguageVersion()
.then(data => {
if (needsUpdate(data)) {
updateLanguageData(data.url);
}
})
.catch(err => {
// Even if update fails, login still works
console.error('Language update failed:', err);
});
}

Expected API Response:

{
"version": 2,
"url": "https://api.example.com/language/formatted_language_data.json",
"last_updated": "2024-01-15T10:30:00Z",
"languages_supported": ["kannada", "hindi", "bengali", "tamil", "telugu", "punjabi", "malayalam", "marathi", "assamese", "gujrati"]
}

Current Implementation:

function translate(text) {
// 1. Check if language_list exists
if (!Array.isArray(language_list)) {
return text; // Fallback to English
}
// 2. Get current language
const currentLanguage = global("current_language").getValue();
// 3. If English, return original
if (currentLanguage === "english") {
return text;
}
// 4. Search array for translation
const entry = language_list.find(item => item.english === text);
// 5. Return translation or original
return entry?.[currentLanguage] || text;
}

Future Implementation (IndexedDB):

// Option 1: Async with caching
let translationCache = {}; // Memory cache
async function translate(text) {
const currentLanguage = global("current_language").getValue();
if (currentLanguage === "english") {
return text;
}
// Check cache first
const cacheKey = `${currentLanguage}_${text}`;
if (translationCache[cacheKey]) {
return translationCache[cacheKey];
}
// Query IndexedDB
try {
const translated = await LanguageDataDB.getTranslationByEnglish(
currentLanguage,
text
);
// Update cache
translationCache[cacheKey] = translated;
return translated;
} catch (error) {
// Fallback to English
return text;
}
}

Option 2: Sync with Pre-loaded Cache

// Load language into memory on language change
let currentLanguageCache = {};
async function loadLanguageCache(languageId) {
const records = await LanguageDataDB.getLanguageRecords(languageId);
currentLanguageCache = {};
records.forEach(record => {
currentLanguageCache[record.english_value] = record.native_value;
});
}
function translate(text) {
const currentLanguage = global("current_language").getValue();
if (currentLanguage === "english") {
return text;
}
// Fast lookup from memory cache
return currentLanguageCache[text] || text;
}

In internal.js:

  • load_language() - Translates all page elements
  • Button captions
  • Text elements
  • Input placeholders
  • Menu items
  • Alert dialogs

Example Usage:

// Translate button
document.getElementById("button_login").innerHTML = translate("Login");
// Translate text
document.getElementById("text_nursery").innerHTML = translate("Nursery");
// Translate placeholder
document.getElementById("input_search").placeholder = translate("Search");

Transformation: The JSON structure is flattened for efficient querying:

Input (JSON):

{
"id": 2,
"type": "text",
"english": "Nursery",
"kannada": { "value": "ನರ್ಸರಿ", "size": 0, "weight": 0 },
"hindi": { "value": "नर्सरी", "size": 0, "weight": 0 }
}

Output (IndexedDB):

// Multiple records created:
{ language_id: "kannada", text_id: 2, english_value: "Nursery", native_value: "ನರ್ಸರಿ" }
{ language_id: "hindi", text_id: 2, english_value: "Nursery", native_value: "नर्सरी" }

Why Batches?

  • IndexedDB transactions auto-commit when event loop finishes
  • Large datasets (26,000+ records) can cause transaction timeouts
  • Processing in batches of 1000 ensures transactions complete successfully

Implementation:

// Process in batches of 1000
const batchSize = 1000;
for (let i = 0; i < allRecords.length; i += batchSize) {
const batch = allRecords.slice(i, i + batchSize);
// Open new transaction for each batch
// Store batch
// Wait for transaction to complete
}

For efficient queries:

  • language_id index - Fast lookup by language
  • text_id index - Fast lookup by text ID
  • english_value index - Fast lookup by English text

  • ✅ IndexedDB utility class created
  • ✅ Data storage methods implemented
  • ✅ Query methods available
  • translate() still uses array
  1. Keep language_list array as fallback
  2. Update translate() to check IndexedDB first
  3. Fall back to array if IndexedDB not ready
  4. Load current language into memory cache

Implementation:

let useIndexedDB = false;
let languageCache = {};
// Check if IndexedDB has data
async function initTranslation() {
const count = await LanguageDataDB.getRecordCount();
if (count > 0) {
useIndexedDB = true;
await loadLanguageCache(global("current_language").getValue());
}
}
function translate(text) {
// Use cache if IndexedDB is ready
if (useIndexedDB && languageCache[text]) {
return languageCache[text];
}
// Fallback to array
if (Array.isArray(language_list)) {
const entry = language_list.find(item => item.english === text);
return entry?.[currentLanguage] || text;
}
return text;
}
  1. Remove language_list array
  2. translate() uses only IndexedDB
  3. Ensure IndexedDB is always initialized before use
  4. Implement proper error handling
  1. Implement version check API call
  2. Automatic updates on app startup
  3. Background sync mechanism

Q1: Is the JSON file currently being used?

Section titled “Q1: Is the JSON file currently being used?”

A: No. The formatted_language_data.json file exists but is not loaded or used. Everything currently uses the hardcoded language_list array in language.js.

Q2: What happens if I remove the array and IndexedDB isn’t ready?

Section titled “Q2: What happens if I remove the array and IndexedDB isn’t ready?”

A: The translate() function will return the original English text. Since HTML elements already contain English text (e.g., <h1>Nursery</h1>), users will see English until IndexedDB is ready. This is a safe fallback.

Q3: Will storing in IndexedDB on login page affect login?

Section titled “Q3: Will storing in IndexedDB on login page affect login?”

A: No. IndexedDB operations are asynchronous and non-blocking. The login form appears immediately, and language data stores in the background without affecting login functionality.

A: Yes, but there are two approaches:

  • IndexedDB cache: Persistent but slower (good for storing user’s frequently used translations)
  • Memory cache: Fast but temporary (good for current session’s active language)
  • Hybrid: Best of both - load current language into memory, use IndexedDB as source of truth

Q5: How does translation work when user selects a language?

Section titled “Q5: How does translation work when user selects a language?”

A:

  1. User selects language (e.g., “kannada”)
  2. global("current_language").setValue("kannada") is called
  3. load_language() function runs
  4. For each element with language: true, translate() is called
  5. translate() queries IndexedDB (or array) for translation
  6. Element’s innerHTML is updated with translated text

Q6: What’s the difference between the array and JSON file?

Section titled “Q6: What’s the difference between the array and JSON file?”

A:

  • Array (language_list): Hardcoded in JavaScript, loaded in memory, requires code deployment to update
  • JSON file: Source of truth, can be updated via API, needs to be loaded and stored in IndexedDB

Q7: How many records are stored in IndexedDB?

Section titled “Q7: How many records are stored in IndexedDB?”

A: Approximately 26,000+ records (26,018 text entries × 10 languages = ~260,180 records)

Q8: What if IndexedDB fails or is not supported?

Section titled “Q8: What if IndexedDB fails or is not supported?”

A: The system should fall back to the language_list array. Always implement fallback logic:

try {
// Try IndexedDB
return await LanguageDataDB.getTranslationByEnglish(language, text);
} catch (error) {
// Fallback to array
const entry = language_list.find(item => item.english === text);
return entry?.[language] || text;
}

Q9: How do I update translations without redeploying?

Section titled “Q9: How do I update translations without redeploying?”

A:

  1. Update formatted_language_data.json on server
  2. Increment version number
  3. App checks API on startup
  4. Downloads new JSON if version is higher
  5. Stores in IndexedDB
  6. Translations update automatically

A:

  • Array lookup: O(n) search, but in memory (very fast)
  • IndexedDB query: Async, but uses indexes (fast with proper indexing)
  • Memory cache: O(1) lookup (fastest)
  • Recommendation: Use memory cache for active language, IndexedDB for storage

// In login page or app initialization
async function initApp() {
try {
// Check if IndexedDB has data
const count = await LanguageDataDB.getRecordCount();
if (count === 0) {
// First time - load from JSON
await initializeLanguageDataDB("./scripts/formatted_language_data.json");
} else {
// Data exists - check for updates
checkForLanguageUpdates();
}
} catch (error) {
console.error("Language initialization failed:", error);
// App continues with array fallback
}
}
async function checkForLanguageUpdates() {
try {
const response = await fetch('/api/language/version', {
headers: { 'Authorization': `Bearer ${JWT_token}` }
});
const apiData = await response.json();
const storedVersion = parseInt(localStorage.getItem('language_version') || '1');
if (apiData.version > storedVersion) {
console.log('New language version available:', apiData.version);
// Download and update
const jsonData = await fetch(apiData.url).then(r => r.json());
await LanguageDataDB.storeLanguageData(jsonData, true);
localStorage.setItem('language_version', apiData.version);
// Reload current language cache if needed
await reloadLanguageCache();
}
} catch (error) {
console.error('Update check failed:', error);
}
}
let languageCache = {};
async function loadLanguageCache(languageId) {
try {
const records = await LanguageDataDB.getLanguageRecords(languageId);
languageCache = {};
records.forEach(record => {
if (record.native_value) {
languageCache[record.english_value] = record.native_value;
}
});
console.log(`Loaded ${Object.keys(languageCache).length} translations for ${languageId}`);
} catch (error) {
console.error('Failed to load language cache:', error);
languageCache = {}; // Empty cache, will fallback to array
}
}
// Call when language changes
function onLanguageChange(newLanguage) {
global("current_language").setValue(newLanguage);
loadLanguageCache(newLanguage).then(() => {
load_language(); // Refresh page translations
});
}
let useIndexedDB = false;
let languageCache = {};
// Initialize on app start
async function initTranslationSystem() {
const count = await LanguageDataDB.getRecordCount();
if (count > 0) {
useIndexedDB = true;
const currentLang = global("current_language").getValue();
await loadLanguageCache(currentLang);
}
}
function translate(text) {
// Early returns
if (!text) return text;
const currentLanguage = global("current_language").getValue();
if (!currentLanguage || currentLanguage === "english") {
return text;
}
// Try memory cache first (fastest)
if (useIndexedDB && languageCache[text]) {
return languageCache[text];
}
// Fallback to array
if (Array.isArray(language_list)) {
const entry = language_list.find(item => item.english === text);
if (entry && entry[currentLanguage]) {
return entry[currentLanguage];
}
}
// Return original if no translation found
return text;
}

Issue: IndexedDB not created after clearing

Section titled “Issue: IndexedDB not created after clearing”

Solution: Increment database version to force recreation:

this.dbVersion = 2; // Change from 1 to 2

Solution: Process records in batches (already implemented):

const batchSize = 1000; // Process 1000 records per transaction

Check:

  1. Is IndexedDB initialized? await LanguageDataDB.getRecordCount()
  2. Is language cache loaded? console.log(languageCache)
  3. Is current language set? global("current_language").getValue()
  4. Does translation exist? await LanguageDataDB.getTranslationByEnglish("kannada", "Nursery")

Solution: Use memory cache for active language:

// Load current language into memory on language change
await loadLanguageCache(currentLanguage);

Solution: Implement proper error handling and fallback:

try {
await updateLanguageData(apiUrl);
} catch (error) {
console.error('Update failed, using existing data:', error);
// App continues with current translations
}

www/
├── scripts/
│ ├── language.js # Current: language_list array + translate()
│ ├── formatted_language_data.json # Source file (not currently used)
│ ├── functions.js # New: LanguageDataDBUtilClass
│ └── internal.js # Uses translate() for page elements
└── index.html # Login page (where initialization happens)

  1. ✅ IndexedDB utility created
  2. ⏳ Update translate() to use IndexedDB with fallback
  3. ⏳ Implement memory caching
  4. ⏳ Add API version check
  5. ⏳ Test on login page
  6. ⏳ Remove language_list array (after full migration)

For issues or questions:

  • Check console logs (all IndexedDB operations are logged)
  • Verify IndexedDB is supported: window.indexedDB
  • Check database in DevTools: Application → IndexedDB → NurseryLanguageDB

Last Updated: 2024-01-15 Version: 1.0