diff --git a/package.json b/package.json index 443273bd..499aaa8d 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "generate:agents": "yarn workspace @cherry-studio/database agents", "generate:icons": "electron-icon-builder --input=./build/logo.png --output=build", "analyze:renderer": "VISUALIZER_RENDERER=true yarn build", - "analyze:main": "VISUALIZER_MAIN=true yarn build" + "analyze:main": "VISUALIZER_MAIN=true yarn build", + "check": "node scripts/check-i18n.js" }, "dependencies": { "@electron-toolkit/preload": "^3.0.0", diff --git a/scripts/check-i18n.js b/scripts/check-i18n.js new file mode 100644 index 00000000..411ce4d5 --- /dev/null +++ b/scripts/check-i18n.js @@ -0,0 +1,104 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +var fs = require('fs') +var path = require('path') +var translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales') +var baseLocale = 'zh-CN' +var baseFileName = ''.concat(baseLocale, '.json') +var baseFilePath = path.join(translationsDir, baseFileName) +/** + * 递归同步 target 对象,使其与 template 对象保持一致 + * 1. 如果 template 中存在 target 中缺少的 key,则添加('[to be translated]') + * 2. 如果 target 中存在 template 中不存在的 key,则删除 + * 3. 对于子对象,递归同步 + * + * @param target 目标对象(需要更新的语言对象) + * @param template 主模板对象(中文) + * @returns 返回是否对 target 进行了更新 + */ +function syncRecursively(target, template) { + var isUpdated = false + // 添加 template 中存在但 target 中缺少的 key + for (var key in template) { + if (!(key in target)) { + target[key] = + typeof template[key] === 'object' && template[key] !== null ? {} : '[to be translated]:'.concat(template[key]) + console.log('\u6DFB\u52A0\u65B0\u5C5E\u6027\uFF1A'.concat(key)) + isUpdated = true + } + if (typeof template[key] === 'object' && template[key] !== null) { + if (typeof target[key] !== 'object' || target[key] === null) { + target[key] = {} + isUpdated = true + } + // 递归同步子对象 + var childUpdated = syncRecursively(target[key], template[key]) + if (childUpdated) { + isUpdated = true + } + } + } + // 删除 target 中存在但 template 中没有的 key + for (var targetKey in target) { + if (!(targetKey in template)) { + console.log('\u79FB\u9664\u591A\u4F59\u5C5E\u6027\uFF1A'.concat(targetKey)) + delete target[targetKey] + isUpdated = true + } + } + return isUpdated +} +function syncTranslations() { + if (!fs.existsSync(baseFilePath)) { + console.error( + '\u4E3B\u6A21\u677F\u6587\u4EF6 '.concat( + baseFileName, + ' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u8DEF\u5F84\u6216\u6587\u4EF6\u540D\u3002' + ) + ) + return + } + var baseContent = fs.readFileSync(baseFilePath, 'utf-8') + var baseJson = {} + try { + baseJson = JSON.parse(baseContent) + } catch (error) { + console.error('\u89E3\u6790 '.concat(baseFileName, ' \u51FA\u9519:'), error) + return + } + var files = fs.readdirSync(translationsDir).filter(function (file) { + return file.endsWith('.json') && file !== baseFileName + }) + for (var _i = 0, files_1 = files; _i < files_1.length; _i++) { + var file = files_1[_i] + var filePath = path.join(translationsDir, file) + var targetJson = {} + try { + var fileContent = fs.readFileSync(filePath, 'utf-8') + targetJson = JSON.parse(fileContent) + } catch (error) { + console.error( + '\u89E3\u6790 '.concat( + file, + ' \u51FA\u9519\uFF0C\u8DF3\u8FC7\u6B64\u6587\u4EF6\u3002\u9519\u8BEF\u4FE1\u606F:' + ), + error + ) + continue + } + var isUpdated = syncRecursively(targetJson, baseJson) + if (isUpdated) { + try { + fs.writeFileSync(filePath, JSON.stringify(targetJson, null, 2), 'utf-8') + console.log( + '\u6587\u4EF6 '.concat(file, ' \u5DF2\u66F4\u65B0\u540C\u6B65\u4E3B\u6A21\u677F\u7684\u5185\u5BB9\u3002') + ) + } catch (error) { + console.error('\u5199\u5165 '.concat(file, ' \u51FA\u9519:'), error) + } + } else { + console.log('\u6587\u4EF6 '.concat(file, ' \u65E0\u9700\u66F4\u65B0\u3002')) + } + } +} +syncTranslations() diff --git a/scripts/check-i18n.ts b/scripts/check-i18n.ts new file mode 100644 index 00000000..a4611527 --- /dev/null +++ b/scripts/check-i18n.ts @@ -0,0 +1,98 @@ +import * as fs from 'fs' +import * as path from 'path' + +const translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales') +const baseLocale = 'zh-CN' +const baseFileName = `${baseLocale}.json` +const baseFilePath = path.join(translationsDir, baseFileName) + +/** + * 递归同步 target 对象,使其与 template 对象保持一致 + * 1. 如果 template 中存在 target 中缺少的 key,则添加('[to be translated]') + * 2. 如果 target 中存在 template 中不存在的 key,则删除 + * 3. 对于子对象,递归同步 + * + * @param target 目标对象(需要更新的语言对象) + * @param template 主模板对象(中文) + * @returns 返回是否对 target 进行了更新 + */ +function syncRecursively(target: any, template: any): boolean { + let isUpdated = false + + // 添加 template 中存在但 target 中缺少的 key + for (const key in template) { + if (!(key in target)) { + target[key] = + typeof template[key] === 'object' && template[key] !== null ? {} : `[to be translated]:${template[key]}` + console.log(`添加新属性:${key}`) + isUpdated = true + } + if (typeof template[key] === 'object' && template[key] !== null) { + if (typeof target[key] !== 'object' || target[key] === null) { + target[key] = {} + isUpdated = true + } + // 递归同步子对象 + const childUpdated = syncRecursively(target[key], template[key]) + if (childUpdated) { + isUpdated = true + } + } + } + + // 删除 target 中存在但 template 中没有的 key + for (const targetKey in target) { + if (!(targetKey in template)) { + console.log(`移除多余属性:${targetKey}`) + delete target[targetKey] + isUpdated = true + } + } + + return isUpdated +} + +function syncTranslations() { + if (!fs.existsSync(baseFilePath)) { + console.error(`主模板文件 ${baseFileName} 不存在,请检查路径或文件名。`) + return + } + + const baseContent = fs.readFileSync(baseFilePath, 'utf-8') + let baseJson: Record = {} + try { + baseJson = JSON.parse(baseContent) + } catch (error) { + console.error(`解析 ${baseFileName} 出错:`, error) + return + } + + const files = fs.readdirSync(translationsDir).filter((file) => file.endsWith('.json') && file !== baseFileName) + + for (const file of files) { + const filePath = path.join(translationsDir, file) + let targetJson: Record = {} + try { + const fileContent = fs.readFileSync(filePath, 'utf-8') + targetJson = JSON.parse(fileContent) + } catch (error) { + console.error(`解析 ${file} 出错,跳过此文件。错误信息:`, error) + continue + } + + const isUpdated = syncRecursively(targetJson, baseJson) + + if (isUpdated) { + try { + fs.writeFileSync(filePath, JSON.stringify(targetJson, null, 2), 'utf-8') + console.log(`文件 ${file} 已更新同步主模板的内容。`) + } catch (error) { + console.error(`写入 ${file} 出错:`, error) + } + } else { + console.log(`文件 ${file} 无需更新。`) + } + } +} + +syncTranslations() diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index f9ba6653..c4f3febc 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -171,7 +171,8 @@ "select": "Select", "topics": "Topics", "warning": "Warning", - "you": "You" + "you": "You", + "footnote": "Reference content" }, "error": { "backup.file_format": "Backup file format error", @@ -337,8 +338,6 @@ "error.enter.model": "Please select a model first", "error.enter.name": "Please enter the name of the knowledge base", "error.get_embedding_dimensions": "Failed to get embedding dimensions", - "error.invalid.enter.api.host": "Please enter a valid API host", - "error.invalid.enter.api.key": "Please enter a valid API key", "error.invalid.enter.model": "Please select a model", "error.invalid.proxy.url": "Invalid proxy URL", "error.invalid.webdav": "Invalid WebDAV settings", @@ -369,7 +368,9 @@ "upgrade.success.button": "Restart", "upgrade.success.content": "Please restart the application to complete the upgrade", "upgrade.success.title": "Upgrade successfully", - "warn.notion.exporting": "Notion is importing, please do not import repeatedly" + "warn.notion.exporting": "Notion is importing, please do not import repeatedly", + "error.invalid.api.host": "Invalid API Host", + "error.invalid.api.key": "Invalid API Key" }, "minapp": { "sidebar.add.title": "Add to sidebar", @@ -498,7 +499,6 @@ "zhipu": "ZHIPU AI" }, "settings": { - "": "MinApp that have been added to the sidebar do not support hiding. If you want to hide them, please remove them from the sidebar first.", "about": "About & Feedback", "about.checkingUpdate": "Checking for updates...", "about.checkUpdate": "Check Update", @@ -545,10 +545,8 @@ "webdav.backup.button": "Backup to WebDAV", "webdav.host": "WebDAV Host", "webdav.host.placeholder": "http://localhost:8080", - "webdav.hour": "Hour", "webdav.hours": "Hours", "webdav.lastSync": "Last Backup", - "webdav.minute": "Minute", "webdav.minutes": "Minutes", "webdav.noSync": "Waiting for next backup", "webdav.password": "WebDAV Password", @@ -662,17 +660,6 @@ "search_placeholder": "Search model id or name", "title": "Model Provider" }, - "provider.api.url.preview": "Preview: {{url}}", - "provider.api.url.reset": "Reset", - "provider.api.url.tip": "Ending with / ignores v1, ending with # forces use of input address", - "provider.api_host": "API Host", - "provider.api_key": "API Key", - "provider.api_key.tip": "Multiple keys separated by commas", - "provider.api_version": "API Version", - "provider.check": "Check", - "provider.docs_check": "Check", - "provider.docs_more_details": "for more details", - "provider.search_placeholder": "Search model id or name", "proxy": { "mode": { "custom": "Custom Proxy", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index f9ccb458..b3928003 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -47,7 +47,12 @@ "settings.model": "モデル設定", "settings.preset_messages": "プリセットメッセージ", "settings.prompt": "プロンプト設定", - "title": "アシスタント" + "title": "アシスタント", + "settings.reasoning_effort": "思考連鎖の長さ", + "settings.reasoning_effort.high": "長い", + "settings.reasoning_effort.low": "短い", + "settings.reasoning_effort.medium": "中程度", + "settings.reasoning_effort.tip": "この設定は推論モデルのみサポートしています" }, "auth": { "error": "APIキーの自動取得に失敗しました。手動で取得してください", @@ -166,7 +171,8 @@ "select": "選択", "topics": "トピック", "warning": "警告", - "you": "あなた" + "you": "あなた", + "footnote": "引用内容" }, "error": { "backup.file_format": "バックアップファイルの形式エラー", @@ -331,8 +337,6 @@ "error.enter.api.key": "APIキーを入力してください", "error.enter.model": "モデルを選択してください", "error.get_embedding_dimensions": "埋込み次元を取得できませんでした", - "error.invalid.enter.api.host": "APIホストを入力してください", - "error.invalid.enter.api.key": "APIキーを入力してください", "error.invalid.enter.model": "モデルを選択してください", "error.invalid.proxy.url": "無効なプロキシURL", "error.invalid.webdav": "無効なWebDAV設定", @@ -363,7 +367,10 @@ "upgrade.success.button": "再起動", "upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください", "upgrade.success.title": "アップグレードに成功しました", - "warn.notion.exporting": "Notion 正在インポート中です。重複インポートしないでください。" + "warn.notion.exporting": "Notion 正在インポート中です。重複インポートしないでください。", + "error.enter.name": "ナレッジベース名を入力してください", + "error.invalid.api.host": "無効なAPIアドレスです", + "error.invalid.api.key": "無効なAPIキーです" }, "minapp": { "sidebar.add.title": "サイドバーに追加", @@ -488,7 +495,8 @@ "together": "Together", "yi": "零一万物", "zhinao": "360智脳", - "zhipu": "智譜AI" + "zhipu": "智譜AI", + "ppio": "PPIO パイオウクラウド" }, "settings": { "about": "について", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 0a6a8815..1ba7c1c7 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -47,7 +47,12 @@ "settings.model": "Настройки модели", "settings.preset_messages": "Предустановленные сообщения", "settings.prompt": "Настройки промптов", - "title": "Ассистенты" + "title": "Ассистенты", + "settings.reasoning_effort": "Длина цепочки рассуждений", + "settings.reasoning_effort.high": "Длинная", + "settings.reasoning_effort.low": "Короткая", + "settings.reasoning_effort.medium": "Средняя", + "settings.reasoning_effort.tip": "Эта настройка поддерживается только моделями с рассуждением" }, "auth": { "error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную", @@ -166,7 +171,8 @@ "select": "Выбрать", "topics": "Топики", "warning": "Предупреждение", - "you": "Вы" + "you": "Вы", + "footnote": "Цитируемый контент" }, "error": { "backup.file_format": "Ошибка формата файла резервной копии", @@ -332,8 +338,6 @@ "error.enter.model": "Пожалуйста, выберите модель", "error.enter.name": "Пожалуйста, введите название базы знаний", "error.get_embedding_dimensions": "Не удалось получить размерность встраивания", - "error.invalid.enter.api.host": "Пожалуйста, введите правильный API хост", - "error.invalid.enter.api.key": "Пожалуйста, введите правильный API ключ", "error.invalid.enter.model": "Пожалуйста, выберите модель", "error.invalid.proxy.url": "Неверный URL прокси", "error.invalid.webdav": "Неверные настройки WebDAV", @@ -364,7 +368,9 @@ "upgrade.success.button": "Перезапустить", "upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления", "upgrade.success.title": "Обновление успешно", - "warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт" + "warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт", + "error.invalid.api.host": "Неверный API адрес", + "error.invalid.api.key": "Неверный API ключ" }, "minapp": { "sidebar.add.title": "Добавить в боковую панель", @@ -489,7 +495,8 @@ "together": "Together", "yi": "Yi", "zhinao": "360AI", - "zhipu": "ZHIPU AI" + "zhipu": "ZHIPU AI", + "ppio": "PPIO" }, "settings": { "about": "О программе и обратная связь", @@ -590,7 +597,6 @@ "input.target_language.chinese-traditional": "Китайский традиционный", "input.target_language.english": "Английский", "input.target_language.japanese": "Японский", - "input.target_language.russianinput.translate": "Русский", "messages.divider": "Показывать разделитель между сообщениями", "messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл", "messages.input.paste_long_text_threshold": "Длина вставки длинного текста", @@ -653,17 +659,6 @@ "search_placeholder": "Поиск по ID или имени модели", "title": "Провайдеры моделей" }, - "provider.api.url.preview": "Предпросмотр: {{url}}", - "provider.api.url.reset": "Сброс", - "provider.api.url.tip": "Заканчивая на / игнорирует v1, заканчивая на # принудительно использует введенный адрес", - "provider.api_host": "Хост API", - "provider.api_key": "Ключ API", - "provider.api_key.tip": "Несколько ключей, разделенных запятыми", - "provider.api_version": "Версия API", - "provider.check": "Проверить", - "provider.docs_check": "Проверить", - "provider.docs_more_details": "для получения дополнительной информации", - "provider.search_placeholder": "Поиск по ID или имени модели", "proxy": { "mode": { "custom": "Пользовательский прокси", @@ -715,7 +710,8 @@ "topic.position.left": "Слева", "topic.position.right": "Справа", "topic.show.time": "Показывать время топика", - "tray.title": "Включить значок системного трея" + "tray.title": "Включить значок системного трея", + "input.target_language.russian": "Русский" }, "translate": { "any.language": "Любой язык", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index a5fb56e6..d5881414 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -171,7 +171,8 @@ "select": "選擇", "topics": "話題", "warning": "警告", - "you": "您" + "you": "您", + "footnote": "引用內容" }, "error": { "backup.file_format": "備份文件格式錯誤", @@ -337,8 +338,6 @@ "error.enter.model": "請先選擇一個模型", "error.enter.name": "請先輸入知識庫名稱", "error.get_embedding_dimensions": "獲取嵌入維度失敗", - "error.invalid.enter.api.host": "請輸入有效的 API 主機地址", - "error.invalid.enter.api.key": "請輸入有效的 API 密鑰", "error.invalid.enter.model": "請選擇一個模型", "error.invalid.proxy.url": "無效的代理 URL", "error.invalid.webdav": "無效的 WebDAV 設定", @@ -369,7 +368,9 @@ "upgrade.success.button": "重新啟動", "upgrade.success.content": "請重新啟動應用以完成升級", "upgrade.success.title": "升級成功", - "warn.notion.exporting": "Notion 正在匯入,請勿重複匯入" + "warn.notion.exporting": "Notion 正在匯入,請勿重複匯入", + "error.invalid.api.host": "無效的 API 位址", + "error.invalid.api.key": "無效的 API 密鑰" }, "minapp": { "sidebar.add.title": "添加到側邊欄", @@ -532,8 +533,6 @@ "success": "緩存清除成功", "title": "清除緩存" }, - "data.app_data": "應用數據", - "data.app_logs": "應用日誌", "data.title": "數據目錄", "notion.api_key": "Notion 金鑰", "notion.database_id": "Notion 資料庫 ID", @@ -557,7 +556,9 @@ "webdav.syncError": "備份錯誤", "webdav.syncStatus": "備份狀態", "webdav.title": "WebDAV", - "webdav.user": "WebDAV 使用者名稱" + "webdav.user": "WebDAV 使用者名稱", + "app_data": "應用數據", + "app_logs": "應用日誌" }, "display.custom.css": "自定義 CSS", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", @@ -604,7 +605,6 @@ "messages.input.show_estimated_tokens": "顯示預估 Token 數", "messages.input.title": "輸入設定", "messages.math_engine": "Markdown 渲染輸入訊息", - "messages.math_render_engine": "數學公式引擎", "messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens", "messages.model.title": "模型設定", "messages.title": "訊息設定", @@ -710,7 +710,8 @@ "topic.position.left": "左側", "topic.position.right": "右側", "topic.show.time": "顯示話題時間", - "tray.title": "啟用系統托盤圖標" + "tray.title": "啟用系統托盤圖標", + "messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息" }, "translate": { "any.language": "任意語言",