From 16ac419b9b1561fa4b1185c03f16e62df6abbc7b Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 22 Mar 2025 22:28:02 +0800 Subject: [PATCH] fix: i18n and lint --- scripts/update-i18n.ts | 166 +++++++++--------- src/main/services/ShortcutService.ts | 2 +- src/main/services/WindowService.ts | 2 +- src/renderer/src/context/AntdProvider.tsx | 13 +- src/renderer/src/i18n/index.ts | 3 +- src/renderer/src/i18n/locales/zh-cn.json | 4 +- src/renderer/src/i18n/locales/zh-tw.json | 4 +- .../pages/home/Messages/MessageMenubar.tsx | 8 +- .../src/pages/home/Messages/Messages.tsx | 2 +- .../src/pages/home/Tabs/SettingsTab.tsx | 2 +- .../src/pages/knowledge/KnowledgeContent.tsx | 2 +- .../settings/DataSettings/WebDavSettings.tsx | 2 +- .../src/pages/settings/GeneralSettings.tsx | 8 +- src/renderer/src/store/settings.ts | 1 - src/renderer/src/types/index.ts | 11 +- 15 files changed, 129 insertions(+), 101 deletions(-) diff --git a/scripts/update-i18n.ts b/scripts/update-i18n.ts index 01d14252..ac05dce2 100644 --- a/scripts/update-i18n.ts +++ b/scripts/update-i18n.ts @@ -1,50 +1,51 @@ - /** * OCOOL_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts */ - // OCOOL API KEY -const OCOOL_API_KEY = process.env.OCOOL_API_KEY; +const OCOOL_API_KEY = process.env.OCOOL_API_KEY const INDEX = [ -// 语言的名称 代码 用来翻译的模型 - {name: "France", code: "fr-fr", model: "qwen2.5-32b-instruct"}, - {name: "Spanish", code: "es-es", model: "qwen2.5-32b-instruct"}, - {name: "Portuguese", code: "pt-pt", model: "qwen2.5-72b-instruct"}, - {name: "Greek", code: "el-gr", model: "qwen-turbo" }, + // 语言的名称 代码 用来翻译的模型 + { name: 'France', code: 'fr-fr', model: 'qwen2.5-32b-instruct' }, + { name: 'Spanish', code: 'es-es', model: 'qwen2.5-32b-instruct' }, + { name: 'Portuguese', code: 'pt-pt', model: 'qwen2.5-72b-instruct' }, + { name: 'Greek', code: 'el-gr', model: 'qwen-turbo' } ] -const fs = require("fs"); -import OpenAI from "openai"; +const fs = require('fs') +import OpenAI from 'openai' -const zh = JSON.parse(fs.readFileSync("src/renderer/src/i18n/locales/zh-cn.json", 'utf8')) as object +const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as object const openai = new OpenAI({ - apiKey: OCOOL_API_KEY, - baseURL: "https://one.ocoolai.com/v1" -}); + apiKey: OCOOL_API_KEY, + baseURL: 'https://one.ocoolai.com/v1' +}) // 递归遍历翻译 -async function translate(zh:object, obj:object, target: string, model:string, updateFile){ - const texts: {[key:string]:string} = {} - for(let e in zh){ - if(typeof zh[e]=="object"){ // 遍历下一层 - if(!obj[e] || typeof obj[e]!="object") obj[e] = {} - await translate(zh[e], obj[e], target, model, updateFile) - }else{ - // 加入到本层待翻译列表 - if(!obj[e] || typeof obj[e]!="string"){ - texts[e] = zh[e] - } - } +async function translate(zh: object, obj: object, target: string, model: string, updateFile) { + const texts: { [key: string]: string } = {} + for (const e in zh) { + if (typeof zh[e] == 'object') { + // 遍历下一层 + if (!obj[e] || typeof obj[e] != 'object') obj[e] = {} + await translate(zh[e], obj[e], target, model, updateFile) + } else { + // 加入到本层待翻译列表 + if (!obj[e] || typeof obj[e] != 'string') { + texts[e] = zh[e] + } } - if(Object.keys(texts).length>0){ - const completion = await openai.chat.completions.create({ - model: model, - response_format: {type:"json_object"}, - messages: [ - {role:"user", content:` + } + if (Object.keys(texts).length > 0) { + const completion = await openai.chat.completions.create({ + model: model, + response_format: { type: 'json_object' }, + messages: [ + { + role: 'user', + content: ` You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on Russian language corpora, you are proficient in using the Russian language. Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the Russian language. When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted. @@ -53,22 +54,28 @@ Output in JSON. INPUT ###################################################### ${JSON.stringify({ - "confirm": "确定要备份数据吗?", - "select_model": "选择模型", - "title": "文件", - "deeply_thought": "已深度思考(用时 {{secounds}} 秒)", + confirm: '确定要备份数据吗?', + select_model: '选择模型', + title: '文件', + deeply_thought: '已深度思考(用时 {{secounds}} 秒)' })} ###################################################### MAKE SURE TO OUTPUT IN Russian. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE. ###################################################### - `}, - {role:"assistant", content:JSON.stringify({ - "confirm": "Подтвердите резервное копирование данных?", - "select_model": "Выберите Модель", - "title": "Файл", - "deeply_thought": "Глубоко продумано (заняло {{seconds}} секунд)" - })}, - {role:"user", content:` + ` + }, + { + role: 'assistant', + content: JSON.stringify({ + confirm: 'Подтвердите резервное копирование данных?', + select_model: 'Выберите Модель', + title: 'Файл', + deeply_thought: 'Глубоко продумано (заняло {{seconds}} секунд)' + }) + }, + { + role: 'user', + content: ` You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on ${target} language corpora, you are proficient in using the ${target} language. Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the ${target} language. When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted. @@ -80,43 +87,44 @@ ${JSON.stringify(texts)} ###################################################### MAKE SURE TO OUTPUT IN ${target}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE. ###################################################### - `} - ] - }) - // 添加翻译后的键值,并打印错译漏译内容 - try{ - const result = JSON.parse(completion.choices[0].message.content!) - for(let e in texts){ - if(result[e] && typeof result[e] === 'string'){ - obj[e] = result[e] - }else{ - console.log("[warning]", `missing value "${e}" in ${target} translation`) - } - } - }catch(e){ - console.log("[error]", e) - for(let e in texts){ - console.log("[warning]", `missing value "${e}" in ${target} translation`) - } + ` } - } - // 删除多余的键值 - for(let e in obj){ - if(!zh[e]){ - delete obj[e] + ] + }) + // 添加翻译后的键值,并打印错译漏译内容 + try { + const result = JSON.parse(completion.choices[0].message.content!) + for (const e in texts) { + if (result[e] && typeof result[e] === 'string') { + obj[e] = result[e] + } else { + console.log('[warning]', `missing value "${e}" in ${target} translation`) } + } + } catch (e) { + console.log('[error]', e) + for (const e in texts) { + console.log('[warning]', `missing value "${e}" in ${target} translation`) + } } - // 更新文件 - updateFile() + } + // 删除多余的键值 + for (const e in obj) { + if (!zh[e]) { + delete obj[e] + } + } + // 更新文件 + updateFile() } -(async ()=>{ - for(let {name, code, model} of INDEX){ - const obj = fs.existsSync(`src/renderer/src/i18n/locales/${code}.json`) - ? JSON.parse(fs.readFileSync(`src/renderer/src/i18n/locales/${code}.json`, 'utf8')) - : {} - await translate(zh, obj, name, model, ()=>{ - fs.writeFileSync(`src/renderer/src/i18n/locales/${code}.json`, JSON.stringify(obj, null, 4), 'utf8') - }) - } -})() \ No newline at end of file +;(async () => { + for (const { name, code, model } of INDEX) { + const obj = fs.existsSync(`src/renderer/src/i18n/locales/${code}.json`) + ? JSON.parse(fs.readFileSync(`src/renderer/src/i18n/locales/${code}.json`, 'utf8')) + : {} + await translate(zh, obj, name, model, () => { + fs.writeFileSync(`src/renderer/src/i18n/locales/${code}.json`, JSON.stringify(obj, null, 4), 'utf8') + }) + } +})() diff --git a/src/main/services/ShortcutService.ts b/src/main/services/ShortcutService.ts index 10d46730..949c07f8 100644 --- a/src/main/services/ShortcutService.ts +++ b/src/main/services/ShortcutService.ts @@ -144,7 +144,7 @@ export function registerShortcuts(window: BrowserWindow) { if (!shortcut.enabled) { return } - + // only register universal shortcuts when needed if (onlyUniversalShortcuts && !['show_app', 'mini_window'].includes(shortcut.key)) { return diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 9ae1c1dc..036418bb 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -144,7 +144,7 @@ export class WindowService { private setupWindowEvents(mainWindow: BrowserWindow) { mainWindow.once('ready-to-show', () => { mainWindow.webContents.setZoomFactor(configManager.getZoomFactor()) - + // show window only when laucn to tray not set const isLaunchToTray = configManager.getLaunchToTray() if (!isLaunchToTray) { diff --git a/src/renderer/src/context/AntdProvider.tsx b/src/renderer/src/context/AntdProvider.tsx index ea5357b9..3e230931 100644 --- a/src/renderer/src/context/AntdProvider.tsx +++ b/src/renderer/src/context/AntdProvider.tsx @@ -1,8 +1,12 @@ import { useSettings } from '@renderer/hooks/useSettings' import { LanguageVarious } from '@renderer/types' import { ConfigProvider, theme } from 'antd' +import elGR from 'antd/locale/el_GR' import enUS from 'antd/locale/en_US' +import esES from 'antd/locale/es_ES' +import frFR from 'antd/locale/fr_FR' import jaJP from 'antd/locale/ja_JP' +import ptPT from 'antd/locale/pt_PT' import ruRU from 'antd/locale/ru_RU' import zhCN from 'antd/locale/zh_CN' import zhTW from 'antd/locale/zh_TW' @@ -53,7 +57,14 @@ function getAntdLocale(language: LanguageVarious) { return ruRU case 'ja-JP': return jaJP - + case 'el-GR': + return elGR + case 'es-ES': + return esES + case 'fr-FR': + return frFR + case 'pt-PT': + return ptPT default: return zhCN } diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index efb267ab..df438f8d 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -1,5 +1,6 @@ import i18n from 'i18next' import { initReactI18next } from 'react-i18next' + import elGR from './locales/el-gr.json' import enUS from './locales/en-us.json' import esES from './locales/es-es.json' @@ -19,7 +20,7 @@ const resources = { 'pt-PT': ptPT, 'ru-RU': ruRU, 'zh-CN': zhCN, - 'zh-TW': zhTW, + 'zh-TW': zhTW } export const getLanguage = () => { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 1eaab35b..c1086dfb 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -556,7 +556,7 @@ "string": "文本" }, "pinned": "已固定", - "rerank_model": "重排序模型", + "rerank_model": "重排模型", "rerank_model_support_provider": "目前重排序模型仅支持部分服务商 ({{provider}})", "rerank_model_tooltip": "在设置->模型服务中点击管理按钮添加", "search": "搜索模型...", @@ -1170,4 +1170,4 @@ "visualization": "可视化" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 11ad3b84..45af07f9 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -565,7 +565,7 @@ "string": "文字" }, "pinned": "已固定", - "rerank_model": "重排序模型", + "rerank_model": "重排模型", "rerank_model_support_provider": "目前重排序模型僅支持部分服務商 ({{provider}})", "rerank_model_tooltip": "在設定->模型服務中點擊管理按鈕添加", "search": "搜尋模型...", @@ -1179,4 +1179,4 @@ "visualization": "視覺化" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index c43dd114..903e1fe8 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -15,8 +15,8 @@ import { UploadOutlined } from '@ant-design/icons' import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup' import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import TextEditPopup from '@renderer/components/Popups/TextEditPopup' -import { TranslateLanguageOptions } from '@renderer/config/translate' import { isReasoningModel } from '@renderer/config/models' +import { TranslateLanguageOptions } from '@renderer/config/translate' import { useMessageOperations } from '@renderer/hooks/useMessageOperations' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getMessageTitle, resetAssistantMessage } from '@renderer/services/MessagesService' @@ -24,7 +24,6 @@ import { translateText } from '@renderer/services/TranslateService' import { Message, Model } from '@renderer/types' import { Assistant, Topic } from '@renderer/types' import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTrailingDoubleSpaces } from '@renderer/utils' -import { withMessageThought } from '@renderer/utils/formats' import { exportMarkdownToJoplin, exportMarkdownToNotion, @@ -32,12 +31,13 @@ import { exportMessageAsMarkdown, messageToMarkdown } from '@renderer/utils/export' +import { withMessageThought } from '@renderer/utils/formats' import { Button, Dropdown, Popconfirm, Tooltip } from 'antd' import dayjs from 'dayjs' +import { clone } from 'lodash' import { FC, memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { clone } from 'lodash' interface Props { message: Message @@ -258,7 +258,7 @@ const MessageMenubar: FC = (props) => { ] } ], - [message, messageContainerRef, onEdit, onNewBranch, t] + [message, messageContainerRef, onEdit, onNewBranch, t, topic.name] ) const onRegenerate = async (e: React.MouseEvent | undefined) => { diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 2c7eb759..ffd0f26f 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -26,8 +26,8 @@ import BeatLoader from 'react-spinners/BeatLoader' import styled from 'styled-components' import ChatNavigation from './ChatNavigation' -import MessageGroup from './MessageGroup' import MessageAnchorLine from './MessageAnchorLine' +import MessageGroup from './MessageGroup' import NarrowLayout from './NarrowLayout' import NewTopicButton from './NewTopicButton' import Prompt from './Prompt' diff --git a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx index 68d5fc99..e796c4e9 100644 --- a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx @@ -24,6 +24,7 @@ import { setFontSize, setMathEngine, setMessageFont, + setMessageNavigation, setMessageStyle, setMultiModelMessageStyle, setPasteLongTextAsFile, @@ -31,7 +32,6 @@ import { setRenderInputMessageAsMarkdown, setShowInputEstimatedTokens, setShowMessageDivider, - setMessageNavigation, setThoughtAutoCollapse } from '@renderer/store/settings' import { Assistant, AssistantSettings, CodeStyleVarious, ThemeMode, TranslateLanguageVarious } from '@renderer/types' diff --git a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx index a632fe86..ada10179 100644 --- a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx +++ b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx @@ -584,10 +584,10 @@ const ModelInfo = styled.div` .model-row { display: flex; align-items: flex-start; + gap: 10px; } .label-column { - width: 80px; flex-shrink: 0; } diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx index 68fbd104..8e70107c 100644 --- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx @@ -12,13 +12,13 @@ import { setWebdavSyncInterval as _setWebdavSyncInterval, setWebdavUser as _setWebdavUser } from '@renderer/store/settings' +import { formatFileSize } from '@renderer/utils' import { Button, Input, Modal, Select, Spin, Tooltip } from 'antd' import dayjs from 'dayjs' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' -import { formatFileSize } from '@renderer/utils' interface BackupFile { fileName: string diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx index 34fe9d0c..65ef1102 100644 --- a/src/renderer/src/pages/settings/GeneralSettings.tsx +++ b/src/renderer/src/pages/settings/GeneralSettings.tsx @@ -96,13 +96,13 @@ const GeneralSettings: FC = () => { const languagesOptions: { value: LanguageVarious; label: string; flag: string }[] = [ { value: 'zh-CN', label: '中文', flag: '🇨🇳' }, { value: 'zh-TW', label: '中文(繁体)', flag: '🇭🇰' }, - { value: 'el-GR', label: 'Ελληνικά', flag: '🇬🇷' }, { value: 'en-US', label: 'English', flag: '🇺🇸' }, + { value: 'ja-JP', label: '日本語', flag: '🇯🇵' }, + { value: 'ru-RU', label: 'Русский', flag: '🇷🇺' }, + { value: 'el-GR', label: 'Ελληνικά', flag: '🇬🇷' }, { value: 'es-ES', label: 'Español', flag: '🇪🇸' }, { value: 'fr-FR', label: 'Français', flag: '🇫🇷' }, - { value: 'ja-JP', label: '日本語', flag: '🇯🇵' }, - { value: 'pt-PT', label: 'Português', flag: '🇵🇹' }, - { value: 'ru-RU', label: 'Русский', flag: '🇷🇺' } + { value: 'pt-PT', label: 'Português', flag: '🇵🇹' } ] return ( diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 0a302596..c6106b38 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -168,7 +168,6 @@ const initialState: SettingsState = { obsidianTages: '', joplinToken: '', joplinUrl: '' - } const settingsSlice = createSlice({ diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index e65a7f6b..93f138e4 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -201,7 +201,16 @@ export enum ThemeMode { export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU' -export type TranslateLanguageVarious = 'chinese' | 'chinese-traditional' | 'greek' | 'english' | 'spanish' | 'french' | 'japanese' | 'portuguese' | 'russian' +export type TranslateLanguageVarious = + | 'chinese' + | 'chinese-traditional' + | 'greek' + | 'english' + | 'spanish' + | 'french' + | 'japanese' + | 'portuguese' + | 'russian' export type CodeStyleVarious = BuiltinTheme | 'auto'