feat(ui): complete core translation infrastructure
- Update batman toolbelt action buttons to use getUIText() - Translate all modal headers (AI Assistant, TTS, Translate) - Convert all input placeholders to use translations - Update critical alert messages with parameter substitution - Add comprehensive translation keys for UI elements - Translate Hermes intro message with proper HTML formatting - Support both English and Chinese translations for all new keys - Maintain fallback to English for missing translations - Fix biome style issues (template literals, optional chains)
This commit is contained in:
parent
eee33986cf
commit
c263cb9d56
3 changed files with 100 additions and 51 deletions
|
|
@ -169,11 +169,11 @@ export class SimpleBPETokenizer {
|
|||
// Check token type and split if needed
|
||||
if (this.isWord(trimmed)) {
|
||||
return this.splitWord(trimmed);
|
||||
} else if (this.isNumber(trimmed)) {
|
||||
return this.splitNumber(trimmed);
|
||||
} else {
|
||||
return [token]; // Punctuation, symbols, etc.
|
||||
}
|
||||
if (this.isNumber(trimmed)) {
|
||||
return this.splitNumber(trimmed);
|
||||
}
|
||||
return [token]; // Punctuation, symbols, etc.
|
||||
}
|
||||
|
||||
isWord(token) {
|
||||
|
|
@ -210,12 +210,11 @@ export class SimpleBPETokenizer {
|
|||
if (remaining.length <= 4) {
|
||||
tokens.push(remaining);
|
||||
break;
|
||||
} else {
|
||||
// Take first 3-4 characters
|
||||
const chunkSize = Math.min(4, Math.ceil(remaining.length / 2));
|
||||
tokens.push(remaining.substring(0, chunkSize));
|
||||
remaining = remaining.substring(chunkSize);
|
||||
}
|
||||
// Take first 3-4 characters
|
||||
const chunkSize = Math.min(4, Math.ceil(remaining.length / 2));
|
||||
tokens.push(remaining.substring(0, chunkSize));
|
||||
remaining = remaining.substring(chunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ export async function translateText(text, targetLanguage) {
|
|||
console.log(`Input tokens: ${tokenInfo.totalTokens}`);
|
||||
console.log(`Text length: ${tokenInfo.textLength} characters`);
|
||||
console.log(`Estimated processing time: ${estimatedSeconds} seconds`);
|
||||
console.log(`Token breakdown:`, tokenInfo.breakdown);
|
||||
console.log("Token breakdown:", tokenInfo.breakdown);
|
||||
|
||||
const { preservedText, preservations } = preserveSpecialContent(text);
|
||||
|
||||
|
|
|
|||
132
src/ui.js
132
src/ui.js
|
|
@ -76,10 +76,9 @@ async function detectPageLanguage() {
|
|||
if (/^[a-z]{2,3}$/.test(detectedLang)) {
|
||||
console.log("AI detected language:", detectedLang);
|
||||
return detectedLang;
|
||||
} else {
|
||||
console.warn("Invalid language code from AI:", detectedLang);
|
||||
return fallbackLanguageDetection();
|
||||
}
|
||||
console.warn("Invalid language code from AI:", detectedLang);
|
||||
return fallbackLanguageDetection();
|
||||
} catch (error) {
|
||||
console.warn("Language detection error:", error);
|
||||
return fallbackLanguageDetection();
|
||||
|
|
@ -126,6 +125,8 @@ const UI_TRANSLATIONS = {
|
|||
refreshModels: "🔄 Refresh Models",
|
||||
clearChat: "🗑️ Clear Chat",
|
||||
fullChatRaw: "📋 Full Chat Raw",
|
||||
fullChatHTML: "📄 Full Chat HTML",
|
||||
fullPageRaw: "📝 Full Page Raw",
|
||||
downloadChat: "💾 Download Chat",
|
||||
|
||||
// Chat UI
|
||||
|
|
@ -133,21 +134,51 @@ const UI_TRANSLATIONS = {
|
|||
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
|
||||
// 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)
|
||||
|
|
@ -164,19 +195,42 @@ const UI_TRANSLATIONS = {
|
|||
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
|
||||
|
|
@ -783,8 +837,7 @@ function refreshUILanguage() {
|
|||
function detectCurrentTheme() {
|
||||
const isDark =
|
||||
document.documentElement.getAttribute("data-theme") === "dark" ||
|
||||
(window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches &&
|
||||
(window.matchMedia?.("(prefers-color-scheme: dark)").matches &&
|
||||
!document.documentElement.getAttribute("data-theme"));
|
||||
|
||||
return isDark ? "dark" : "light";
|
||||
|
|
@ -926,7 +979,7 @@ function addCodeBlockCopyButtons(element) {
|
|||
copyBtn.textContent = originalText;
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
alert("Failed to copy code: " + error.message);
|
||||
alert(getUIText("failedToCopyCode", { error: error.message }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1241,7 +1294,7 @@ export async function handleTTSFromElement(button) {
|
|||
const text = textarea?.value?.trim();
|
||||
|
||||
if (!text) {
|
||||
alert("Please enter some text first!");
|
||||
alert(getUIText("pleaseEnterText"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1271,7 +1324,7 @@ export async function handleTTSFromElement(button) {
|
|||
resultDiv.appendChild(result.audio);
|
||||
resultDiv.appendChild(audioControls);
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = "<strong>Error:</strong> " + error.message;
|
||||
resultDiv.innerHTML = `<strong>Error:</strong> ${error.message}`;
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "🔊 Convert to Speech";
|
||||
|
|
@ -1284,7 +1337,7 @@ export async function handleUploadFromElement(button) {
|
|||
const resultDiv = container.querySelector("[data-upload-result]");
|
||||
|
||||
if (!fileInput.files[0]) {
|
||||
alert("Please select a file first!");
|
||||
alert(getUIText("pleaseSelectFile"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1302,7 +1355,7 @@ export async function handleUploadFromElement(button) {
|
|||
fileInput.value = "";
|
||||
} catch (error) {
|
||||
hideProgressIndicator();
|
||||
resultDiv.innerHTML = "<strong>Error:</strong> " + error.message;
|
||||
resultDiv.innerHTML = `<strong>Error:</strong> ${error.message}`;
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.textContent = "📁 Upload & Analyze";
|
||||
|
|
@ -1463,7 +1516,7 @@ export async function handleSmartTranslate(button) {
|
|||
} catch (error) {
|
||||
button.textContent = getUIText("smartTranslate");
|
||||
button.disabled = false;
|
||||
alert(`Language detection failed: ${error.message}`);
|
||||
alert(getUIText("languageDetectionFailed", { error: error.message }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1672,11 +1725,11 @@ function setupFloatingButtonHandler(floatingButton) {
|
|||
console.error(
|
||||
"toggleUncloseaiEmbeddedModal function not available in any form",
|
||||
);
|
||||
alert("AI modal not available in this context");
|
||||
alert(getUIText("aiModalNotAvailable"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error opening modal:", error);
|
||||
alert("Error opening AI modal: " + error.message);
|
||||
alert(getUIText("errorOpeningModal", { error: error.message }));
|
||||
} finally {
|
||||
// Restore button state
|
||||
floatingButton.textContent = originalText;
|
||||
|
|
@ -1774,8 +1827,8 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
const pageTitle = document.title || window.location.hostname;
|
||||
subtitle.innerHTML = `
|
||||
<div style="font-size: 12px; opacity: 0.9; font-weight: normal; line-height: 1.3;">
|
||||
<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> 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<br>
|
||||
<span style="opacity: 0.8;">You are discussing: ${pageTitle}</span>
|
||||
<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> ${getUIText("hermesIntro")}<br>
|
||||
<span style="opacity: 0.8;">${getUIText("discussingPage")}: ${pageTitle}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -1874,7 +1927,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
// Model selection
|
||||
const modelSection = document.createElement("div");
|
||||
modelSection.style.cssText = `margin-bottom: 16px;`;
|
||||
modelSection.style.cssText = "margin-bottom: 16px;";
|
||||
|
||||
const modelLabel = document.createElement("label");
|
||||
modelLabel.textContent = getUIText("modelLabel");
|
||||
|
|
@ -1973,7 +2026,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
// Voice selection for TTS
|
||||
const voiceSection = document.createElement("div");
|
||||
voiceSection.style.cssText = `margin-bottom: 16px;`;
|
||||
voiceSection.style.cssText = "margin-bottom: 16px;";
|
||||
|
||||
const voiceLabel = document.createElement("label");
|
||||
voiceLabel.textContent = getUIText("voiceLabel");
|
||||
|
|
@ -2015,7 +2068,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
// Language preference section
|
||||
const languageSection = document.createElement("div");
|
||||
languageSection.style.cssText = `margin-bottom: 16px;`;
|
||||
languageSection.style.cssText = "margin-bottom: 16px;";
|
||||
|
||||
const languageLabel = document.createElement("label");
|
||||
languageLabel.textContent = getUIText("languageLabel");
|
||||
|
|
@ -2074,7 +2127,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
// Action buttons
|
||||
const actionsSection = document.createElement("div");
|
||||
actionsSection.style.cssText = `margin-bottom: 16px;`;
|
||||
actionsSection.style.cssText = "margin-bottom: 16px;";
|
||||
|
||||
const actionsLabel = document.createElement("div");
|
||||
actionsLabel.textContent = getUIText("quickActions");
|
||||
|
|
@ -2374,7 +2427,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
isReadingPage = false;
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Failed to read page: " + error.message);
|
||||
alert(getUIText("failedToReadPage", { error: error.message }));
|
||||
button.textContent = "📖 Read Page";
|
||||
isReadingPage = false;
|
||||
} finally {
|
||||
|
|
@ -2512,12 +2565,12 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
} catch (error) {
|
||||
btn.textContent = "🤖 Smart Translate";
|
||||
btn.disabled = false;
|
||||
alert(`Language detection failed: ${error.message}`);
|
||||
alert(getUIText("languageDetectionFailed", { error: error.message }));
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "📋 Full Chat Raw",
|
||||
text: getUIText("fullChatRaw"),
|
||||
action: async () => {
|
||||
try {
|
||||
const { getChatHistory } = await import("./chat.js");
|
||||
|
|
@ -2531,14 +2584,14 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
}
|
||||
});
|
||||
await navigator.clipboard.writeText(rawContent);
|
||||
alert("Full chat copied as raw markdown!");
|
||||
alert(getUIText("fullChatCopied"));
|
||||
} catch (error) {
|
||||
alert("Failed to copy: " + error.message);
|
||||
alert(getUIText("failedToCopy", { error: error.message }));
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "📄 Full Chat HTML",
|
||||
text: getUIText("fullChatHTML"),
|
||||
action: async () => {
|
||||
try {
|
||||
const { getChatHistory } = await import("./chat.js");
|
||||
|
|
@ -2554,14 +2607,14 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
});
|
||||
htmlContent += "</div>";
|
||||
await navigator.clipboard.writeText(htmlContent);
|
||||
alert("Full chat copied as HTML!");
|
||||
alert(getUIText("fullChatHTMLCopied"));
|
||||
} catch (error) {
|
||||
alert("Failed to copy: " + error.message);
|
||||
alert(getUIText("failedToCopy", { error: error.message }));
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "📝 Full Page Raw",
|
||||
text: getUIText("fullPageRaw"),
|
||||
action: async () => {
|
||||
try {
|
||||
// Get the entire page HTML
|
||||
|
|
@ -2569,9 +2622,9 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
// Convert to markdown
|
||||
const markdown = htmlToMarkdown(pageHtml);
|
||||
await navigator.clipboard.writeText(markdown);
|
||||
alert("Full page copied as markdown!");
|
||||
alert(getUIText("fullPageCopied"));
|
||||
} catch (error) {
|
||||
alert("Failed to copy page: " + error.message);
|
||||
alert(getUIText("failedToCopyPage", { error: error.message }));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -2623,7 +2676,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
const input = document.createElement("textarea");
|
||||
input.id = "modal-user-input";
|
||||
input.placeholder = "Ask about this page...";
|
||||
input.placeholder = getUIText("typePlaceholder");
|
||||
input.style.cssText = `
|
||||
flex: 1;
|
||||
min-height: 44px;
|
||||
|
|
@ -2646,7 +2699,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
// Auto-resize textarea
|
||||
input.oninput = () => {
|
||||
input.style.height = "auto";
|
||||
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
||||
input.style.height = `${Math.min(input.scrollHeight, 120)}px`;
|
||||
};
|
||||
|
||||
const sendBtn = document.createElement("button");
|
||||
|
|
@ -4588,7 +4641,7 @@ Format: Start with "Greetings! I'm Hermes..." and make it sound natural and enga
|
|||
|
||||
const userInput = document.createElement("textarea");
|
||||
userInput.id = "modal-user-input";
|
||||
userInput.placeholder = "Ask about this page...";
|
||||
userInput.placeholder = getUIText("typePlaceholder");
|
||||
if (USE_CUSTOM_STYLING) {
|
||||
userInput.style.cssText = `
|
||||
flex: 1;
|
||||
|
|
@ -4780,8 +4833,7 @@ export function openTTSModal() {
|
|||
article.appendChild(header);
|
||||
|
||||
const h1 = document.createElement("h1");
|
||||
h1.innerHTML =
|
||||
"<span style=\"font-family: 'ChunkFiveRegular', monospace;\">uncloseai.</span> TTS Anything!";
|
||||
h1.innerHTML = `<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> ${getUIText("ttsModalTitle")}`;
|
||||
if (USE_CUSTOM_STYLING) {
|
||||
h1.style.cssText = `
|
||||
margin: 0;
|
||||
|
|
@ -4834,8 +4886,7 @@ export function openTTSModal() {
|
|||
const textArea = document.createElement("textarea");
|
||||
textArea.style.width = "100%";
|
||||
textArea.style.height = "240px";
|
||||
textArea.placeholder =
|
||||
"text-to-speech: write any message & have the artificial intelligence speak it!";
|
||||
textArea.placeholder = getUIText("ttsPlaceholder");
|
||||
article.appendChild(textArea);
|
||||
|
||||
// Voice selection
|
||||
|
|
@ -5077,8 +5128,7 @@ export function openTranslateModal() {
|
|||
article.appendChild(header);
|
||||
|
||||
const h1 = document.createElement("h1");
|
||||
h1.innerHTML =
|
||||
"<span style=\"font-family: 'ChunkFiveRegular', monospace;\">uncloseai.</span> 🌐 Translation";
|
||||
h1.innerHTML = `<span style="font-family: 'ChunkFiveRegular', monospace;">uncloseai.</span> ${getUIText("translateModalTitle")}`;
|
||||
if (USE_CUSTOM_STYLING) {
|
||||
h1.style.cssText = `
|
||||
margin: 0;
|
||||
|
|
@ -5164,7 +5214,7 @@ export function openTranslateModal() {
|
|||
const textArea = document.createElement("textarea");
|
||||
textArea.style.width = "100%";
|
||||
textArea.style.height = "150px";
|
||||
textArea.placeholder = "Enter any text to translate...";
|
||||
textArea.placeholder = getUIText("translatePlaceholder");
|
||||
customContent.appendChild(textArea);
|
||||
|
||||
article.appendChild(pageContent);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue