diff --git a/journal.rst b/journal.rst
index 6a11aeb..3497d38 100644
--- a/journal.rst
+++ b/journal.rst
@@ -17,4 +17,105 @@ Built `token_estimator.js` - a simplified BPE-based token counter for translatio
- Generation: 121 tokens/sec
- Translation estimates: 1K tokens ≈ 11 seconds
-The estimator uses real production metrics from our Hermes machine to provide realistic translation timing.
\ No newline at end of file
+The estimator uses real production metrics from our Hermes machine to provide realistic translation timing.
+
+July 3, 2025
+============
+
+Complete Translation Infrastructure Overhaul
+--------------------------------------------
+
+**Modular Language System Implementation**
+
+Restructured the entire translation system to use individual language files instead of one monolithic object. Each of the 19 supported languages now has its own dedicated JS file in `/src/languages/`.
+
+**Language Coverage:**
+- 🇺🇸 English (en.js) - 46 translation keys
+- 🇨🇳 Chinese Simplified (zh.js)
+- 🇹🇼 Chinese Traditional (zh-tw.js)
+- 🇮🇳 Hindi (hi.js)
+- 🇪🇸 Spanish (es.js)
+- 🇫🇷 French (fr.js)
+- 🇸🇦 Arabic (ar.js)
+- 🇧🇩 Bengali (bn.js)
+- 🇷🇺 Russian (ru.js)
+- 🇧🇷 Portuguese (pt.js)
+- 🇵🇰 Urdu (ur.js)
+- 🇮🇩 Indonesian (id.js)
+- 🇩🇪 German (de.js)
+- 🇯🇵 Japanese (ja.js)
+- 🇰🇪 Swahili (sw.js)
+- 🇮🇳 Marathi (mr.js)
+- 🇮🇳 Telugu (te.js)
+- 🇹🇷 Turkish (tr.js)
+- 🇰🇷 Korean (ko.js)
+
+**Dynamic UI Refresh System**
+
+Implemented `refreshUILanguage()` function that updates all UI text without requiring browser refresh:
+
+- **Declarative translations**: Elements with `data-i18n` attributes automatically update
+- **Parameter support**: `data-i18n-params` for dynamic text with variables like `{lang}` and `{error}`
+- **Smart element detection**: Handles inputs, buttons, placeholders, and modal titles
+- **Event system**: Dispatches `languageChanged` custom events for component integration
+- **Backward compatibility**: Maintains `window.refreshUILanguage` for existing code
+
+**Translation Verification System**
+
+Created `verify-translations.js` - a Node.js script that ensures translation consistency:
+
+- **Key extraction**: Parses JS files to extract translation object keys
+- **Completeness verification**: Compares all languages against English reference
+- **Missing/extra key detection**: Reports inconsistencies with colored console output
+- **Exit codes**: Returns 0 for success, 1 for issues (CI/CD friendly)
+
+**Adding New Languages - Workflow:**
+
+1. **Before adding**: Run verification to ensure current state is clean
+ ```bash
+ cd /home/fox/git/ai.unturf.com/src/languages
+ node verify-translations.js
+ ```
+ Should show: "🎉 All languages have complete translations!"
+
+2. **Create new language file**: Copy structure from `en.js`
+ ```bash
+ cp en.js new-lang.js # Replace 'new-lang' with actual language code
+ ```
+
+3. **Translate all values**: Keep the same keys, translate only the string values
+
+4. **Update index.js**: Add import and export for the new language
+ ```javascript
+ import { newLang } from "./new-lang.js";
+ // Add to UI_TRANSLATIONS object
+ ```
+
+5. **Verify completeness**: Run script again to check for issues
+ ```bash
+ node verify-translations.js
+ ```
+
+6. **Fix any issues**: Script will show missing/extra keys in red
+ - Missing keys: Add them with proper translations
+ - Extra keys: Remove them or add to English reference if needed
+
+7. **Final verification**: Run until you see the success message again
+
+**File Structure:**
+```
+src/languages/
+├── index.js # Aggregates all translations
+├── en.js # English reference (46 keys)
+├── [18 other languages] # Complete translations
+└── verify-translations.js # Sanity check script
+```
+
+**Benefits:**
+- **Maintainability**: Each language isolated in its own file
+- **Consistency**: 100% key coverage verified across all 19 languages
+- **Real-time UX**: Language switching without page refresh
+- **Developer experience**: Automated verification prevents translation drift
+- **Scalability**: Easy to add new languages following the established pattern
+
+This system supports the full user journey from language detection to dynamic interface updates, ensuring a seamless multilingual experience for all Hermes AI users.
\ No newline at end of file
diff --git a/src/ui-translations.js b/src/ui-translations.js
index b22d458..f598fbe 100644
--- a/src/ui-translations.js
+++ b/src/ui-translations.js
@@ -1,211 +1,11 @@
// UI Translations for all supported languages
// This file contains all user interface text translations
-// Complete UI Translations for all supported languages
-export const UI_TRANSLATIONS = {
- en: {
- // Main buttons
- readPage: "📖 Read Page",
- ttsAnything: "🔊 TTS Anything",
- translate: "🌐 Translate",
- smartTranslate: "🤖 Smart Translate",
- uploadFile: "📁 Upload File",
+// Import translations from individual language files
+import { UI_TRANSLATIONS } from "./languages/index.js";
- // Settings
- modelLabel: "AI Model:",
- voiceLabel: "TTS Voice:",
- languageLabel: "Interface Language:",
- quickActions: "Quick Actions:",
-
- // Actions
- refreshModels: "🔄 Refresh Models",
- clearChat: "🗑️ Clear Chat",
- fullChatRaw: "📋 Full Chat Raw",
- fullChatHTML: "📄 Full Chat HTML",
- fullPageRaw: "📝 Full Page Raw",
- downloadChat: "💾 Download Chat",
-
- // Chat UI
- sendButton: "Send",
- typePlaceholder: "Type your message...",
- clearChatConfirm: "Clear all conversation history?",
-
- // Modal headers
- aiAssistantTitle: "AI Assistant",
- ttsModalTitle: "Text-to-Speech",
- translateModalTitle: "Translate Page",
-
- // System messages
- pageContentPrefix: "Here's the content of the webpage:",
- additionalContext:
- "You are embedded on this page. When answering questions, consider the page content and context.",
-
- // Notifications and alerts
- languageChanged:
- "Language set to {lang}. Refresh the page to see all interface elements in your preferred language.",
- translationFailed: "Translation failed: {error}",
- ttsFailed: "TTS failed: {error}",
- fullChatCopied: "Full chat copied as raw markdown!",
- fullChatHTMLCopied: "Full chat copied as HTML!",
- fullPageCopied: "Full page copied as markdown!",
- failedToCopy: "Failed to copy: {error}",
- failedToCopyPage: "Failed to copy page: {error}",
- failedToCopyCode: "Failed to copy code: {error}",
- errorOpeningModal: "Error opening AI modal: {error}",
- failedToReadPage: "Failed to read page: {error}",
- languageDetectionFailed: "Language detection failed: {error}",
-
- // Dropdown labels
- currentPage: "Current page: {lang}",
- translatingTo: "Translating...",
- detectingLanguage: "🔍 Detecting...",
-
- // Intro/header text
- hermesIntro:
- 'presents nous research\'s hermes large language model',
- discussingPage: "You are discussing",
-
- // TTS placeholder
- ttsPlaceholder:
- "text-to-speech: write any message & have the artificial intelligence speak it!",
- translatePlaceholder: "Enter any text to translate...",
-
- // More alerts and messages
- pleaseEnterText: "Please enter some text first!",
- pleaseSelectFile: "Please select a file first!",
- pleaseEnterTranslateText: "Please enter some text to translate!",
- aiModalNotAvailable: "AI modal not available in this context",
- },
-
- // Chinese (Simplified)
- zh: {
- readPage: "📖 阅读页面",
- ttsAnything: "🔊 语音合成",
- translate: "🌐 翻译",
- smartTranslate: "🤖 智能翻译",
- uploadFile: "📁 上传文件",
- modelLabel: "AI 模型:",
- voiceLabel: "语音类型:",
- languageLabel: "界面语言:",
- quickActions: "快捷操作:",
- refreshModels: "🔄 刷新模型",
- clearChat: "🗑️ 清除聊天",
- fullChatRaw: "📋 完整聊天记录",
- fullChatHTML: "📄 完整聊天HTML",
- fullPageRaw: "📝 完整页面原始",
- downloadChat: "💾 下载聊天",
- sendButton: "发送",
- typePlaceholder: "输入您的消息...",
- clearChatConfirm: "清除所有对话历史记录?",
- aiAssistantTitle: "AI助手",
- ttsModalTitle: "文本转语音",
- translateModalTitle: "翻译页面",
- pageContentPrefix: "以下是网页内容:",
- additionalContext: "您已嵌入此页面。回答问题时,请考虑页面内容和上下文。",
- languageChanged:
- "语言设置为 {lang}。刷新页面以查看所有界面元素的首选语言。",
- translationFailed: "翻译失败:{error}",
- ttsFailed: "语音合成失败:{error}",
- fullChatCopied: "完整聊天记录已复制为markdown!",
- fullChatHTMLCopied: "完整聊天记录已复制为HTML!",
- fullPageCopied: "完整页面已复制为markdown!",
- failedToCopy: "复制失败:{error}",
- failedToCopyPage: "复制页面失败:{error}",
- failedToCopyCode: "复制代码失败:{error}",
- errorOpeningModal: "打开AI模态框时出错:{error}",
- failedToReadPage: "读取页面失败:{error}",
- languageDetectionFailed: "语言检测失败:{error}",
- currentPage: "当前页面:{lang}",
- translatingTo: "翻译中...",
- detectingLanguage: "🔍 检测中...",
- hermesIntro:
- '呈现nous research的hermes大型语言模型',
- discussingPage: "您正在讨论",
- ttsPlaceholder: "文本转语音:写任何消息,让人工智能朗读出来!",
- translatePlaceholder: "输入任何要翻译的文本...",
- pleaseEnterText: "请先输入一些文本!",
- pleaseSelectFile: "请先选择一个文件!",
- pleaseEnterTranslateText: "请输入一些要翻译的文本!",
- aiModalNotAvailable: "AI模态框在此上下文中不可用",
- },
-
- // Hindi
- hi: {
- readPage: "📖 पृष्ठ पढ़ें",
- ttsAnything: "🔊 TTS कुछ भी",
- translate: "🌐 अनुवाद करें",
- smartTranslate: "🤖 स्मार्ट अनुवाद",
- uploadFile: "📁 फ़ाइल अपलोड करें",
- modelLabel: "AI मॉडल:",
- voiceLabel: "TTS आवाज़:",
- languageLabel: "इंटरफ़ेस भाषा:",
- quickActions: "त्वरित क्रियाएं:",
- refreshModels: "🔄 मॉडल रीफ्रेश करें",
- clearChat: "🗑️ चैट साफ़ करें",
- fullChatRaw: "📋 पूर्ण चैट रॉ",
- fullChatHTML: "📄 पूर्ण चैट HTML",
- fullPageRaw: "📝 पूर्ण पृष्ठ रॉ",
- downloadChat: "💾 चैट डाउनलोड करें",
- sendButton: "भेजें",
- typePlaceholder: "अपना संदेश टाइप करें...",
- clearChatConfirm: "सभी वार्तालाप इतिहास साफ़ करें?",
- aiAssistantTitle: "AI सहायक",
- ttsModalTitle: "टेक्स्ट-टू-स्पीच",
- translateModalTitle: "पृष्ठ अनुवाद",
- pageContentPrefix: "यहाँ वेबपेज की सामग्री है:",
- additionalContext:
- "आप इस पृष्ठ पर एम्बेडेड हैं। प्रश्नों का उत्तर देते समय, पृष्ठ सामग्री और संदर्भ पर विचार करें।",
- languageChanged:
- "भाषा {lang} पर सेट की गई। अपनी पसंदीदा भाषा में सभी इंटरफ़ेस तत्वों को देखने के लिए पृष्ठ को रीफ्रेश करें।",
- translationFailed: "अनुवाद विफल: {error}",
- ttsFailed: "TTS विफल: {error}",
- fullChatCopied: "पूर्ण चैट रॉ मार्कडाउन के रूप में कॉपी किया गया!",
- fullChatHTMLCopied: "पूर्ण चैट HTML के रूप में कॉपी किया गया!",
- fullPageCopied: "पूर्ण पृष्ठ मार्कडाउन के रूप में कॉपी किया गया!",
- failedToCopy: "कॉपी करने में विफल: {error}",
- failedToCopyPage: "पृष्ठ कॉपी करने में विफल: {error}",
- failedToCopyCode: "कोड कॉपी करने में विफल: {error}",
- errorOpeningModal: "AI मॉडल खोलने में त्रुटि: {error}",
- failedToReadPage: "पृष्ठ पढ़ने में विफल: {error}",
- languageDetectionFailed: "भाषा का पता लगाने में विफल: {error}",
- currentPage: "वर्तमान पृष्ठ: {lang}",
- translatingTo: "अनुवाद हो रहा है...",
- detectingLanguage: "🔍 पता लगाया जा रहा है...",
- hermesIntro:
- 'nous research के hermes बड़े भाषा मॉडल प्रस्तुत करता है',
- discussingPage: "आप चर्चा कर रहे हैं",
- ttsPlaceholder: "टेक्स्ट-टू-स्पीच: कोई भी संदेश लिखें और कृत्रिम बुद्धि इसे बोलने दें!",
- translatePlaceholder: "अनुवाद के लिए कोई भी टेक्स्ट दर्ज करें...",
- pleaseEnterText: "कृपया पहले कुछ टेक्स्ट दर्ज करें!",
- pleaseSelectFile: "कृपया पहले एक फ़ाइल चुनें!",
- pleaseEnterTranslateText: "कृपया अनुवाद के लिए कुछ टेक्स्ट दर्ज करें!",
- aiModalNotAvailable: "AI मॉडल इस संदर्भ में उपलब्ध नहीं है",
- },
-
- // TODO: Add remaining 16 languages with all keys
- // For now, other languages will fall back to English
-
- // Spanish - basic translations (needs complete keys)
- es: {
- readPage: "📖 Leer Página",
- ttsAnything: "🔊 TTS Todo",
- translate: "🌐 Traducir",
- smartTranslate: "🤖 Traducción Inteligente",
- // Additional keys will be added via translation system
- },
-
- // French - basic translations (needs complete keys)
- fr: {
- readPage: "📖 Lire la Page",
- ttsAnything: "🔊 TTS Tout",
- translate: "🌐 Traduire",
- smartTranslate: "🤖 Traduction Intelligente",
- // Additional keys will be added via translation system
- },
-
- // Additional languages with basic keys...
- // Full translations for all 19 languages will be added systematically
-};
+// Export UI_TRANSLATIONS for use in other modules
+export { UI_TRANSLATIONS };
// Get UI text in user's preferred language
export function getUIText(key, params = {}) {
@@ -236,14 +36,123 @@ export function setUserLanguagePreference(langCode) {
localStorage.setItem("uncloseai_language", langCode);
console.log("Language preference saved:", langCode);
// Trigger UI refresh when language changes
- if (typeof window !== "undefined" && window.refreshUILanguage) {
- window.refreshUILanguage();
- }
+ refreshUILanguage();
} catch (error) {
console.warn("Failed to save language preference:", error);
}
}
+// Dynamic UI refresh mechanism
+export function refreshUILanguage() {
+ if (typeof window === "undefined") return;
+
+ try {
+ // Update all elements with data-i18n attributes
+ const elementsWithI18n = document.querySelectorAll("[data-i18n]");
+ elementsWithI18n.forEach((element) => {
+ const key = element.getAttribute("data-i18n");
+ const params = element.getAttribute("data-i18n-params");
+
+ let parsedParams = {};
+ if (params) {
+ try {
+ parsedParams = JSON.parse(params);
+ } catch (e) {
+ console.warn("Failed to parse i18n params:", params);
+ }
+ }
+
+ const translatedText = getUIText(key, parsedParams);
+
+ // Handle different element types
+ if (element.tagName === "INPUT" && element.type !== "submit") {
+ if (element.hasAttribute("placeholder")) {
+ element.placeholder = translatedText;
+ } else {
+ element.value = translatedText;
+ }
+ } else if (element.tagName === "INPUT" && element.type === "submit") {
+ element.value = translatedText;
+ } else {
+ // For regular elements, check if they contain HTML
+ if (translatedText.includes("<")) {
+ element.innerHTML = translatedText;
+ } else {
+ element.textContent = translatedText;
+ }
+ }
+ });
+
+ // Update specific UI elements that might not have data-i18n attributes
+ updateCommonUIElements();
+
+ console.log("UI language refreshed successfully");
+
+ // Dispatch custom event for other components to listen to
+ window.dispatchEvent(new CustomEvent("languageChanged", {
+ detail: { language: getUserLanguagePreference() }
+ }));
+
+ } catch (error) {
+ console.warn("Failed to refresh UI language:", error);
+ }
+}
+
+// Update common UI elements that might not have data-i18n attributes
+function updateCommonUIElements() {
+ const lang = getUserLanguagePreference();
+
+ // Update button texts
+ const buttons = {
+ "button[onclick*='readPage']": "readPage",
+ "button[onclick*='openTTSModal']": "ttsAnything",
+ "button[onclick*='translatePage']": "translate",
+ "button[onclick*='smartTranslate']": "smartTranslate",
+ "button[onclick*='uploadFile']": "uploadFile",
+ "button[onclick*='refreshModels']": "refreshModels",
+ "button[onclick*='clearChat']": "clearChat",
+ "button[onclick*='downloadChat']": "downloadChat"
+ };
+
+ Object.entries(buttons).forEach(([selector, key]) => {
+ const element = document.querySelector(selector);
+ if (element) {
+ element.textContent = getUIText(key);
+ }
+ });
+
+ // Update modal titles
+ const modals = document.querySelectorAll("dialog h2, .modal-title");
+ modals.forEach((modal) => {
+ if (modal.textContent.includes("AI Assistant") || modal.id === "ai-assistant-title") {
+ modal.textContent = getUIText("aiAssistantTitle");
+ } else if (modal.textContent.includes("Text-to-Speech") || modal.id === "tts-title") {
+ modal.textContent = getUIText("ttsModalTitle");
+ } else if (modal.textContent.includes("Translate") || modal.id === "translate-title") {
+ modal.textContent = getUIText("translateModalTitle");
+ }
+ });
+
+ // Update placeholders
+ const placeholders = {
+ "textarea[placeholder*='Type your message']": "typePlaceholder",
+ "textarea[placeholder*='text-to-speech']": "ttsPlaceholder",
+ "textarea[placeholder*='translate']": "translatePlaceholder"
+ };
+
+ Object.entries(placeholders).forEach(([selector, key]) => {
+ const element = document.querySelector(selector);
+ if (element) {
+ element.placeholder = getUIText(key);
+ }
+ });
+}
+
+// Initialize UI refresh function on window object for backward compatibility
+if (typeof window !== "undefined") {
+ window.refreshUILanguage = refreshUILanguage;
+}
+
// Translation function for UI text
export async function translateUIText(text, targetLang) {
if (targetLang === "en" || !text) return text;