docs: document translation system and update ui-translations module
This commit is contained in:
parent
b9ab0e83b3
commit
65504b2d19
2 changed files with 218 additions and 208 deletions
103
journal.rst
103
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.
|
||||
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.
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue