diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 75116cc..8c43028 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,9 +1,11 @@ { "extName": { "message": "Easy Translator" }, "extDesc": { "message": "Translate web pages, PDF, PPT, DOCX files with custom LLM backends" }, + "tabTranslate": { "message": "Translate" }, "tabSettings": { "message": "Settings" }, "tabAbout": { "message": "About" }, + "translatePage": { "message": "Translate Entire Page" }, "translatePageDesc": { "message": "Replace all page text with translations" }, "translateFile": { "message": "Translate Current File" }, @@ -13,41 +15,235 @@ "autoDetect": { "message": "Auto Detect" }, "translateBtn": { "message": "Translate" }, "swapLang": { "message": "Swap languages" }, + + "modelConfigTitle": { "message": "Model Configuration" }, "providerLabel": { "message": "API Provider" }, + "providerOpenAI": { "message": "OpenAI-compatible (OpenAI / DeepSeek / Qwen / Zhipu, etc.)" }, + "providerOllama": { "message": "Ollama (local models)" }, + "ollamaHintPre": { "message": "Start Ollama with the environment variable" }, + "ollamaHintPost": { "message": "set, otherwise requests are blocked by CORS" }, "apiUrlLabel": { "message": "API URL" }, "apiKeyLabel": { "message": "API Key" }, + "showHide": { "message": "Show/Hide" }, "modelLabel": { "message": "Model Name" }, + "refreshModels": { "message": "Refresh Models" }, "styleLabel": { "message": "Translation Style" }, "styleNatural": { "message": "Natural & Fluent" }, "styleFormal": { "message": "Formal & Professional" }, + + "customPromptLabel": { "message": "Custom Prompt" }, + "promptDefaultOption": { "message": "Default (generated from translation style)" }, + "promptPlaceholder": { "message": "You are a professional translator..." }, + "promptDefaultPlaceholder": { "message": "Uses the default prompt (generated from \"Translation Style\")" }, + "promptSaveNew": { "message": "Save as new template" }, + "promptUpdate": { "message": "Update current template" }, + "promptDelete": { "message": "Delete" }, + "promptVarsLabel": { "message": "Variables:" }, + "promptVarSrc": { "message": "source language" }, + "promptVarTgt": { "message": "target language" }, + "promptBatchNote": { "message": "The [#N] marker instruction is appended automatically for batch translation" }, + "promptNamePrompt": { "message": "Template name:" }, + "promptDefaultName": { + "message": "Custom $N$", + "placeholders": { "n": { "content": "$1" } } + }, + "promptConfirmDelete": { "message": "Delete this template?" }, + "statusPromptEmpty": { "message": "Enter prompt content first" }, + "statusPromptSaved": { "message": "✓ New template saved" }, + "statusPromptUpdated": { "message": "✓ Template updated" }, + "statusPromptDeleted": { "message": "✓ Template deleted" }, + "pageTranslateLabel": { "message": "Page Translation" }, - "shortcutHint": { "message": "Select text and press Alt+T for quick translation" }, + "shortcutLabel": { "message": "Shortcut" }, + "shortcutHint": { "message": "Select text and press" }, + "shortcutHint2": { "message": "for quick translation" }, "displayModeLabel": { "message": "Display Mode" }, "displayModeTooltip": { "message": "Floating Tooltip" }, "displayModeInline": { "message": "Replace Inline" }, "displayModeSidebar": { "message": "Sidebar" }, "testBtn": { "message": "Test Connection" }, "saveBtn": { "message": "Save Settings" }, - "aboutTitle": { "message": "Universal Translator v1.0.0" }, + + "aboutTitle": { "message": "Easy Translator v1.0.0" }, "aboutDesc": { "message": "Translate web pages, PDF, PPT, DOCX and more." }, "aboutModels": { "message": "Supports multiple LLM backends:" }, + "aboutModelOpenAI": { "message": "OpenAI GPT family" }, + "aboutModelAnthropic": { "message": "Anthropic Claude family" }, + "aboutModelGemini": { "message": "Google Gemini family" }, + "aboutModelOthers": { "message": "DeepSeek, Qwen, Zhipu and others" }, "aboutUsage": { "message": "How to use:" }, "aboutUsage1": { "message": "Select text → Right-click → Translate" }, "aboutUsage2": { "message": "Select text → Press Alt+T" }, "aboutUsage3": { "message": "Click extension icon to translate input text" }, "aboutPDF": { "message": "PDF: Opens automatically in the translation viewer" }, - "modelConfigTitle": { "message": "Model Configuration" }, - "showHide": { "message": "Show/Hide" }, - "refreshModels": { "message": "Refresh Models" }, - "shortcutLabel": { "message": "Shortcut" }, + + "statusFetchingModels": { "message": "Fetching model list..." }, + "statusModelsUpdated": { "message": "Model list updated" }, + "statusModelsDefault": { "message": "Loaded the default model list" }, + "statusNeedApiKey": { "message": "Please enter an API Key" }, + "statusTesting": { "message": "Testing connection..." }, + "statusTestOk": { + "message": "✓ Connected! Test translation: $RESULT$", + "placeholders": { "result": { "content": "$1" } } + }, + "statusTestFail": { + "message": "✗ Connection failed: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "statusSettingsSaved": { "message": "✓ Settings saved" }, + "statusInjectFailed": { "message": "Could not inject the translation script" }, + "statusNotAFile": { "message": "The current page is not a PDF or Office file" }, + "btnTranslating": { "message": "Translating..." }, + "btnTesting": { "message": "Testing..." }, + "contextMenuSelection": { "message": "Translate Selected Text" }, "contextMenuPage": { "message": "Translate Entire Page" }, + + "confirmReload": { "message": "The translator extension has been updated. Reload the page to continue?" }, + "floatBtnTranslate": { "message": "Translate" }, + "tooltipTitle": { "message": "Translation" }, + "loading": { "message": "Translating..." }, + "actionCopy": { "message": "Copy" }, + "actionSwitch": { "message": "Switch language" }, + "translateFailed": { + "message": "Translation failed: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "sidebarOriginal": { "message": "Original" }, + "sidebarTranslation": { "message": "Translation" }, + "alertNeedApiKey": { "message": "Please configure an API Key first (click the extension icon → Settings)" }, + + "bannerPreparing": { "message": "Preparing..." }, + "bannerScanning": { "message": "Scanning page text..." }, + "bannerNoText": { "message": "No translatable text found" }, + "bannerCacheLoaded": { "message": "Cached translation loaded" }, + "bannerCacheLoadedCount": { + "message": "Cached translation loaded ($COUNT$ segments)", + "placeholders": { "count": { "content": "$1" } } + }, + "bannerProgressStart": { + "message": "Translating... 0/$TOTAL$ (cached $CACHED$, pending $TOTAL2$)", + "placeholders": { + "total": { "content": "$1" }, + "cached": { "content": "$2" }, + "total2": { "content": "$3" } + } + }, + "bannerProgress": { + "message": "Translating... $DONE$/$TOTAL$ ($CHUNK$/$CHUNKS$ blocks)", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" }, + "chunk": { "content": "$3" }, + "chunks": { "content": "$4" } + } + }, + "bannerDone": { + "message": "Translation complete! $DONE$/$TOTAL$ segments", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "bannerStopped": { "message": "Translation stopped" }, + "bannerReverted": { "message": "Original text restored" }, + "bannerShowingTranslation": { "message": "Showing translation" }, + "titleClose": { "message": "Close" }, + "btnStop": { "message": "Stop" }, + "titleStop": { "message": "Stop translation" }, + "btnShowTranslation": { "message": "Show translation" }, + "btnShowOriginal": { "message": "Original" }, + + "pdfBtnTranslate": { "message": "Translate PDF" }, + "pdfBtnTranslateThis": { "message": "Translate this PDF" }, + "pdfNoText": { "message": "Could not extract text from this PDF. It may be a scan or an encrypted document." }, + "pdfFailedMark": { "message": "[translation failed]" }, + "pdfPanelTitle": { "message": "PDF Translation" }, + "pdfPreparing": { "message": "Preparing translation..." }, + "pdfProgress": { + "message": "Progress: $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "pdfDone": { "message": "✅ Translation complete" }, + "viewerTitle": { "message": "PDF Translator" }, "viewerTranslate": { "message": "Translate" }, - "viewerToggleOrig": { "message": "Show Original" }, - "viewerToggleTrans": { "message": "Show Translation" }, - "viewerSourceLang": { "message": "Source" }, - "viewerTargetLang": { "message": "Target" }, - "viewerPageOf": { "message": "of" }, - "viewerPages": { "message": "pages" } + "viewerToggleOrig": { "message": "Original" }, + "viewerToggleTrans": { "message": "Translation" }, + "viewerMissingParam": { "message": "Missing file parameter" }, + "viewerNoPdfData": { "message": "Could not load PDF data" }, + "viewerParseFailed": { + "message": "Failed to parse PDF: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "viewerRendering": { + "message": "Rendering... $PAGE$/$TOTAL$", + "placeholders": { + "page": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTranslatingProgress": { + "message": "Translating... $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTransFailed": { "message": "(translation failed)" }, + "viewerLoadingFile": { "message": "Loading file..." }, + "viewerNoFileData": { "message": "Could not load file data" }, + "viewerEmptyFile": { "message": "The file is empty" }, + "viewerSegments": { + "message": "$COUNT$ segments", + "placeholders": { "count": { "content": "$1" } } + }, + "viewerPagesCount": { + "message": "$COUNT$ pages", + "placeholders": { "count": { "content": "$1" } } + }, + + "promptSrcAuto": { "message": "source text" }, + "promptBatch": { + "message": "You are a professional translator. Translate the following $SRC$ into $TGT$. Every paragraph in the text starts with a \"[#number]\" marker. Translate each paragraph one by one and keep the \"[#number]\" markers and the paragraph order exactly as they are. Do not merge or skip any paragraph. Return only the translation, without any extra content.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptFormal": { + "message": "You are a professional translator. Translate the following $SRC$ into $TGT$. Use a formal, professional tone. Return only the translation, without any extra content.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptNatural": { + "message": "You are a professional translator. Translate the following $SRC$ into $TGT$. Use natural, fluent everyday language. Return only the translation, without any extra content.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptBatchSuffix": { "message": "Every paragraph in the text starts with a \"[#number]\" marker. Translate each paragraph one by one and keep the \"[#number]\" markers and the paragraph order exactly as they are. Do not merge or skip any paragraph." }, + + "errNoApiKey": { "message": "Please configure an API Key first" }, + "errNoText": { "message": "No text to translate" }, + "errApi": { + "message": "API error ($STATUS$): $BODY$", + "placeholders": { + "status": { "content": "$1" }, + "body": { "content": "$2" } + } + }, + "errOllamaConnect": { "message": "Cannot reach Ollama — make sure it is running (ollama serve)" }, + "errOllamaCors": { "message": "Ollama rejected the extension request (CORS). Start Ollama with the environment variable OLLAMA_ORIGINS=* set" }, + "errPdfNoText": { "message": "Could not extract text from the PDF, or the PDF is a scan" }, + "errPdfFailed": { + "message": "PDF processing failed: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + } } diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 8b562f5..a47a543 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -1,9 +1,11 @@ { "extName": { "message": "Easy Translator" }, "extDesc": { "message": "ウェブページ、PDF、PPT、DOCXファイルをカスタムLLMで翻訳" }, + "tabTranslate": { "message": "翻訳" }, "tabSettings": { "message": "設定" }, "tabAbout": { "message": "概要" }, + "translatePage": { "message": "ページ全体を翻訳" }, "translatePageDesc": { "message": "ページ上のすべてのテキストを翻訳に置き換え" }, "translateFile": { "message": "現在のファイルを翻訳" }, @@ -13,41 +15,235 @@ "autoDetect": { "message": "自動検出" }, "translateBtn": { "message": "翻訳" }, "swapLang": { "message": "言語を入れ替え" }, + + "modelConfigTitle": { "message": "モデル設定" }, "providerLabel": { "message": "API プロバイダ" }, + "providerOpenAI": { "message": "OpenAI 互換 (OpenAI / DeepSeek / Qwen / Zhipu など)" }, + "providerOllama": { "message": "Ollama (ローカルモデル)" }, + "ollamaHintPre": { "message": "環境変数" }, + "ollamaHintPost": { "message": "を設定してから Ollama を起動してください。設定しないと CORS でブロックされます" }, "apiUrlLabel": { "message": "API URL" }, "apiKeyLabel": { "message": "API キー" }, + "showHide": { "message": "表示/非表示" }, "modelLabel": { "message": "モデル名" }, + "refreshModels": { "message": "モデル更新" }, "styleLabel": { "message": "翻訳スタイル" }, "styleNatural": { "message": "自然で流暢" }, "styleFormal": { "message": "フォーマル・専門的" }, + + "customPromptLabel": { "message": "カスタムプロンプト" }, + "promptDefaultOption": { "message": "デフォルト (翻訳スタイルから自動生成)" }, + "promptPlaceholder": { "message": "あなたはプロの翻訳者です..." }, + "promptDefaultPlaceholder": { "message": "デフォルトのプロンプトを使用(「翻訳スタイル」から自動生成)" }, + "promptSaveNew": { "message": "新しいテンプレートとして保存" }, + "promptUpdate": { "message": "現在のテンプレートを更新" }, + "promptDelete": { "message": "削除" }, + "promptVarsLabel": { "message": "変数:" }, + "promptVarSrc": { "message": "翻訳元の言語" }, + "promptVarTgt": { "message": "翻訳先の言語" }, + "promptBatchNote": { "message": "一括翻訳時には [#番号] マーカーの説明が自動的に追加されます" }, + "promptNamePrompt": { "message": "テンプレート名:" }, + "promptDefaultName": { + "message": "カスタム $N$", + "placeholders": { "n": { "content": "$1" } } + }, + "promptConfirmDelete": { "message": "このテンプレートを削除しますか?" }, + "statusPromptEmpty": { "message": "先にプロンプトの内容を入力してください" }, + "statusPromptSaved": { "message": "✓ 新しいテンプレートを保存しました" }, + "statusPromptUpdated": { "message": "✓ テンプレートを更新しました" }, + "statusPromptDeleted": { "message": "✓ テンプレートを削除しました" }, + "pageTranslateLabel": { "message": "ページ翻訳" }, - "shortcutHint": { "message": "テキストを選択して Alt+T でクイック翻訳" }, + "shortcutLabel": { "message": "ショートカット" }, + "shortcutHint": { "message": "テキストを選択して" }, + "shortcutHint2": { "message": "でクイック翻訳" }, "displayModeLabel": { "message": "表示モード" }, "displayModeTooltip": { "message": "フローティングツールチップ" }, "displayModeInline": { "message": "インライン置換" }, "displayModeSidebar": { "message": "サイドバー" }, "testBtn": { "message": "接続テスト" }, "saveBtn": { "message": "設定を保存" }, - "aboutTitle": { "message": "万能翻訳機 v1.0.0" }, + + "aboutTitle": { "message": "Easy Translator v1.0.0" }, "aboutDesc": { "message": "ウェブページ、PDF、PPT、DOCXなどを翻訳します。" }, "aboutModels": { "message": "複数のLLMバックエンドに対応:" }, + "aboutModelOpenAI": { "message": "OpenAI GPT シリーズ" }, + "aboutModelAnthropic": { "message": "Anthropic Claude シリーズ" }, + "aboutModelGemini": { "message": "Google Gemini シリーズ" }, + "aboutModelOthers": { "message": "DeepSeek、Qwen、Zhipu など" }, "aboutUsage": { "message": "使い方:" }, "aboutUsage1": { "message": "テキストを選択 → 右クリック → 翻訳" }, "aboutUsage2": { "message": "テキストを選択 → Alt+T を押す" }, "aboutUsage3": { "message": "拡張機能アイコンをクリックしてテキストを入力" }, "aboutPDF": { "message": "PDF:自動的に翻訳ビューアーで開きます" }, - "modelConfigTitle": { "message": "モデル設定" }, - "showHide": { "message": "表示/非表示" }, - "refreshModels": { "message": "モデル更新" }, - "shortcutLabel": { "message": "ショートカット" }, + + "statusFetchingModels": { "message": "モデル一覧を取得中..." }, + "statusModelsUpdated": { "message": "モデル一覧を更新しました" }, + "statusModelsDefault": { "message": "デフォルトのモデル一覧を読み込みました" }, + "statusNeedApiKey": { "message": "API キーを入力してください" }, + "statusTesting": { "message": "接続をテスト中..." }, + "statusTestOk": { + "message": "✓ 接続成功!テスト翻訳: $RESULT$", + "placeholders": { "result": { "content": "$1" } } + }, + "statusTestFail": { + "message": "✗ 接続失敗: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "statusSettingsSaved": { "message": "✓ 設定を保存しました" }, + "statusInjectFailed": { "message": "翻訳スクリプトを挿入できません" }, + "statusNotAFile": { "message": "現在のページは PDF または Office ファイルではありません" }, + "btnTranslating": { "message": "翻訳中..." }, + "btnTesting": { "message": "テスト中..." }, + "contextMenuSelection": { "message": "選択テキストを翻訳" }, "contextMenuPage": { "message": "ページ全体を翻訳" }, + + "confirmReload": { "message": "翻訳拡張機能が更新されました。続けて使うにはページを再読み込みする必要があります。再読み込みしますか?" }, + "floatBtnTranslate": { "message": "翻訳" }, + "tooltipTitle": { "message": "翻訳結果" }, + "loading": { "message": "翻訳中..." }, + "actionCopy": { "message": "コピー" }, + "actionSwitch": { "message": "言語を切り替え" }, + "translateFailed": { + "message": "翻訳に失敗しました: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "sidebarOriginal": { "message": "原文" }, + "sidebarTranslation": { "message": "訳文" }, + "alertNeedApiKey": { "message": "先に API キーを設定してください(拡張機能アイコン → 設定)" }, + + "bannerPreparing": { "message": "準備中..." }, + "bannerScanning": { "message": "ページのテキストをスキャン中..." }, + "bannerNoText": { "message": "翻訳できるテキストが見つかりません" }, + "bannerCacheLoaded": { "message": "キャッシュされた翻訳を読み込みました" }, + "bannerCacheLoadedCount": { + "message": "キャッシュされた翻訳を読み込みました ($COUNT$ 段落)", + "placeholders": { "count": { "content": "$1" } } + }, + "bannerProgressStart": { + "message": "翻訳中... 0/$TOTAL$ (キャッシュ $CACHED$, 未翻訳 $TOTAL2$)", + "placeholders": { + "total": { "content": "$1" }, + "cached": { "content": "$2" }, + "total2": { "content": "$3" } + } + }, + "bannerProgress": { + "message": "翻訳中... $DONE$/$TOTAL$ ($CHUNK$/$CHUNKS$ ブロック)", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" }, + "chunk": { "content": "$3" }, + "chunks": { "content": "$4" } + } + }, + "bannerDone": { + "message": "翻訳完了!$DONE$/$TOTAL$ 段落", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "bannerStopped": { "message": "翻訳を停止しました" }, + "bannerReverted": { "message": "原文に戻しました" }, + "bannerShowingTranslation": { "message": "訳文を表示中" }, + "titleClose": { "message": "閉じる" }, + "btnStop": { "message": "停止" }, + "titleStop": { "message": "翻訳を停止" }, + "btnShowTranslation": { "message": "訳文を表示" }, + "btnShowOriginal": { "message": "原文" }, + + "pdfBtnTranslate": { "message": "PDFを翻訳" }, + "pdfBtnTranslateThis": { "message": "このPDFを翻訳" }, + "pdfNoText": { "message": "この PDF からテキストを抽出できません。スキャン文書または暗号化された文書の可能性があります。" }, + "pdfFailedMark": { "message": "[翻訳失敗]" }, + "pdfPanelTitle": { "message": "PDF 翻訳" }, + "pdfPreparing": { "message": "翻訳の準備中..." }, + "pdfProgress": { + "message": "翻訳の進捗: $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "pdfDone": { "message": "✅ 翻訳完了" }, + "viewerTitle": { "message": "PDF 翻訳" }, "viewerTranslate": { "message": "翻訳" }, - "viewerToggleOrig": { "message": "原文表示" }, - "viewerToggleTrans": { "message": "訳文表示" }, - "viewerSourceLang": { "message": "翻訳元" }, - "viewerTargetLang": { "message": "翻訳先" }, - "viewerPageOf": { "message": "全" }, - "viewerPages": { "message": "ページ" } + "viewerToggleOrig": { "message": "原文" }, + "viewerToggleTrans": { "message": "訳文" }, + "viewerMissingParam": { "message": "ファイルパラメータがありません" }, + "viewerNoPdfData": { "message": "PDF データを読み込めません" }, + "viewerParseFailed": { + "message": "PDF の解析に失敗しました: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "viewerRendering": { + "message": "レンダリング中... $PAGE$/$TOTAL$ ページ", + "placeholders": { + "page": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTranslatingProgress": { + "message": "翻訳中... $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTransFailed": { "message": "(翻訳失敗)" }, + "viewerLoadingFile": { "message": "ファイルを読み込み中..." }, + "viewerNoFileData": { "message": "ファイルデータを読み込めません" }, + "viewerEmptyFile": { "message": "ファイルの内容が空です" }, + "viewerSegments": { + "message": "全 $COUNT$ 段落", + "placeholders": { "count": { "content": "$1" } } + }, + "viewerPagesCount": { + "message": "全 $COUNT$ ページ", + "placeholders": { "count": { "content": "$1" } } + }, + + "promptSrcAuto": { "message": "原文" }, + "promptBatch": { + "message": "あなたはプロの翻訳者です。以下の$SRC$を$TGT$に翻訳してください。テキストの各段落は「[#番号]」マーカーで始まります。各段落を一つずつ翻訳し、「[#番号]」マーカーと段落の順序は厳密にそのまま保ってください。段落を結合したり省略したりしないでください。翻訳結果のみを返し、余計な内容は含めないでください。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptFormal": { + "message": "あなたはプロの翻訳者です。以下の$SRC$を$TGT$に翻訳してください。フォーマルで専門的な文体を使用してください。翻訳結果のみを返し、余計な内容は含めないでください。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptNatural": { + "message": "あなたはプロの翻訳者です。以下の$SRC$を$TGT$に翻訳してください。自然で流暢な口語表現を使用してください。翻訳結果のみを返し、余計な内容は含めないでください。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptBatchSuffix": { "message": "テキストの各段落は「[#番号]」マーカーで始まります。各段落を一つずつ翻訳し、「[#番号]」マーカーと段落の順序は厳密にそのまま保ってください。段落を結合したり省略したりしないでください。" }, + + "errNoApiKey": { "message": "先に API キーを設定してください" }, + "errNoText": { "message": "翻訳するテキストがありません" }, + "errApi": { + "message": "API エラー ($STATUS$): $BODY$", + "placeholders": { + "status": { "content": "$1" }, + "body": { "content": "$2" } + } + }, + "errOllamaConnect": { "message": "Ollama に接続できません。Ollama が起動しているか確認してください(ollama serve)" }, + "errOllamaCors": { "message": "Ollama が拡張機能のリクエストを拒否しました(CORS)。Ollama の起動時に環境変数 OLLAMA_ORIGINS=* を設定してください" }, + "errPdfNoText": { "message": "PDF からテキストを抽出できません。またはスキャン文書です" }, + "errPdfFailed": { + "message": "PDF の処理に失敗しました: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + } } diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index 87ec839..11c2f18 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -1,9 +1,11 @@ { "extName": { "message": "Easy Translator" }, "extDesc": { "message": "웹페이지, PDF, PPT, DOCX 파일을 커스텀 LLM으로 번역" }, + "tabTranslate": { "message": "번역" }, "tabSettings": { "message": "설정" }, "tabAbout": { "message": "정보" }, + "translatePage": { "message": "전체 페이지 번역" }, "translatePageDesc": { "message": "페이지의 모든 텍스트를 번역문으로 교체" }, "translateFile": { "message": "현재 파일 번역" }, @@ -13,41 +15,235 @@ "autoDetect": { "message": "자동 감지" }, "translateBtn": { "message": "번역" }, "swapLang": { "message": "언어 교환" }, + + "modelConfigTitle": { "message": "모델 설정" }, "providerLabel": { "message": "API 제공자" }, + "providerOpenAI": { "message": "OpenAI 호환 (OpenAI / DeepSeek / Qwen / Zhipu 등)" }, + "providerOllama": { "message": "Ollama (로컬 모델)" }, + "ollamaHintPre": { "message": "환경 변수" }, + "ollamaHintPost": { "message": "를 설정한 뒤 Ollama를 실행하세요. 그렇지 않으면 CORS로 차단됩니다" }, "apiUrlLabel": { "message": "API URL" }, "apiKeyLabel": { "message": "API 키" }, + "showHide": { "message": "표시/숨기기" }, "modelLabel": { "message": "모델 이름" }, + "refreshModels": { "message": "모델 새로고침" }, "styleLabel": { "message": "번역 스타일" }, "styleNatural": { "message": "자연스럽고 유창하게" }, "styleFormal": { "message": "격식 있고 전문적으로" }, + + "customPromptLabel": { "message": "사용자 지정 프롬프트" }, + "promptDefaultOption": { "message": "기본값 (번역 스타일에서 자동 생성)" }, + "promptPlaceholder": { "message": "당신은 전문 번역가입니다..." }, + "promptDefaultPlaceholder": { "message": "기본 프롬프트 사용(\"번역 스타일\"에서 자동 생성)" }, + "promptSaveNew": { "message": "새 템플릿으로 저장" }, + "promptUpdate": { "message": "현재 템플릿 업데이트" }, + "promptDelete": { "message": "삭제" }, + "promptVarsLabel": { "message": "변수:" }, + "promptVarSrc": { "message": "원본 언어" }, + "promptVarTgt": { "message": "대상 언어" }, + "promptBatchNote": { "message": "일괄 번역 시 [#번호] 마커 설명이 자동으로 추가됩니다" }, + "promptNamePrompt": { "message": "템플릿 이름:" }, + "promptDefaultName": { + "message": "사용자 지정 $N$", + "placeholders": { "n": { "content": "$1" } } + }, + "promptConfirmDelete": { "message": "이 템플릿을 삭제할까요?" }, + "statusPromptEmpty": { "message": "먼저 프롬프트 내용을 입력하세요" }, + "statusPromptSaved": { "message": "✓ 새 템플릿을 저장했습니다" }, + "statusPromptUpdated": { "message": "✓ 템플릿을 업데이트했습니다" }, + "statusPromptDeleted": { "message": "✓ 템플릿을 삭제했습니다" }, + "pageTranslateLabel": { "message": "페이지 번역" }, - "shortcutHint": { "message": "텍스트 선택 후 Alt+T로 빠른 번역" }, + "shortcutLabel": { "message": "단축키" }, + "shortcutHint": { "message": "텍스트 선택 후" }, + "shortcutHint2": { "message": "로 빠른 번역" }, "displayModeLabel": { "message": "표시 모드" }, "displayModeTooltip": { "message": "플로팅 툴팁" }, "displayModeInline": { "message": "인라인 교체" }, "displayModeSidebar": { "message": "사이드바" }, "testBtn": { "message": "연결 테스트" }, "saveBtn": { "message": "설정 저장" }, - "aboutTitle": { "message": "만능 번역기 v1.0.0" }, + + "aboutTitle": { "message": "Easy Translator v1.0.0" }, "aboutDesc": { "message": "웹페이지, PDF, PPT, DOCX 등을 번역합니다." }, "aboutModels": { "message": "다양한 LLM 백엔드 지원:" }, + "aboutModelOpenAI": { "message": "OpenAI GPT 계열" }, + "aboutModelAnthropic": { "message": "Anthropic Claude 계열" }, + "aboutModelGemini": { "message": "Google Gemini 계열" }, + "aboutModelOthers": { "message": "DeepSeek, Qwen, Zhipu 등" }, "aboutUsage": { "message": "사용 방법:" }, "aboutUsage1": { "message": "텍스트 선택 → 우클릭 → 번역" }, "aboutUsage2": { "message": "텍스트 선택 → Alt+T 누르기" }, "aboutUsage3": { "message": "확장 프로그램 아이콘 클릭하여 텍스트 입력" }, "aboutPDF": { "message": "PDF: 번역 뷰어에서 자동으로 열립니다" }, - "modelConfigTitle": { "message": "모델 설정" }, - "showHide": { "message": "표시/숨기기" }, - "refreshModels": { "message": "모델 새로고침" }, - "shortcutLabel": { "message": "단축키" }, + + "statusFetchingModels": { "message": "모델 목록을 가져오는 중..." }, + "statusModelsUpdated": { "message": "모델 목록을 업데이트했습니다" }, + "statusModelsDefault": { "message": "기본 모델 목록을 불러왔습니다" }, + "statusNeedApiKey": { "message": "API 키를 입력하세요" }, + "statusTesting": { "message": "연결을 테스트하는 중..." }, + "statusTestOk": { + "message": "✓ 연결 성공! 테스트 번역: $RESULT$", + "placeholders": { "result": { "content": "$1" } } + }, + "statusTestFail": { + "message": "✗ 연결 실패: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "statusSettingsSaved": { "message": "✓ 설정을 저장했습니다" }, + "statusInjectFailed": { "message": "번역 스크립트를 주입할 수 없습니다" }, + "statusNotAFile": { "message": "현재 페이지는 PDF 또는 Office 파일이 아닙니다" }, + "btnTranslating": { "message": "번역 중..." }, + "btnTesting": { "message": "테스트 중..." }, + "contextMenuSelection": { "message": "선택한 텍스트 번역" }, "contextMenuPage": { "message": "전체 페이지 번역" }, + + "confirmReload": { "message": "번역 확장 프로그램이 업데이트되었습니다. 계속 사용하려면 페이지를 새로고침해야 합니다. 새로고침할까요?" }, + "floatBtnTranslate": { "message": "번역" }, + "tooltipTitle": { "message": "번역 결과" }, + "loading": { "message": "번역 중..." }, + "actionCopy": { "message": "복사" }, + "actionSwitch": { "message": "언어 전환" }, + "translateFailed": { + "message": "번역 실패: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "sidebarOriginal": { "message": "원문" }, + "sidebarTranslation": { "message": "번역문" }, + "alertNeedApiKey": { "message": "먼저 API 키를 설정하세요 (확장 프로그램 아이콘 → 설정)" }, + + "bannerPreparing": { "message": "준비 중..." }, + "bannerScanning": { "message": "페이지 텍스트를 스캔하는 중..." }, + "bannerNoText": { "message": "번역할 텍스트를 찾을 수 없습니다" }, + "bannerCacheLoaded": { "message": "캐시된 번역을 불러왔습니다" }, + "bannerCacheLoadedCount": { + "message": "캐시된 번역을 불러왔습니다 ($COUNT$개 단락)", + "placeholders": { "count": { "content": "$1" } } + }, + "bannerProgressStart": { + "message": "번역 중... 0/$TOTAL$ (캐시 $CACHED$, 번역 필요 $TOTAL2$)", + "placeholders": { + "total": { "content": "$1" }, + "cached": { "content": "$2" }, + "total2": { "content": "$3" } + } + }, + "bannerProgress": { + "message": "번역 중... $DONE$/$TOTAL$ ($CHUNK$/$CHUNKS$ 블록)", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" }, + "chunk": { "content": "$3" }, + "chunks": { "content": "$4" } + } + }, + "bannerDone": { + "message": "번역 완료! $DONE$/$TOTAL$개 단락", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "bannerStopped": { "message": "번역을 중지했습니다" }, + "bannerReverted": { "message": "원문으로 되돌렸습니다" }, + "bannerShowingTranslation": { "message": "번역문 표시 중" }, + "titleClose": { "message": "닫기" }, + "btnStop": { "message": "중지" }, + "titleStop": { "message": "번역 중지" }, + "btnShowTranslation": { "message": "번역문 보기" }, + "btnShowOriginal": { "message": "원문" }, + + "pdfBtnTranslate": { "message": "PDF 번역" }, + "pdfBtnTranslateThis": { "message": "이 PDF 번역" }, + "pdfNoText": { "message": "이 PDF에서 텍스트를 추출할 수 없습니다. 스캔본이거나 암호화된 문서일 수 있습니다." }, + "pdfFailedMark": { "message": "[번역 실패]" }, + "pdfPanelTitle": { "message": "PDF 번역" }, + "pdfPreparing": { "message": "번역 준비 중..." }, + "pdfProgress": { + "message": "번역 진행률: $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "pdfDone": { "message": "✅ 번역 완료" }, + "viewerTitle": { "message": "PDF 번역" }, "viewerTranslate": { "message": "번역" }, - "viewerToggleOrig": { "message": "원문 보기" }, - "viewerToggleTrans": { "message": "번역문 보기" }, - "viewerSourceLang": { "message": "소스" }, - "viewerTargetLang": { "message": "타겟" }, - "viewerPageOf": { "message": "전체" }, - "viewerPages": { "message": "페이지" } + "viewerToggleOrig": { "message": "원문" }, + "viewerToggleTrans": { "message": "번역문" }, + "viewerMissingParam": { "message": "파일 매개변수가 없습니다" }, + "viewerNoPdfData": { "message": "PDF 데이터를 불러올 수 없습니다" }, + "viewerParseFailed": { + "message": "PDF 분석 실패: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "viewerRendering": { + "message": "렌더링 중... $PAGE$/$TOTAL$ 페이지", + "placeholders": { + "page": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTranslatingProgress": { + "message": "번역 중... $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTransFailed": { "message": "(번역 실패)" }, + "viewerLoadingFile": { "message": "파일을 불러오는 중..." }, + "viewerNoFileData": { "message": "파일 데이터를 불러올 수 없습니다" }, + "viewerEmptyFile": { "message": "파일 내용이 비어 있습니다" }, + "viewerSegments": { + "message": "총 $COUNT$개 단락", + "placeholders": { "count": { "content": "$1" } } + }, + "viewerPagesCount": { + "message": "총 $COUNT$페이지", + "placeholders": { "count": { "content": "$1" } } + }, + + "promptSrcAuto": { "message": "원문" }, + "promptBatch": { + "message": "당신은 전문 번역가입니다. 다음 $SRC$을(를) $TGT$(으)로 번역하세요. 텍스트의 각 단락은 \"[#번호]\" 마커로 시작합니다. 각 단락을 하나씩 번역하고 \"[#번호]\" 마커와 단락 순서를 그대로 유지하세요. 단락을 합치거나 건너뛰지 마세요. 번역 결과만 반환하고 추가 내용은 포함하지 마세요.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptFormal": { + "message": "당신은 전문 번역가입니다. 다음 $SRC$을(를) $TGT$(으)로 번역하세요. 격식 있고 전문적인 어조를 사용하세요. 번역 결과만 반환하고 추가 내용은 포함하지 마세요.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptNatural": { + "message": "당신은 전문 번역가입니다. 다음 $SRC$을(를) $TGT$(으)로 번역하세요. 자연스럽고 유창한 구어체를 사용하세요. 번역 결과만 반환하고 추가 내용은 포함하지 마세요.", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptBatchSuffix": { "message": "텍스트의 각 단락은 \"[#번호]\" 마커로 시작합니다. 각 단락을 하나씩 번역하고 \"[#번호]\" 마커와 단락 순서를 그대로 유지하세요. 단락을 합치거나 건너뛰지 마세요." }, + + "errNoApiKey": { "message": "먼저 API 키를 설정하세요" }, + "errNoText": { "message": "번역할 텍스트가 없습니다" }, + "errApi": { + "message": "API 오류 ($STATUS$): $BODY$", + "placeholders": { + "status": { "content": "$1" }, + "body": { "content": "$2" } + } + }, + "errOllamaConnect": { "message": "Ollama에 연결할 수 없습니다. Ollama가 실행 중인지 확인하세요 (ollama serve)" }, + "errOllamaCors": { "message": "Ollama가 확장 프로그램 요청을 거부했습니다 (CORS). Ollama 실행 시 환경 변수 OLLAMA_ORIGINS=* 를 설정하세요" }, + "errPdfNoText": { "message": "PDF에서 텍스트를 추출할 수 없거나 스캔 문서입니다" }, + "errPdfFailed": { + "message": "PDF 처리 실패: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + } } diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 0c8674b..bddd6f7 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -1,9 +1,11 @@ { "extName": { "message": "Easy Translator" }, "extDesc": { "message": "翻译网页、PDF、PPT、DOCX文件,支持自定义接入大模型" }, + "tabTranslate": { "message": "翻译" }, "tabSettings": { "message": "设置" }, "tabAbout": { "message": "关于" }, + "translatePage": { "message": "翻译整个页面" }, "translatePageDesc": { "message": "替换当前页面所有文字为译文" }, "translateFile": { "message": "翻译当前文件" }, @@ -13,41 +15,235 @@ "autoDetect": { "message": "自动检测" }, "translateBtn": { "message": "翻译" }, "swapLang": { "message": "交换语言" }, + + "modelConfigTitle": { "message": "模型配置" }, "providerLabel": { "message": "API 提供商" }, + "providerOpenAI": { "message": "OpenAI 兼容 (OpenAI / DeepSeek / 通义千问 / 智谱等)" }, + "providerOllama": { "message": "Ollama (本地模型)" }, + "ollamaHintPre": { "message": "需要设置环境变量" }, + "ollamaHintPost": { "message": "后启动 Ollama,否则会被 CORS 阻止" }, "apiUrlLabel": { "message": "API 地址" }, "apiKeyLabel": { "message": "API Key" }, + "showHide": { "message": "显示/隐藏" }, "modelLabel": { "message": "模型名称" }, + "refreshModels": { "message": "刷新可用模型" }, "styleLabel": { "message": "翻译风格" }, "styleNatural": { "message": "自然流畅" }, "styleFormal": { "message": "正式专业" }, + + "customPromptLabel": { "message": "自定义 Prompt" }, + "promptDefaultOption": { "message": "默认 (根据翻译风格自动生成)" }, + "promptPlaceholder": { "message": "你是一个专业翻译..." }, + "promptDefaultPlaceholder": { "message": "使用默认 prompt(根据“翻译风格”自动生成)" }, + "promptSaveNew": { "message": "保存为新模板" }, + "promptUpdate": { "message": "更新当前模板" }, + "promptDelete": { "message": "删除" }, + "promptVarsLabel": { "message": "变量:" }, + "promptVarSrc": { "message": "源语言" }, + "promptVarTgt": { "message": "目标语言" }, + "promptBatchNote": { "message": "系统会自动在批量翻译时加入 [#数字] 标记说明" }, + "promptNamePrompt": { "message": "模板名称:" }, + "promptDefaultName": { + "message": "自定义 $N$", + "placeholders": { "n": { "content": "$1" } } + }, + "promptConfirmDelete": { "message": "确定删除此模板?" }, + "statusPromptEmpty": { "message": "请先输入 prompt 内容" }, + "statusPromptSaved": { "message": "✓ 已保存新模板" }, + "statusPromptUpdated": { "message": "✓ 已更新模板" }, + "statusPromptDeleted": { "message": "✓ 已删除模板" }, + "pageTranslateLabel": { "message": "页面翻译" }, - "shortcutHint": { "message": "选中文本后按 Alt+T 快速翻译" }, + "shortcutLabel": { "message": "快捷键" }, + "shortcutHint": { "message": "选中文本后按" }, + "shortcutHint2": { "message": "快速翻译" }, "displayModeLabel": { "message": "显示模式" }, "displayModeTooltip": { "message": "浮动提示框" }, "displayModeInline": { "message": "替换原文" }, "displayModeSidebar": { "message": "侧边栏" }, "testBtn": { "message": "测试连接" }, "saveBtn": { "message": "保存设置" }, + "aboutTitle": { "message": "Easy Translator v1.0.0" }, "aboutDesc": { "message": "支持翻译网页、PDF、PPT、DOCX等文件内容。" }, "aboutModels": { "message": "可接入多种大模型:" }, + "aboutModelOpenAI": { "message": "OpenAI GPT 系列" }, + "aboutModelAnthropic": { "message": "Anthropic Claude 系列" }, + "aboutModelGemini": { "message": "Google Gemini 系列" }, + "aboutModelOthers": { "message": "DeepSeek、通义千问、智谱等" }, "aboutUsage": { "message": "使用方式:" }, "aboutUsage1": { "message": "选中文本 → 右键 → 翻译选中文本" }, "aboutUsage2": { "message": "选中文本 → 按 Alt+T" }, "aboutUsage3": { "message": "点击图标打开弹窗输入文本" }, "aboutPDF": { "message": "PDF 支持:打开 PDF 文件后自动加载翻译工具" }, - "modelConfigTitle": { "message": "模型配置" }, - "showHide": { "message": "显示/隐藏" }, - "refreshModels": { "message": "刷新可用模型" }, - "shortcutLabel": { "message": "快捷键" }, + + "statusFetchingModels": { "message": "正在获取模型列表..." }, + "statusModelsUpdated": { "message": "模型列表已更新" }, + "statusModelsDefault": { "message": "已加载默认模型列表" }, + "statusNeedApiKey": { "message": "请填写 API Key" }, + "statusTesting": { "message": "正在测试连接..." }, + "statusTestOk": { + "message": "✓ 连接成功!测试翻译: $RESULT$", + "placeholders": { "result": { "content": "$1" } } + }, + "statusTestFail": { + "message": "✗ 连接失败: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "statusSettingsSaved": { "message": "✓ 设置已保存" }, + "statusInjectFailed": { "message": "无法注入翻译脚本" }, + "statusNotAFile": { "message": "当前页面不是 PDF 或 Office 文件" }, + "btnTranslating": { "message": "翻译中..." }, + "btnTesting": { "message": "测试中..." }, + "contextMenuSelection": { "message": "翻译选中文本" }, "contextMenuPage": { "message": "翻译整个页面" }, + + "confirmReload": { "message": "翻译插件已更新,需要刷新页面才能继续使用。是否刷新?" }, + "floatBtnTranslate": { "message": "翻译" }, + "tooltipTitle": { "message": "翻译结果" }, + "loading": { "message": "翻译中..." }, + "actionCopy": { "message": "复制" }, + "actionSwitch": { "message": "切换语言" }, + "translateFailed": { + "message": "翻译失败: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "sidebarOriginal": { "message": "原文" }, + "sidebarTranslation": { "message": "译文" }, + "alertNeedApiKey": { "message": "请先配置 API Key!(点击扩展图标 → 设置)" }, + + "bannerPreparing": { "message": "准备中..." }, + "bannerScanning": { "message": "正在扫描页面文本..." }, + "bannerNoText": { "message": "未找到可翻译的文本" }, + "bannerCacheLoaded": { "message": "已加载缓存翻译" }, + "bannerCacheLoadedCount": { + "message": "已加载缓存翻译 ($COUNT$ 段)", + "placeholders": { "count": { "content": "$1" } } + }, + "bannerProgressStart": { + "message": "正在翻译... 0/$TOTAL$ (缓存命中 $CACHED$, 需翻译 $TOTAL2$)", + "placeholders": { + "total": { "content": "$1" }, + "cached": { "content": "$2" }, + "total2": { "content": "$3" } + } + }, + "bannerProgress": { + "message": "正在翻译... $DONE$/$TOTAL$ ($CHUNK$/$CHUNKS$ 块)", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" }, + "chunk": { "content": "$3" }, + "chunks": { "content": "$4" } + } + }, + "bannerDone": { + "message": "翻译完成!$DONE$/$TOTAL$ 段文本", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "bannerStopped": { "message": "翻译已停止" }, + "bannerReverted": { "message": "已恢复原文" }, + "bannerShowingTranslation": { "message": "显示译文" }, + "titleClose": { "message": "关闭" }, + "btnStop": { "message": "停止" }, + "titleStop": { "message": "停止翻译" }, + "btnShowTranslation": { "message": "显示译文" }, + "btnShowOriginal": { "message": "原文" }, + + "pdfBtnTranslate": { "message": "翻译PDF" }, + "pdfBtnTranslateThis": { "message": "翻译此PDF" }, + "pdfNoText": { "message": "无法从 PDF 中提取文本。可能是扫描件或加密文档。" }, + "pdfFailedMark": { "message": "[翻译失败]" }, + "pdfPanelTitle": { "message": "PDF 翻译" }, + "pdfPreparing": { "message": "准备翻译..." }, + "pdfProgress": { + "message": "翻译进度: $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "pdfDone": { "message": "✅ 翻译完成" }, + "viewerTitle": { "message": "PDF 翻译" }, "viewerTranslate": { "message": "翻译" }, - "viewerToggleOrig": { "message": "显示原文" }, - "viewerToggleTrans": { "message": "显示译文" }, - "viewerSourceLang": { "message": "源语言" }, - "viewerTargetLang": { "message": "目标语言" }, - "viewerPageOf": { "message": "共" }, - "viewerPages": { "message": "页" } + "viewerToggleOrig": { "message": "原文" }, + "viewerToggleTrans": { "message": "译文" }, + "viewerMissingParam": { "message": "缺少文件参数" }, + "viewerNoPdfData": { "message": "无法加载 PDF 数据" }, + "viewerParseFailed": { + "message": "PDF 解析失败: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + }, + "viewerRendering": { + "message": "正在渲染... $PAGE$/$TOTAL$ 页", + "placeholders": { + "page": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTranslatingProgress": { + "message": "翻译中... $DONE$/$TOTAL$", + "placeholders": { + "done": { "content": "$1" }, + "total": { "content": "$2" } + } + }, + "viewerTransFailed": { "message": "(翻译失败)" }, + "viewerLoadingFile": { "message": "正在加载文件..." }, + "viewerNoFileData": { "message": "无法加载文件数据" }, + "viewerEmptyFile": { "message": "文件内容为空" }, + "viewerSegments": { + "message": "共 $COUNT$ 段", + "placeholders": { "count": { "content": "$1" } } + }, + "viewerPagesCount": { + "message": "共 $COUNT$ 页", + "placeholders": { "count": { "content": "$1" } } + }, + + "promptSrcAuto": { "message": "原文" }, + "promptBatch": { + "message": "你是一个专业翻译。请将以下$SRC$翻译为$TGT$。文本中的每个段落以“[#数字]”标记开头。请逐一翻译每个段落,严格保持“[#数字]”标记和段落顺序不变。不要合并或跳过任何段落。只返回翻译结果,不要任何额外内容。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptFormal": { + "message": "你是一个专业翻译。请将以下$SRC$翻译为$TGT$。使用正式、专业的语气。只返回翻译结果,不要任何额外内容。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptNatural": { + "message": "你是一个专业翻译。请将以下$SRC$翻译为$TGT$。使用自然流畅的口语。只返回翻译结果,不要任何额外内容。", + "placeholders": { + "src": { "content": "$1" }, + "tgt": { "content": "$2" } + } + }, + "promptBatchSuffix": { "message": "文本中的每个段落以“[#数字]”标记开头。请逐一翻译每个段落,严格保持“[#数字]”标记和段落顺序不变。不要合并或跳过任何段落。" }, + + "errNoApiKey": { "message": "请先配置 API Key" }, + "errNoText": { "message": "没有待翻译的文本" }, + "errApi": { + "message": "API 错误 ($STATUS$): $BODY$", + "placeholders": { + "status": { "content": "$1" }, + "body": { "content": "$2" } + } + }, + "errOllamaConnect": { "message": "无法连接 Ollama,请确认 Ollama 已启动(ollama serve)" }, + "errOllamaCors": { "message": "Ollama 拒绝了扩展请求(CORS),请在启动 Ollama 时设置环境变量:OLLAMA_ORIGINS=*" }, + "errPdfNoText": { "message": "无法从PDF中提取文本,或PDF为扫描件" }, + "errPdfFailed": { + "message": "PDF处理失败: $ERROR$", + "placeholders": { "error": { "content": "$1" } } + } } diff --git a/background.js b/background.js index 2d61b1c..ee5ec74 100644 --- a/background.js +++ b/background.js @@ -185,12 +185,12 @@ async function handlePDFTranslation(tabId, fileUrl) { // Simple text extraction from PDF (handles text-based PDFs) const text = await extractTextFromPDF(arrayBuffer); - if (!text.trim()) throw new Error('无法从PDF中提取文本,或PDF为扫描件'); + if (!text.trim()) throw new Error(t('errPdfNoText')); const settings = await new Promise(resolve => chrome.storage.sync.get(null, resolve)); return await translateText(text.slice(0, 10000), settings); } catch (err) { - throw new Error(`PDF处理失败: ${err.message}`); + throw new Error(t('errPdfFailed', err.message)); } } diff --git a/content/content.js b/content/content.js index f227a94..13d9b2d 100644 --- a/content/content.js +++ b/content/content.js @@ -5,6 +5,16 @@ // 跳过 PDF 页面(由 pdf-content.js 处理) if (isPDFPage()) return; + // i18n — content scripts can call chrome.i18n directly; falls back to the key + // so a missing message stays visible instead of rendering as an empty label. + function t(key, ...subs) { + if (typeof chrome === 'undefined' || !chrome.i18n) return key; + const msg = subs.length + ? chrome.i18n.getMessage(key, subs.map(String)) + : chrome.i18n.getMessage(key); + return msg || key; + } + // ─── 状态 ────────────────────────────────────────── let floatBtn = null; let tooltip = null; @@ -48,7 +58,7 @@ console.warn('Extension reloaded detected, reloading page to re-inject content script'); try { // Try to reconnect by triggering a page reload - if (confirm('翻译插件已更新,需要刷新页面才能继续使用。是否刷新?')) { + if (confirm(t('confirmReload'))) { window.location.reload(); } } catch {} @@ -137,7 +147,7 @@ if (!floatBtn) { floatBtn = document.createElement('button'); floatBtn.className = 'translate-float-btn'; - floatBtn.innerHTML = '🌐 翻译'; + floatBtn.textContent = '🌐 ' + t('floatBtnTranslate'); floatBtn.addEventListener('click', (e) => { e.stopPropagation(); showTranslation(selectedText); @@ -178,13 +188,13 @@ tooltip.className = 'translate-tooltip'; tooltip.innerHTML = `
- 变量: {src} 源语言, {tgt} 目标语言
- 系统会自动在批量翻译时加入 [#数字] 标记说明
+ 变量: {src} 源语言, {tgt} 目标语言
+ 系统会自动在批量翻译时加入 [#数字] 标记说明
支持翻译网页、PDF、PPT、DOCX等文件内容。
可接入多种大模型:
使用方式: