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;