feat: add i18n sync script (#1538)

* feat: add i18n sync script

* chore
This commit is contained in:
Chen Tao 2025-02-13 11:34:23 +08:00 committed by GitHub
parent e99f253d48
commit 8739c49634
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 248 additions and 53 deletions

View File

@ -44,7 +44,8 @@
"generate:agents": "yarn workspace @cherry-studio/database agents", "generate:agents": "yarn workspace @cherry-studio/database agents",
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build", "generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
"analyze:renderer": "VISUALIZER_RENDERER=true yarn 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": { "dependencies": {
"@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/preload": "^3.0.0",

104
scripts/check-i18n.js Normal file
View File

@ -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()

98
scripts/check-i18n.ts Normal file
View File

@ -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<string, any> = {}
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<string, any> = {}
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()

View File

@ -171,7 +171,8 @@
"select": "Select", "select": "Select",
"topics": "Topics", "topics": "Topics",
"warning": "Warning", "warning": "Warning",
"you": "You" "you": "You",
"footnote": "Reference content"
}, },
"error": { "error": {
"backup.file_format": "Backup file format error", "backup.file_format": "Backup file format error",
@ -337,8 +338,6 @@
"error.enter.model": "Please select a model first", "error.enter.model": "Please select a model first",
"error.enter.name": "Please enter the name of the knowledge base", "error.enter.name": "Please enter the name of the knowledge base",
"error.get_embedding_dimensions": "Failed to get embedding dimensions", "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.enter.model": "Please select a model",
"error.invalid.proxy.url": "Invalid proxy URL", "error.invalid.proxy.url": "Invalid proxy URL",
"error.invalid.webdav": "Invalid WebDAV settings", "error.invalid.webdav": "Invalid WebDAV settings",
@ -369,7 +368,9 @@
"upgrade.success.button": "Restart", "upgrade.success.button": "Restart",
"upgrade.success.content": "Please restart the application to complete the upgrade", "upgrade.success.content": "Please restart the application to complete the upgrade",
"upgrade.success.title": "Upgrade successfully", "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": { "minapp": {
"sidebar.add.title": "Add to sidebar", "sidebar.add.title": "Add to sidebar",
@ -498,7 +499,6 @@
"zhipu": "ZHIPU AI" "zhipu": "ZHIPU AI"
}, },
"settings": { "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": "About & Feedback",
"about.checkingUpdate": "Checking for updates...", "about.checkingUpdate": "Checking for updates...",
"about.checkUpdate": "Check Update", "about.checkUpdate": "Check Update",
@ -545,10 +545,8 @@
"webdav.backup.button": "Backup to WebDAV", "webdav.backup.button": "Backup to WebDAV",
"webdav.host": "WebDAV Host", "webdav.host": "WebDAV Host",
"webdav.host.placeholder": "http://localhost:8080", "webdav.host.placeholder": "http://localhost:8080",
"webdav.hour": "Hour",
"webdav.hours": "Hours", "webdav.hours": "Hours",
"webdav.lastSync": "Last Backup", "webdav.lastSync": "Last Backup",
"webdav.minute": "Minute",
"webdav.minutes": "Minutes", "webdav.minutes": "Minutes",
"webdav.noSync": "Waiting for next backup", "webdav.noSync": "Waiting for next backup",
"webdav.password": "WebDAV Password", "webdav.password": "WebDAV Password",
@ -662,17 +660,6 @@
"search_placeholder": "Search model id or name", "search_placeholder": "Search model id or name",
"title": "Model Provider" "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": { "proxy": {
"mode": { "mode": {
"custom": "Custom Proxy", "custom": "Custom Proxy",

View File

@ -47,7 +47,12 @@
"settings.model": "モデル設定", "settings.model": "モデル設定",
"settings.preset_messages": "プリセットメッセージ", "settings.preset_messages": "プリセットメッセージ",
"settings.prompt": "プロンプト設定", "settings.prompt": "プロンプト設定",
"title": "アシスタント" "title": "アシスタント",
"settings.reasoning_effort": "思考連鎖の長さ",
"settings.reasoning_effort.high": "長い",
"settings.reasoning_effort.low": "短い",
"settings.reasoning_effort.medium": "中程度",
"settings.reasoning_effort.tip": "この設定は推論モデルのみサポートしています"
}, },
"auth": { "auth": {
"error": "APIキーの自動取得に失敗しました。手動で取得してください", "error": "APIキーの自動取得に失敗しました。手動で取得してください",
@ -166,7 +171,8 @@
"select": "選択", "select": "選択",
"topics": "トピック", "topics": "トピック",
"warning": "警告", "warning": "警告",
"you": "あなた" "you": "あなた",
"footnote": "引用内容"
}, },
"error": { "error": {
"backup.file_format": "バックアップファイルの形式エラー", "backup.file_format": "バックアップファイルの形式エラー",
@ -331,8 +337,6 @@
"error.enter.api.key": "APIキーを入力してください", "error.enter.api.key": "APIキーを入力してください",
"error.enter.model": "モデルを選択してください", "error.enter.model": "モデルを選択してください",
"error.get_embedding_dimensions": "埋込み次元を取得できませんでした", "error.get_embedding_dimensions": "埋込み次元を取得できませんでした",
"error.invalid.enter.api.host": "APIホストを入力してください",
"error.invalid.enter.api.key": "APIキーを入力してください",
"error.invalid.enter.model": "モデルを選択してください", "error.invalid.enter.model": "モデルを選択してください",
"error.invalid.proxy.url": "無効なプロキシURL", "error.invalid.proxy.url": "無効なプロキシURL",
"error.invalid.webdav": "無効なWebDAV設定", "error.invalid.webdav": "無効なWebDAV設定",
@ -363,7 +367,10 @@
"upgrade.success.button": "再起動", "upgrade.success.button": "再起動",
"upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください", "upgrade.success.content": "アップグレードを完了するためにアプリケーションを再起動してください",
"upgrade.success.title": "アップグレードに成功しました", "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": { "minapp": {
"sidebar.add.title": "サイドバーに追加", "sidebar.add.title": "サイドバーに追加",
@ -488,7 +495,8 @@
"together": "Together", "together": "Together",
"yi": "零一万物", "yi": "零一万物",
"zhinao": "360智脳", "zhinao": "360智脳",
"zhipu": "智譜AI" "zhipu": "智譜AI",
"ppio": "PPIO パイオウクラウド"
}, },
"settings": { "settings": {
"about": "について", "about": "について",

View File

@ -47,7 +47,12 @@
"settings.model": "Настройки модели", "settings.model": "Настройки модели",
"settings.preset_messages": "Предустановленные сообщения", "settings.preset_messages": "Предустановленные сообщения",
"settings.prompt": "Настройки промптов", "settings.prompt": "Настройки промптов",
"title": "Ассистенты" "title": "Ассистенты",
"settings.reasoning_effort": "Длина цепочки рассуждений",
"settings.reasoning_effort.high": "Длинная",
"settings.reasoning_effort.low": "Короткая",
"settings.reasoning_effort.medium": "Средняя",
"settings.reasoning_effort.tip": "Эта настройка поддерживается только моделями с рассуждением"
}, },
"auth": { "auth": {
"error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную", "error": "Автоматический получение ключа API не удалось, пожалуйста, получите ключ вручную",
@ -166,7 +171,8 @@
"select": "Выбрать", "select": "Выбрать",
"topics": "Топики", "topics": "Топики",
"warning": "Предупреждение", "warning": "Предупреждение",
"you": "Вы" "you": "Вы",
"footnote": "Цитируемый контент"
}, },
"error": { "error": {
"backup.file_format": "Ошибка формата файла резервной копии", "backup.file_format": "Ошибка формата файла резервной копии",
@ -332,8 +338,6 @@
"error.enter.model": "Пожалуйста, выберите модель", "error.enter.model": "Пожалуйста, выберите модель",
"error.enter.name": "Пожалуйста, введите название базы знаний", "error.enter.name": "Пожалуйста, введите название базы знаний",
"error.get_embedding_dimensions": "Не удалось получить размерность встраивания", "error.get_embedding_dimensions": "Не удалось получить размерность встраивания",
"error.invalid.enter.api.host": "Пожалуйста, введите правильный API хост",
"error.invalid.enter.api.key": "Пожалуйста, введите правильный API ключ",
"error.invalid.enter.model": "Пожалуйста, выберите модель", "error.invalid.enter.model": "Пожалуйста, выберите модель",
"error.invalid.proxy.url": "Неверный URL прокси", "error.invalid.proxy.url": "Неверный URL прокси",
"error.invalid.webdav": "Неверные настройки WebDAV", "error.invalid.webdav": "Неверные настройки WebDAV",
@ -364,7 +368,9 @@
"upgrade.success.button": "Перезапустить", "upgrade.success.button": "Перезапустить",
"upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления", "upgrade.success.content": "Пожалуйста, перезапустите приложение для завершения обновления",
"upgrade.success.title": "Обновление успешно", "upgrade.success.title": "Обновление успешно",
"warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт" "warn.notion.exporting": "Идет импорт в Notion, пожалуйста, не повторяйте импорт",
"error.invalid.api.host": "Неверный API адрес",
"error.invalid.api.key": "Неверный API ключ"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "Добавить в боковую панель", "sidebar.add.title": "Добавить в боковую панель",
@ -489,7 +495,8 @@
"together": "Together", "together": "Together",
"yi": "Yi", "yi": "Yi",
"zhinao": "360AI", "zhinao": "360AI",
"zhipu": "ZHIPU AI" "zhipu": "ZHIPU AI",
"ppio": "PPIO"
}, },
"settings": { "settings": {
"about": "О программе и обратная связь", "about": "О программе и обратная связь",
@ -590,7 +597,6 @@
"input.target_language.chinese-traditional": "Китайский традиционный", "input.target_language.chinese-traditional": "Китайский традиционный",
"input.target_language.english": "Английский", "input.target_language.english": "Английский",
"input.target_language.japanese": "Японский", "input.target_language.japanese": "Японский",
"input.target_language.russianinput.translate": "Русский",
"messages.divider": "Показывать разделитель между сообщениями", "messages.divider": "Показывать разделитель между сообщениями",
"messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл", "messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл",
"messages.input.paste_long_text_threshold": "Длина вставки длинного текста", "messages.input.paste_long_text_threshold": "Длина вставки длинного текста",
@ -653,17 +659,6 @@
"search_placeholder": "Поиск по ID или имени модели", "search_placeholder": "Поиск по ID или имени модели",
"title": "Провайдеры моделей" "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": { "proxy": {
"mode": { "mode": {
"custom": "Пользовательский прокси", "custom": "Пользовательский прокси",
@ -715,7 +710,8 @@
"topic.position.left": "Слева", "topic.position.left": "Слева",
"topic.position.right": "Справа", "topic.position.right": "Справа",
"topic.show.time": "Показывать время топика", "topic.show.time": "Показывать время топика",
"tray.title": "Включить значок системного трея" "tray.title": "Включить значок системного трея",
"input.target_language.russian": "Русский"
}, },
"translate": { "translate": {
"any.language": "Любой язык", "any.language": "Любой язык",

View File

@ -171,7 +171,8 @@
"select": "選擇", "select": "選擇",
"topics": "話題", "topics": "話題",
"warning": "警告", "warning": "警告",
"you": "您" "you": "您",
"footnote": "引用內容"
}, },
"error": { "error": {
"backup.file_format": "備份文件格式錯誤", "backup.file_format": "備份文件格式錯誤",
@ -337,8 +338,6 @@
"error.enter.model": "請先選擇一個模型", "error.enter.model": "請先選擇一個模型",
"error.enter.name": "請先輸入知識庫名稱", "error.enter.name": "請先輸入知識庫名稱",
"error.get_embedding_dimensions": "獲取嵌入維度失敗", "error.get_embedding_dimensions": "獲取嵌入維度失敗",
"error.invalid.enter.api.host": "請輸入有效的 API 主機地址",
"error.invalid.enter.api.key": "請輸入有效的 API 密鑰",
"error.invalid.enter.model": "請選擇一個模型", "error.invalid.enter.model": "請選擇一個模型",
"error.invalid.proxy.url": "無效的代理 URL", "error.invalid.proxy.url": "無效的代理 URL",
"error.invalid.webdav": "無效的 WebDAV 設定", "error.invalid.webdav": "無效的 WebDAV 設定",
@ -369,7 +368,9 @@
"upgrade.success.button": "重新啟動", "upgrade.success.button": "重新啟動",
"upgrade.success.content": "請重新啟動應用以完成升級", "upgrade.success.content": "請重新啟動應用以完成升級",
"upgrade.success.title": "升級成功", "upgrade.success.title": "升級成功",
"warn.notion.exporting": "Notion 正在匯入,請勿重複匯入" "warn.notion.exporting": "Notion 正在匯入,請勿重複匯入",
"error.invalid.api.host": "無效的 API 位址",
"error.invalid.api.key": "無效的 API 密鑰"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "添加到側邊欄", "sidebar.add.title": "添加到側邊欄",
@ -532,8 +533,6 @@
"success": "緩存清除成功", "success": "緩存清除成功",
"title": "清除緩存" "title": "清除緩存"
}, },
"data.app_data": "應用數據",
"data.app_logs": "應用日誌",
"data.title": "數據目錄", "data.title": "數據目錄",
"notion.api_key": "Notion 金鑰", "notion.api_key": "Notion 金鑰",
"notion.database_id": "Notion 資料庫 ID", "notion.database_id": "Notion 資料庫 ID",
@ -557,7 +556,9 @@
"webdav.syncError": "備份錯誤", "webdav.syncError": "備份錯誤",
"webdav.syncStatus": "備份狀態", "webdav.syncStatus": "備份狀態",
"webdav.title": "WebDAV", "webdav.title": "WebDAV",
"webdav.user": "WebDAV 使用者名稱" "webdav.user": "WebDAV 使用者名稱",
"app_data": "應用數據",
"app_logs": "應用日誌"
}, },
"display.custom.css": "自定義 CSS", "display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
@ -604,7 +605,6 @@
"messages.input.show_estimated_tokens": "顯示預估 Token 數", "messages.input.show_estimated_tokens": "顯示預估 Token 數",
"messages.input.title": "輸入設定", "messages.input.title": "輸入設定",
"messages.math_engine": "Markdown 渲染輸入訊息", "messages.math_engine": "Markdown 渲染輸入訊息",
"messages.math_render_engine": "數學公式引擎",
"messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens", "messages.metrics": "首字時延 {{time_first_token_millsec}}ms | 每秒 {{token_speed}} tokens",
"messages.model.title": "模型設定", "messages.model.title": "模型設定",
"messages.title": "訊息設定", "messages.title": "訊息設定",
@ -710,7 +710,8 @@
"topic.position.left": "左側", "topic.position.left": "左側",
"topic.position.right": "右側", "topic.position.right": "右側",
"topic.show.time": "顯示話題時間", "topic.show.time": "顯示話題時間",
"tray.title": "啟用系統托盤圖標" "tray.title": "啟用系統托盤圖標",
"messages.markdown_rendering_input_message": "Markdown 渲染輸入訊息"
}, },
"translate": { "translate": {
"any.language": "任意語言", "any.language": "任意語言",