docs: document translation system and update ui-translations module

This commit is contained in:
Russell Ballestrini 2025-07-03 12:51:52 -04:00
parent b9ab0e83b3
commit 65504b2d19
2 changed files with 218 additions and 208 deletions

View file

@ -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.
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.

View file

@ -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 <a href="https://nousresearch.com/hermes3/" target="_blank" style="color: rgba(255,255,255,0.9); text-decoration: underline;">hermes</a> 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的<a href="https://nousresearch.com/hermes3/" target="_blank" style="color: rgba(255,255,255,0.9); text-decoration: underline;">hermes</a>大型语言模型',
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 के <a href="https://nousresearch.com/hermes3/" target="_blank" style="color: rgba(255,255,255,0.9); text-decoration: underline;">hermes</a> बड़े भाषा मॉडल प्रस्तुत करता है',
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;