From 0d971d927f8c08e659ed15b0508cd0d7e9ab3a12 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Mon, 15 Jun 2026 18:32:56 +0800 Subject: [PATCH 01/28] =?UTF-8?q?feat(i18n):=20=E8=BF=81=E7=A7=BB=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E8=AE=BE=E7=BD=AE=E9=A1=B5=20(#97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i18n 增加 settings.shortcut.*;Shortcut.vue 页面静态文案改用 t() (快捷键动作名 action.label 在 composable 中,后续单独迁移)。 --- src/components/setting/Shortcut.vue | 14 ++++++++------ src/i18n/index.ts | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/components/setting/Shortcut.vue b/src/components/setting/Shortcut.vue index 2324102..88469ab 100644 --- a/src/components/setting/Shortcut.vue +++ b/src/components/setting/Shortcut.vue @@ -3,9 +3,9 @@

- 快捷键 + {{ t('settings.nav.shortcut') }}

- +
@@ -17,7 +17,7 @@
- 按下快捷键… + {{ t('settings.shortcut.press') }} @@ -26,26 +26,28 @@
-

提示:修改后关闭设置即生效。

+

{{ t('settings.shortcut.hint') }}

diff --git a/src/composables/useSettings.ts b/src/composables/useSettings.ts index 9ab8922..279bdd5 100644 --- a/src/composables/useSettings.ts +++ b/src/composables/useSettings.ts @@ -1,5 +1,5 @@ import { computed, nextTick, ref } from 'vue' -import { BracesIcon, CodeIcon, Cpu, Database, FileText, Globe, Keyboard, Server, ShieldIcon, Sparkles } from 'lucide-vue-next' +import { BracesIcon, CodeIcon, Cpu, Database, FileText, Globe, Keyboard, Languages, Server, ShieldIcon, Sparkles } from 'lucide-vue-next' import { i18n } from '../i18n' export function useSettings(emit: any) @@ -21,7 +21,8 @@ export function useSettings(emit: any) { key: 'lsp', label: t('settings.nav.lsp'), icon: Cpu }, { key: 'network', label: t('settings.nav.network'), icon: Globe }, { key: 'cache', label: t('settings.nav.cache'), icon: Database }, - { key: 'logs', label: t('settings.nav.logs'), icon: FileText } + { key: 'logs', label: t('settings.nav.logs'), icon: FileText }, + { key: 'i18n', label: t('settings.nav.i18n'), icon: Languages } ]) const handleEditorSettingsChanged = (config: any) => { diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 004571e..35ac3e9 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,487 +1,126 @@ -// 国际化(vue-i18n)。默认简体中文,界面零变化;英文为增量翻译,可在通用设置切换。 -// 文案按面逐步迁移,已迁移:通用设置页。 +// 国际化(vue-i18n)。 +// 内置语言包以 JSON 文件初始化(locales/*.json),用户自定义语言包存于本地数据库(KV), +// 启动时合并;可在「设置 → 语言包」中新增/编辑/删除,方便扩展。 +import {ref} from 'vue' import {createI18n} from 'vue-i18n' -import {kvGet, kvSet} from '../composables/useKvStore' +import {kvGet, kvSet, kvGetJSON, kvSetJSON} from '../composables/useKvStore' +import zhCN from './locales/zh-CN.json' +import en from './locales/en.json' -export const SUPPORTED_LOCALES = [ - {value: 'zh-CN', label: '简体中文'}, - {value: 'en', label: 'English'} -] -const LOCALE_KEY = 'app-locale' +export interface LocaleDef { + name: string + messages: Record +} -const messages = { - 'zh-CN': { - settings: { - title: '设置', - uiLanguage: '界面语言', - nav: { - general: '通用', - editor: '编辑器', - shortcut: '快捷键', - ai: 'AI', - database: '数据库', - language: '语言', - lsp: '语言服务', - network: '网络', - cache: '缓存', - logs: '日志' - }, - general: { - appearance: '外观', - theme: '主题', - runAndFile: '运行与文件', - runUnsaved: '运行未保存文件时', - selectStrategy: '选择运行策略', - maxFileSize: '打开文件大小上限 (MB)', - maxFileSizeHint: '超过该大小将以只读方式查看', - github: 'GitHub 配置', - githubToken: 'GitHub Token (可选)', - githubInfoTitle: 'GitHub Token 说明', - githubInfo1: '用于提高 GitHub API 请求速率限制(从 60次/小时 提升到 5000次/小时)', - githubInfo2Pre: '在 ', - githubInfo2Link: 'GitHub Settings', - githubInfo2Post: ' 创建 Personal Access Token', - githubInfo3: 'Token 不需要任何权限(public access 即可)', - githubInfo4: '留空则使用未认证模式访问 GitHub API', - saveGithub: '保存 GitHub 配置', - savingGithub: '保存中...', - clearToken: '清除 Token' - }, - editor: { - indentWithTab: '是否使用 Tab 缩进', - showLineNumbers: '是否显示行号', - showFunctionHelp: '是否显示函数帮助信息', - spaceDotOmission: '是否显示空格省略', - tabSize: '缩进空格数', - font: '编辑器字体', - fontSize: '字体大小', - theme: '编辑器主题', - selectTheme: '选择编辑器主题' - }, - ai: { - title: 'AI 助手', - provider: '服务商', - apiKey: 'API Key', - apiKeyHint: '填写所选服务商的 API Key', - model: '模型', - baseUrl: '接口地址(可选)', - info1: '请求经本地后端转发,避免浏览器跨域问题', - info2: 'API Key 仅保存在本机', - info3: '留空接口地址则使用服务商默认地址' - }, - network: { - title: 'CDN 镜像配置', - enable: '启用 CDN 镜像加速', - enabled: '已启用', - disabled: '未启用', - baseUrl: 'CDN 基础 URL', - fallback: 'CDN 下载失败时自动回退到 GitHub 官方源', - infoTitle: 'CDN 镜像说明', - info1: 'CDN 镜像用于加速环境安装包的下载', - info2: '启用自动回退后,CDN 下载失败会自动使用 GitHub 官方源', - info3: '关闭自动回退后,CDN 下载失败将直接报错,不会尝试其他源', - save: '保存配置', - saving: '保存中...', - reset: '重置为默认', - test: '测试连接', - testing: '测试中...' - }, - shortcut: { - resetAll: '全部重置', - press: '按下快捷键…', - cancel: '取消', - edit: '修改', - reset: '重置', - hint: '提示:修改后关闭设置即生效。' - }, - cache: { - title: '缓存管理', - desc: '管理应用缓存,包括代码执行临时文件等', - pluginsCache: '插件执行缓存', - totalCache: '总缓存大小', - loading: '加载中...', - clearPluginsTitle: '清理插件执行缓存', - clearPluginsDesc: '清理代码执行产生的临时文件', - clearAllTitle: '清理所有缓存', - clearAllDesc: '清理应用所有缓存数据', - clear: '清理', - clearedPlugins: '插件执行缓存已清理', - clearedAll: '所有缓存已清理' - }, - logs: { - title: '日志设置', - currentDir: '当前日志目录', - loading: '加载中...', - selectDir: '选择新的日志目录', - dirPlaceholder: '选择或输入日志目录路径', - apply: '应用更改', - openDir: '打开日志目录', - resetDefault: '重置为默认', - recentFiles: '最近的日志文件', - mgmtTitle: '日志管理', - cleanup: '清理日志', - keepDaysPlaceholder: '选择保留天数', - clearNow: '立即清理' - }, - theme: {system: '跟随系统', light: '浅色', dark: '深色'}, - runStrategy: {autoSave: '自动保存后运行', ask: '每次询问', tempCopy: '运行副本(不保存)'} - }, - header: { - selectLanguage: '选择语言', - run: '运行代码', - stop: '停止执行', - loadExample: '加载示例', - history: '执行历史', - openFile: '打开文件', - saveFile: '保存文件', - ai: 'AI 助手', - showSidebar: '显示侧栏', - hideSidebar: '隐藏侧栏', - layoutHorizontal: '左右布局', - layoutVertical: '上下布局', - layoutEditor: '仅编辑器', - hasUpdate: '有新版本' - }, - status: { - checking: '检查环境中...', - notInstalled: '环境未安装', - recheck: '重新检查环境', - latency: '最新: {ms} 毫秒', - lspIndexing: '语言服务索引中:{lang}', - lspReady: '语言服务已就绪:{lang}(点击查看问题)', - lspBadgeIndexing: 'LSP 索引中', - chars: '字符', - terminalTip: '终端({key})' - }, - git: { - notRepo: '非 Git 仓库', - refresh: '刷新', - close: '关闭', - staged: '暂存的更改', - unstageAll: '全部取消暂存', - changes: '更改', - stageAll: '全部暂存', - clean: '没有未提交的更改', - messagePlaceholder: '提交信息…(Cmd/Ctrl+Enter 提交)', - aiGenTitle: '用 AI 根据改动生成提交信息', - commit: '提交', - commitPush: '提交并推送', - push: '推送', - statusFailed: '读取 Git 状态失败', - stageFailed: '暂存失败', - unstageFailed: '取消暂存失败', - committed: '已提交', - commitFailed: '提交失败', - pushed: '已推送', - pushFailed: '推送失败', - needMessage: '请填写提交信息并暂存改动', - committedPushed: '已提交并推送', - commitPushFailed: '提交并推送失败', - switched: '已切换到 {branch}', - switchFailed: '切换分支失败', - aiNeedKey: '请先在设置中配置 AI 的 API Key', - noDiff: '没有可用于生成的改动', - genFailed: '生成失败' - }, - dialog: { - gotoLinePlaceholder: '跳转到行(1 - {max})', - gotoLineHint: '共 {n} 行 · 回车跳转', - quickOpenPlaceholder: '按文件名快速打开…', - quickOpenLoading: '加载文件列表…', - quickOpenEmpty: '无匹配文件', - recent: '最近', - outlinePlaceholder: '跳转到符号…', - outlineNoSymbols: '未识别到符号', - outlineEmpty: '无匹配符号' - }, - search: { - placeholder: '在文件夹内搜索…', - searching: '搜索中…', - countSummary: '{count} 处 / {files} 文件', - replacePlaceholder: '替换为…(大小写不敏感)', - replaceAllTitle: '替换全部 {count} 处', - searchFirst: '先搜索出结果', - replacing: '替换中…', - replaceAll: '全部替换', - confirmLine: '将把 {count} 处「{query}」替换为「{rep}」,涉及 {files} 个文件。', - confirmWarn: '直接写入磁盘且不可撤销,确定继续?', - cancel: '取消', - confirmReplace: '确认替换', - empty: '无匹配结果', - replacedSummary: '已替换 {count} 处,涉及 {files} 个文件', - replaceFailed: '替换失败' - }, - chat: { - title: 'AI 助手', - clear: '清空对话', - close: '关闭', - linked: '已关联运行 #{id},对话随该次运行保存', - temp: '临时会话:运行代码后对话才会保存', - analyzeError: '分析报错', - explain: '解释代码', - findBug: '找 Bug', - optimize: '优化', - genCommit: '生成提交信息', - emptyHint: '向 AI 提问,或用上方快捷动作处理当前代码', - thinking: '思考中…', - stop: '停止生成', - inputPlaceholder: '输入问题,Enter 发送(Shift+Enter 换行)', - copy: '复制', - insert: '应用到编辑器', - copied: '已复制代码', - inserted: '已应用到编辑器', - needKey: '请先在 设置 → AI 中填写 API Key', - emptyResponse: '(空响应)', - noCode: '当前编辑器没有代码', - noDiff: '没有检测到改动' - } - }, - en: { - settings: { - title: 'Settings', - uiLanguage: 'Language', - nav: { - general: 'General', - editor: 'Editor', - shortcut: 'Shortcuts', - ai: 'AI', - database: 'Database', - language: 'Languages', - lsp: 'Language Server', - network: 'Network', - cache: 'Cache', - logs: 'Logs' - }, - general: { - appearance: 'Appearance', - theme: 'Theme', - runAndFile: 'Run & Files', - runUnsaved: 'When running an unsaved file', - selectStrategy: 'Select run strategy', - maxFileSize: 'Max file size to open (MB)', - maxFileSizeHint: 'Larger files open in read-only view', - github: 'GitHub', - githubToken: 'GitHub Token (optional)', - githubInfoTitle: 'About GitHub Token', - githubInfo1: 'Raises GitHub API rate limit (from 60/hr to 5000/hr)', - githubInfo2Pre: 'Create a Personal Access Token in ', - githubInfo2Link: 'GitHub Settings', - githubInfo2Post: '', - githubInfo3: 'The token needs no scopes (public access is enough)', - githubInfo4: 'Leave empty to access the GitHub API unauthenticated', - saveGithub: 'Save GitHub config', - savingGithub: 'Saving...', - clearToken: 'Clear token' - }, - editor: { - indentWithTab: 'Use Tab for indentation', - showLineNumbers: 'Show line numbers', - showFunctionHelp: 'Show function help', - spaceDotOmission: 'Show whitespace dots', - tabSize: 'Indent size', - font: 'Editor font', - fontSize: 'Font size', - theme: 'Editor theme', - selectTheme: 'Select editor theme' - }, - ai: { - title: 'AI Assistant', - provider: 'Provider', - apiKey: 'API Key', - apiKeyHint: 'Enter the API Key for the selected provider', - model: 'Model', - baseUrl: 'API endpoint (optional)', - info1: 'Requests are proxied by the local backend to avoid CORS issues', - info2: 'The API Key is stored only on this machine', - info3: 'Leave the endpoint empty to use the provider default' - }, - network: { - title: 'CDN Mirror', - enable: 'Enable CDN mirror acceleration', - enabled: 'Enabled', - disabled: 'Disabled', - baseUrl: 'CDN base URL', - fallback: 'Fall back to official GitHub source when CDN download fails', - infoTitle: 'About CDN mirror', - info1: 'CDN mirror speeds up downloading environment install packages', - info2: 'With fallback on, a failed CDN download automatically uses the official GitHub source', - info3: 'With fallback off, a failed CDN download errors out without trying other sources', - save: 'Save', - saving: 'Saving...', - reset: 'Reset to default', - test: 'Test connection', - testing: 'Testing...' - }, - shortcut: { - resetAll: 'Reset all', - press: 'Press a shortcut…', - cancel: 'Cancel', - edit: 'Edit', - reset: 'Reset', - hint: 'Tip: changes take effect after closing Settings.' - }, - cache: { - title: 'Cache', - desc: 'Manage app cache, including temporary files from code execution', - pluginsCache: 'Plugin execution cache', - totalCache: 'Total cache size', - loading: 'Loading...', - clearPluginsTitle: 'Clear plugin execution cache', - clearPluginsDesc: 'Clear temporary files produced by code execution', - clearAllTitle: 'Clear all cache', - clearAllDesc: 'Clear all cached data of the app', - clear: 'Clear', - clearedPlugins: 'Plugin execution cache cleared', - clearedAll: 'All cache cleared' - }, - logs: { - title: 'Logs', - currentDir: 'Current log directory', - loading: 'Loading...', - selectDir: 'Choose a new log directory', - dirPlaceholder: 'Select or enter a log directory path', - apply: 'Apply changes', - openDir: 'Open log directory', - resetDefault: 'Reset to default', - recentFiles: 'Recent log files', - mgmtTitle: 'Log management', - cleanup: 'Clean logs', - keepDaysPlaceholder: 'Days to keep', - clearNow: 'Clean now' - }, - theme: {system: 'Follow system', light: 'Light', dark: 'Dark'}, - runStrategy: {autoSave: 'Auto-save then run', ask: 'Ask every time', tempCopy: 'Run a copy (no save)'} - }, - header: { - selectLanguage: 'Select language', - run: 'Run', - stop: 'Stop', - loadExample: 'Load example', - history: 'History', - openFile: 'Open file', - saveFile: 'Save file', - ai: 'AI Assistant', - showSidebar: 'Show sidebar', - hideSidebar: 'Hide sidebar', - layoutHorizontal: 'Side-by-side', - layoutVertical: 'Top-bottom', - layoutEditor: 'Editor only', - hasUpdate: 'Update available' - }, - status: { - checking: 'Checking environment...', - notInstalled: 'Not installed', - recheck: 'Recheck environment', - latency: 'Last: {ms} ms', - lspIndexing: 'Language server indexing: {lang}', - lspReady: 'Language server ready: {lang} (click to view problems)', - lspBadgeIndexing: 'LSP indexing', - chars: 'chars', - terminalTip: 'Terminal ({key})' - }, - git: { - notRepo: 'Not a Git repository', - refresh: 'Refresh', - close: 'Close', - staged: 'Staged changes', - unstageAll: 'Unstage all', - changes: 'Changes', - stageAll: 'Stage all', - clean: 'No uncommitted changes', - messagePlaceholder: 'Commit message… (Cmd/Ctrl+Enter to commit)', - aiGenTitle: 'Generate commit message from changes with AI', - commit: 'Commit', - commitPush: 'Commit & push', - push: 'Push', - statusFailed: 'Failed to read Git status', - stageFailed: 'Stage failed', - unstageFailed: 'Unstage failed', - committed: 'Committed', - commitFailed: 'Commit failed', - pushed: 'Pushed', - pushFailed: 'Push failed', - needMessage: 'Enter a commit message and stage changes', - committedPushed: 'Committed & pushed', - commitPushFailed: 'Commit & push failed', - switched: 'Switched to {branch}', - switchFailed: 'Failed to switch branch', - aiNeedKey: 'Configure the AI API Key in Settings first', - noDiff: 'No changes to generate from', - genFailed: 'Generation failed' - }, - dialog: { - gotoLinePlaceholder: 'Go to line (1 - {max})', - gotoLineHint: '{n} lines · Enter to jump', - quickOpenPlaceholder: 'Quick open by file name…', - quickOpenLoading: 'Loading files…', - quickOpenEmpty: 'No matching files', - recent: 'Recent', - outlinePlaceholder: 'Go to symbol…', - outlineNoSymbols: 'No symbols detected', - outlineEmpty: 'No matching symbols' - }, - search: { - placeholder: 'Search in folder…', - searching: 'Searching…', - countSummary: '{count} matches / {files} files', - replacePlaceholder: 'Replace with… (case-insensitive)', - replaceAllTitle: 'Replace all {count} matches', - searchFirst: 'Search first', - replacing: 'Replacing…', - replaceAll: 'Replace all', - confirmLine: 'Replace {count} occurrence(s) of "{query}" with "{rep}" across {files} file(s).', - confirmWarn: 'Writes to disk directly and cannot be undone. Continue?', - cancel: 'Cancel', - confirmReplace: 'Replace', - empty: 'No matching results', - replacedSummary: 'Replaced {count} occurrence(s) across {files} file(s)', - replaceFailed: 'Replace failed' - }, - chat: { - title: 'AI Assistant', - clear: 'Clear chat', - close: 'Close', - linked: 'Linked to run #{id}; chat is saved with this run', - temp: 'Temporary session: chat is saved only after running code', - analyzeError: 'Analyze error', - explain: 'Explain code', - findBug: 'Find bugs', - optimize: 'Optimize', - genCommit: 'Generate commit message', - emptyHint: 'Ask the AI, or use the quick actions above on the current code', - thinking: 'Thinking…', - stop: 'Stop generating', - inputPlaceholder: 'Type a question, Enter to send (Shift+Enter for newline)', - copy: 'Copy', - insert: 'Insert into editor', - copied: 'Code copied', - inserted: 'Inserted into editor', - needKey: 'Fill in the API Key in Settings → AI first', - emptyResponse: '(empty response)', - noCode: 'No code in the current editor', - noDiff: 'No changes detected' - } - } +// 内置语言包(初始化数据源) +const BUILTIN: Record = { + 'zh-CN': {name: '简体中文', messages: zhCN}, + en: {name: 'English', messages: en} } +const LOCALE_KEY = 'app-locale' +const CUSTOM_KEY = 'i18n-custom-locales' // DB(KV):{ [code]: { name, messages } } + export const i18n = createI18n({ legacy: false, locale: 'zh-CN', fallbackLocale: 'zh-CN', - messages + messages: {'zh-CN': zhCN, en} as any }) +// 可用语言列表(内置 + 自定义),响应式,供界面语言下拉与管理页使用 +export const availableLocales = ref<{ value: string; label: string; builtin: boolean }[]>([]) + +const readCustom = (): Record => kvGetJSON>(CUSTOM_KEY, {}) +const writeCustom = (data: Record) => kvSetJSON(CUSTOM_KEY, data) + +const rebuildAvailable = (custom: Record) => { + const list: { value: string; label: string; builtin: boolean }[] = [] + for (const [code, def] of Object.entries(BUILTIN)) { + list.push({value: code, label: custom[code]?.name || def.name, builtin: true}) + } + for (const [code, def] of Object.entries(custom)) { + if (!BUILTIN[code]) { + list.push({value: code, label: def.name || code, builtin: false}) + } + } + availableLocales.value = list +} + +// 启动时调用(须在 loadKvStore 之后):合并 DB 自定义语言包并恢复上次语言 +export const loadLocales = () => { + const custom = readCustom() + for (const [code, def] of Object.entries(custom)) { + i18n.global.setLocaleMessage(code, def.messages as any) + } + rebuildAvailable(custom) + const saved = kvGet(LOCALE_KEY) + if (saved && availableLocales.value.some(l => l.value === saved)) { + i18n.global.locale.value = saved as any + } +} + export const setLocale = (locale: string) => { - i18n.global.locale.value = locale as 'zh-CN' | 'en' + i18n.global.locale.value = locale as any kvSet(LOCALE_KEY, locale) } +export const getLocale = (): string => i18n.global.locale.value as string -export const getLocale = (): string => i18n.global.locale.value +// —— 语言包管理(供「设置 → 语言包」)—— -// 启动时从 KV 恢复(须在 loadKvStore 之后调用) -export const loadSavedLocale = () => { - const saved = kvGet(LOCALE_KEY) - if (saved && SUPPORTED_LOCALES.some(s => s.value === saved)) { - i18n.global.locale.value = saved as 'zh-CN' | 'en' +export const isBuiltinLocale = (code: string) => !!BUILTIN[code] + +// 取某语言当前完整文案(自定义优先,否则内置);用于编辑器预填 +export const getLocaleMessages = (code: string): Record => { + const custom = readCustom() + if (custom[code]) { + return custom[code].messages + } + if (BUILTIN[code]) { + return BUILTIN[code].messages } + return {} } + +// 取内置默认文案(用于以某内置语言为模板新建) +export const getBuiltinMessages = (code: string): Record => BUILTIN[code]?.messages ?? BUILTIN['zh-CN'].messages + +// 新增/编辑:写入 DB、应用到 i18n、刷新列表 +export const saveLocale = (code: string, name: string, messages: Record) => { + const custom = readCustom() + custom[code] = {name, messages} + writeCustom(custom) + i18n.global.setLocaleMessage(code, messages as any) + rebuildAvailable(custom) +} + +// 删除自定义语言包(内置不可删);删的是当前语言则切回 zh-CN +export const deleteLocale = (code: string) => { + if (BUILTIN[code]) { + return + } + const custom = readCustom() + delete custom[code] + writeCustom(custom) + rebuildAvailable(custom) + if (getLocale() === code) { + setLocale('zh-CN') + } +} + +// 恢复内置默认(移除对内置语言的自定义覆盖) +export const resetBuiltin = (code: string) => { + if (!BUILTIN[code]) { + return + } + const custom = readCustom() + if (custom[code]) { + delete custom[code] + writeCustom(custom) + } + i18n.global.setLocaleMessage(code, BUILTIN[code].messages as any) + rebuildAvailable(custom) +} + +// 模块初始化时先用内置填充列表(KV 尚未加载时的兜底;loadLocales 后会再刷新) +rebuildAvailable({}) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json new file mode 100644 index 0000000..1689a66 --- /dev/null +++ b/src/i18n/locales/en.json @@ -0,0 +1,252 @@ +{ + "settings": { + "title": "Settings", + "uiLanguage": "Language", + "nav": { + "general": "General", + "editor": "Editor", + "shortcut": "Shortcuts", + "ai": "AI", + "database": "Database", + "language": "Languages", + "lsp": "Language Server", + "network": "Network", + "cache": "Cache", + "logs": "Logs", + "i18n": "Language packs" + }, + "general": { + "appearance": "Appearance", + "theme": "Theme", + "runAndFile": "Run & Files", + "runUnsaved": "When running an unsaved file", + "selectStrategy": "Select run strategy", + "maxFileSize": "Max file size to open (MB)", + "maxFileSizeHint": "Larger files open in read-only view", + "github": "GitHub", + "githubToken": "GitHub Token (optional)", + "githubInfoTitle": "About GitHub Token", + "githubInfo1": "Raises GitHub API rate limit (from 60/hr to 5000/hr)", + "githubInfo2Pre": "Create a Personal Access Token in ", + "githubInfo2Link": "GitHub Settings", + "githubInfo2Post": "", + "githubInfo3": "The token needs no scopes (public access is enough)", + "githubInfo4": "Leave empty to access the GitHub API unauthenticated", + "saveGithub": "Save GitHub config", + "savingGithub": "Saving...", + "clearToken": "Clear token" + }, + "editor": { + "indentWithTab": "Use Tab for indentation", + "showLineNumbers": "Show line numbers", + "showFunctionHelp": "Show function help", + "spaceDotOmission": "Show whitespace dots", + "tabSize": "Indent size", + "font": "Editor font", + "fontSize": "Font size", + "theme": "Editor theme", + "selectTheme": "Select editor theme" + }, + "ai": { + "title": "AI Assistant", + "provider": "Provider", + "apiKey": "API Key", + "apiKeyHint": "Enter the API Key for the selected provider", + "model": "Model", + "baseUrl": "API endpoint (optional)", + "info1": "Requests are proxied by the local backend to avoid CORS issues", + "info2": "The API Key is stored only on this machine", + "info3": "Leave the endpoint empty to use the provider default" + }, + "network": { + "title": "CDN Mirror", + "enable": "Enable CDN mirror acceleration", + "enabled": "Enabled", + "disabled": "Disabled", + "baseUrl": "CDN base URL", + "fallback": "Fall back to official GitHub source when CDN download fails", + "infoTitle": "About CDN mirror", + "info1": "CDN mirror speeds up downloading environment install packages", + "info2": "With fallback on, a failed CDN download automatically uses the official GitHub source", + "info3": "With fallback off, a failed CDN download errors out without trying other sources", + "save": "Save", + "saving": "Saving...", + "reset": "Reset to default", + "test": "Test connection", + "testing": "Testing..." + }, + "shortcut": { + "resetAll": "Reset all", + "press": "Press a shortcut…", + "cancel": "Cancel", + "edit": "Edit", + "reset": "Reset", + "hint": "Tip: changes take effect after closing Settings." + }, + "cache": { + "title": "Cache", + "desc": "Manage app cache, including temporary files from code execution", + "pluginsCache": "Plugin execution cache", + "totalCache": "Total cache size", + "loading": "Loading...", + "clearPluginsTitle": "Clear plugin execution cache", + "clearPluginsDesc": "Clear temporary files produced by code execution", + "clearAllTitle": "Clear all cache", + "clearAllDesc": "Clear all cached data of the app", + "clear": "Clear", + "clearedPlugins": "Plugin execution cache cleared", + "clearedAll": "All cache cleared" + }, + "logs": { + "title": "Logs", + "currentDir": "Current log directory", + "loading": "Loading...", + "selectDir": "Choose a new log directory", + "dirPlaceholder": "Select or enter a log directory path", + "apply": "Apply changes", + "openDir": "Open log directory", + "resetDefault": "Reset to default", + "recentFiles": "Recent log files", + "mgmtTitle": "Log management", + "cleanup": "Clean logs", + "keepDaysPlaceholder": "Days to keep", + "clearNow": "Clean now" + }, + "i18n": { + "title": "Language packs", + "desc": "Manage UI language packs. Add languages or edit text; saved to the local database and applied instantly.", + "addLocale": "Add language", + "code": "Locale code", + "codePlaceholder": "e.g. ja, ko, zh-TW", + "name": "Display name", + "namePlaceholder": "e.g. 日本語", + "basedOn": "Based on", + "create": "Create", + "cancel": "Cancel", + "edit": "Edit", + "delete": "Delete", + "builtin": "Built-in", + "custom": "Custom", + "messages": "Messages (JSON)", + "save": "Save", + "resetBuiltin": "Reset to built-in default", + "saved": "Language pack saved", + "invalidJson": "Invalid JSON, please check", + "codeRequired": "Enter both locale code and name", + "codeExists": "This locale code already exists", + "deleted": "Language pack deleted", + "resetDone": "Reset to built-in default" + }, + "theme": {"system": "Follow system", "light": "Light", "dark": "Dark"}, + "runStrategy": {"autoSave": "Auto-save then run", "ask": "Ask every time", "tempCopy": "Run a copy (no save)"} + }, + "header": { + "selectLanguage": "Select language", + "run": "Run", + "stop": "Stop", + "loadExample": "Load example", + "history": "History", + "openFile": "Open file", + "saveFile": "Save file", + "ai": "AI Assistant", + "showSidebar": "Show sidebar", + "hideSidebar": "Hide sidebar", + "layoutHorizontal": "Side-by-side", + "layoutVertical": "Top-bottom", + "layoutEditor": "Editor only", + "hasUpdate": "Update available" + }, + "status": { + "checking": "Checking environment...", + "notInstalled": "Not installed", + "recheck": "Recheck environment", + "latency": "Last: {ms} ms", + "lspIndexing": "Language server indexing: {lang}", + "lspReady": "Language server ready: {lang} (click to view problems)", + "lspBadgeIndexing": "LSP indexing", + "chars": "chars", + "terminalTip": "Terminal ({key})" + }, + "git": { + "notRepo": "Not a Git repository", + "refresh": "Refresh", + "close": "Close", + "staged": "Staged changes", + "unstageAll": "Unstage all", + "changes": "Changes", + "stageAll": "Stage all", + "clean": "No uncommitted changes", + "messagePlaceholder": "Commit message… (Cmd/Ctrl+Enter to commit)", + "aiGenTitle": "Generate commit message from changes with AI", + "commit": "Commit", + "commitPush": "Commit & push", + "push": "Push", + "statusFailed": "Failed to read Git status", + "stageFailed": "Stage failed", + "unstageFailed": "Unstage failed", + "committed": "Committed", + "commitFailed": "Commit failed", + "pushed": "Pushed", + "pushFailed": "Push failed", + "needMessage": "Enter a commit message and stage changes", + "committedPushed": "Committed & pushed", + "commitPushFailed": "Commit & push failed", + "switched": "Switched to {branch}", + "switchFailed": "Failed to switch branch", + "aiNeedKey": "Configure the AI API Key in Settings first", + "noDiff": "No changes to generate from", + "genFailed": "Generation failed" + }, + "dialog": { + "gotoLinePlaceholder": "Go to line (1 - {max})", + "gotoLineHint": "{n} lines · Enter to jump", + "quickOpenPlaceholder": "Quick open by file name…", + "quickOpenLoading": "Loading files…", + "quickOpenEmpty": "No matching files", + "recent": "Recent", + "outlinePlaceholder": "Go to symbol…", + "outlineNoSymbols": "No symbols detected", + "outlineEmpty": "No matching symbols" + }, + "search": { + "placeholder": "Search in folder…", + "searching": "Searching…", + "countSummary": "{count} matches / {files} files", + "replacePlaceholder": "Replace with… (case-insensitive)", + "replaceAllTitle": "Replace all {count} matches", + "searchFirst": "Search first", + "replacing": "Replacing…", + "replaceAll": "Replace all", + "confirmLine": "Replace {count} occurrence(s) of \"{query}\" with \"{rep}\" across {files} file(s).", + "confirmWarn": "Writes to disk directly and cannot be undone. Continue?", + "cancel": "Cancel", + "confirmReplace": "Replace", + "empty": "No matching results", + "replacedSummary": "Replaced {count} occurrence(s) across {files} file(s)", + "replaceFailed": "Replace failed" + }, + "chat": { + "title": "AI Assistant", + "clear": "Clear chat", + "close": "Close", + "linked": "Linked to run #{id}; chat is saved with this run", + "temp": "Temporary session: chat is saved only after running code", + "analyzeError": "Analyze error", + "explain": "Explain code", + "findBug": "Find bugs", + "optimize": "Optimize", + "genCommit": "Generate commit message", + "emptyHint": "Ask the AI, or use the quick actions above on the current code", + "thinking": "Thinking…", + "stop": "Stop generating", + "inputPlaceholder": "Type a question, Enter to send (Shift+Enter for newline)", + "copy": "Copy", + "insert": "Insert into editor", + "copied": "Code copied", + "inserted": "Inserted into editor", + "needKey": "Fill in the API Key in Settings → AI first", + "emptyResponse": "(empty response)", + "noCode": "No code in the current editor", + "noDiff": "No changes detected" + } +} diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json new file mode 100644 index 0000000..e7657bc --- /dev/null +++ b/src/i18n/locales/zh-CN.json @@ -0,0 +1,252 @@ +{ + "settings": { + "title": "设置", + "uiLanguage": "界面语言", + "nav": { + "general": "通用", + "editor": "编辑器", + "shortcut": "快捷键", + "ai": "AI", + "database": "数据库", + "language": "语言", + "lsp": "语言服务", + "network": "网络", + "cache": "缓存", + "logs": "日志", + "i18n": "语言包" + }, + "general": { + "appearance": "外观", + "theme": "主题", + "runAndFile": "运行与文件", + "runUnsaved": "运行未保存文件时", + "selectStrategy": "选择运行策略", + "maxFileSize": "打开文件大小上限 (MB)", + "maxFileSizeHint": "超过该大小将以只读方式查看", + "github": "GitHub 配置", + "githubToken": "GitHub Token (可选)", + "githubInfoTitle": "GitHub Token 说明", + "githubInfo1": "用于提高 GitHub API 请求速率限制(从 60次/小时 提升到 5000次/小时)", + "githubInfo2Pre": "在 ", + "githubInfo2Link": "GitHub Settings", + "githubInfo2Post": " 创建 Personal Access Token", + "githubInfo3": "Token 不需要任何权限(public access 即可)", + "githubInfo4": "留空则使用未认证模式访问 GitHub API", + "saveGithub": "保存 GitHub 配置", + "savingGithub": "保存中...", + "clearToken": "清除 Token" + }, + "editor": { + "indentWithTab": "是否使用 Tab 缩进", + "showLineNumbers": "是否显示行号", + "showFunctionHelp": "是否显示函数帮助信息", + "spaceDotOmission": "是否显示空格省略", + "tabSize": "缩进空格数", + "font": "编辑器字体", + "fontSize": "字体大小", + "theme": "编辑器主题", + "selectTheme": "选择编辑器主题" + }, + "ai": { + "title": "AI 助手", + "provider": "服务商", + "apiKey": "API Key", + "apiKeyHint": "填写所选服务商的 API Key", + "model": "模型", + "baseUrl": "接口地址(可选)", + "info1": "请求经本地后端转发,避免浏览器跨域问题", + "info2": "API Key 仅保存在本机", + "info3": "留空接口地址则使用服务商默认地址" + }, + "network": { + "title": "CDN 镜像配置", + "enable": "启用 CDN 镜像加速", + "enabled": "已启用", + "disabled": "未启用", + "baseUrl": "CDN 基础 URL", + "fallback": "CDN 下载失败时自动回退到 GitHub 官方源", + "infoTitle": "CDN 镜像说明", + "info1": "CDN 镜像用于加速环境安装包的下载", + "info2": "启用自动回退后,CDN 下载失败会自动使用 GitHub 官方源", + "info3": "关闭自动回退后,CDN 下载失败将直接报错,不会尝试其他源", + "save": "保存配置", + "saving": "保存中...", + "reset": "重置为默认", + "test": "测试连接", + "testing": "测试中..." + }, + "shortcut": { + "resetAll": "全部重置", + "press": "按下快捷键…", + "cancel": "取消", + "edit": "修改", + "reset": "重置", + "hint": "提示:修改后关闭设置即生效。" + }, + "cache": { + "title": "缓存管理", + "desc": "管理应用缓存,包括代码执行临时文件等", + "pluginsCache": "插件执行缓存", + "totalCache": "总缓存大小", + "loading": "加载中...", + "clearPluginsTitle": "清理插件执行缓存", + "clearPluginsDesc": "清理代码执行产生的临时文件", + "clearAllTitle": "清理所有缓存", + "clearAllDesc": "清理应用所有缓存数据", + "clear": "清理", + "clearedPlugins": "插件执行缓存已清理", + "clearedAll": "所有缓存已清理" + }, + "logs": { + "title": "日志设置", + "currentDir": "当前日志目录", + "loading": "加载中...", + "selectDir": "选择新的日志目录", + "dirPlaceholder": "选择或输入日志目录路径", + "apply": "应用更改", + "openDir": "打开日志目录", + "resetDefault": "重置为默认", + "recentFiles": "最近的日志文件", + "mgmtTitle": "日志管理", + "cleanup": "清理日志", + "keepDaysPlaceholder": "选择保留天数", + "clearNow": "立即清理" + }, + "i18n": { + "title": "语言包", + "desc": "管理界面语言包。可新增语言或编辑文案,保存后存入本地数据库并即时生效。", + "addLocale": "新增语言", + "code": "语言代码", + "codePlaceholder": "如 ja、ko、zh-TW", + "name": "显示名称", + "namePlaceholder": "如 日本語", + "basedOn": "基于", + "create": "创建", + "cancel": "取消", + "edit": "编辑", + "delete": "删除", + "builtin": "内置", + "custom": "自定义", + "messages": "文案(JSON)", + "save": "保存", + "resetBuiltin": "恢复内置默认", + "saved": "语言包已保存", + "invalidJson": "JSON 格式有误,请检查", + "codeRequired": "请填写语言代码与名称", + "codeExists": "该语言代码已存在", + "deleted": "已删除该语言包", + "resetDone": "已恢复为内置默认" + }, + "theme": {"system": "跟随系统", "light": "浅色", "dark": "深色"}, + "runStrategy": {"autoSave": "自动保存后运行", "ask": "每次询问", "tempCopy": "运行副本(不保存)"} + }, + "header": { + "selectLanguage": "选择语言", + "run": "运行代码", + "stop": "停止执行", + "loadExample": "加载示例", + "history": "执行历史", + "openFile": "打开文件", + "saveFile": "保存文件", + "ai": "AI 助手", + "showSidebar": "显示侧栏", + "hideSidebar": "隐藏侧栏", + "layoutHorizontal": "左右布局", + "layoutVertical": "上下布局", + "layoutEditor": "仅编辑器", + "hasUpdate": "有新版本" + }, + "status": { + "checking": "检查环境中...", + "notInstalled": "环境未安装", + "recheck": "重新检查环境", + "latency": "最新: {ms} 毫秒", + "lspIndexing": "语言服务索引中:{lang}", + "lspReady": "语言服务已就绪:{lang}(点击查看问题)", + "lspBadgeIndexing": "LSP 索引中", + "chars": "字符", + "terminalTip": "终端({key})" + }, + "git": { + "notRepo": "非 Git 仓库", + "refresh": "刷新", + "close": "关闭", + "staged": "暂存的更改", + "unstageAll": "全部取消暂存", + "changes": "更改", + "stageAll": "全部暂存", + "clean": "没有未提交的更改", + "messagePlaceholder": "提交信息…(Cmd/Ctrl+Enter 提交)", + "aiGenTitle": "用 AI 根据改动生成提交信息", + "commit": "提交", + "commitPush": "提交并推送", + "push": "推送", + "statusFailed": "读取 Git 状态失败", + "stageFailed": "暂存失败", + "unstageFailed": "取消暂存失败", + "committed": "已提交", + "commitFailed": "提交失败", + "pushed": "已推送", + "pushFailed": "推送失败", + "needMessage": "请填写提交信息并暂存改动", + "committedPushed": "已提交并推送", + "commitPushFailed": "提交并推送失败", + "switched": "已切换到 {branch}", + "switchFailed": "切换分支失败", + "aiNeedKey": "请先在设置中配置 AI 的 API Key", + "noDiff": "没有可用于生成的改动", + "genFailed": "生成失败" + }, + "dialog": { + "gotoLinePlaceholder": "跳转到行(1 - {max})", + "gotoLineHint": "共 {n} 行 · 回车跳转", + "quickOpenPlaceholder": "按文件名快速打开…", + "quickOpenLoading": "加载文件列表…", + "quickOpenEmpty": "无匹配文件", + "recent": "最近", + "outlinePlaceholder": "跳转到符号…", + "outlineNoSymbols": "未识别到符号", + "outlineEmpty": "无匹配符号" + }, + "search": { + "placeholder": "在文件夹内搜索…", + "searching": "搜索中…", + "countSummary": "{count} 处 / {files} 文件", + "replacePlaceholder": "替换为…(大小写不敏感)", + "replaceAllTitle": "替换全部 {count} 处", + "searchFirst": "先搜索出结果", + "replacing": "替换中…", + "replaceAll": "全部替换", + "confirmLine": "将把 {count} 处「{query}」替换为「{rep}」,涉及 {files} 个文件。", + "confirmWarn": "直接写入磁盘且不可撤销,确定继续?", + "cancel": "取消", + "confirmReplace": "确认替换", + "empty": "无匹配结果", + "replacedSummary": "已替换 {count} 处,涉及 {files} 个文件", + "replaceFailed": "替换失败" + }, + "chat": { + "title": "AI 助手", + "clear": "清空对话", + "close": "关闭", + "linked": "已关联运行 #{id},对话随该次运行保存", + "temp": "临时会话:运行代码后对话才会保存", + "analyzeError": "分析报错", + "explain": "解释代码", + "findBug": "找 Bug", + "optimize": "优化", + "genCommit": "生成提交信息", + "emptyHint": "向 AI 提问,或用上方快捷动作处理当前代码", + "thinking": "思考中…", + "stop": "停止生成", + "inputPlaceholder": "输入问题,Enter 发送(Shift+Enter 换行)", + "copy": "复制", + "insert": "应用到编辑器", + "copied": "已复制代码", + "inserted": "已应用到编辑器", + "needKey": "请先在 设置 → AI 中填写 API Key", + "emptyResponse": "(空响应)", + "noCode": "当前编辑器没有代码", + "noDiff": "没有检测到改动" + } +} diff --git a/src/main.ts b/src/main.ts index 62718cc..9d8c190 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,11 +3,11 @@ import App from './App.vue' import './style.css' import ToastPlugin from './plugins/toast' import { loadKvStore } from './composables/useKvStore' -import { i18n, loadSavedLocale } from './i18n' +import { i18n, loadLocales } from './i18n' // 先从数据库载入全部键值(替代 localStorage),再挂载应用,保证同步读取可用 loadKvStore().finally(() => { - loadSavedLocale() // KV 载入后恢复界面语言 + loadLocales() // KV 载入后合并自定义语言包并恢复界面语言 createApp(App) .use(ToastPlugin) .use(i18n) From b4bf7cb62ee59ddef8f8b41af7317781a47d92b1 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Tue, 16 Jun 2026 12:01:58 +0800 Subject: [PATCH 12/28] =?UTF-8?q?fix(i18n):=20=E8=AF=AD=E8=A8=80=E5=8C=85?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=94=B9=E4=B8=BA=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E4=BC=98=E5=85=88=E3=80=81JSON=20=E5=A1=AB=E5=85=85=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 启动/保存时对每个语言用 JSON 内置文案作底,深合并数据库自定义(DB 优先), 缺失的键回退到 JSON,而非整套替换。编辑器预填也显示合并后的完整文案。 --- src/i18n/index.ts | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 35ac3e9..60ae582 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -34,6 +34,28 @@ export const availableLocales = ref<{ value: string; label: string; builtin: boo const readCustom = (): Record => kvGetJSON>(CUSTOM_KEY, {}) const writeCustom = (data: Record) => kvSetJSON(CUSTOM_KEY, data) +// 深合并:override 优先,缺失的键用 base 填充(对象递归,数组/基本类型直接覆盖) +const deepMerge = (base: any, override: any): any => { + if (!base || typeof base !== 'object' || Array.isArray(base)) { + return override ?? base + } + if (!override || typeof override !== 'object' || Array.isArray(override)) { + return override ?? base + } + const out: Record = {...base} + for (const k of Object.keys(override)) { + out[k] = k in base ? deepMerge(base[k], override[k]) : override[k] + } + return out +} + +// 某语言的最终生效文案:内置 JSON 作底,数据库自定义覆盖(DB 优先,缺失用 JSON 填充) +const effectiveMessages = (code: string, custom?: Record): Record => { + const c = custom ?? readCustom() + const base = BUILTIN[code]?.messages ?? {} + return c[code] ? deepMerge(base, c[code].messages) : base +} + const rebuildAvailable = (custom: Record) => { const list: { value: string; label: string; builtin: boolean }[] = [] for (const [code, def] of Object.entries(BUILTIN)) { @@ -50,8 +72,9 @@ const rebuildAvailable = (custom: Record) => { // 启动时调用(须在 loadKvStore 之后):合并 DB 自定义语言包并恢复上次语言 export const loadLocales = () => { const custom = readCustom() - for (const [code, def] of Object.entries(custom)) { - i18n.global.setLocaleMessage(code, def.messages as any) + // 数据库优先、JSON 填充:内置语言把自定义合并到 JSON 底;纯自定义语言直接用其文案 + for (const code of Object.keys(custom)) { + i18n.global.setLocaleMessage(code, effectiveMessages(code, custom) as any) } rebuildAvailable(custom) const saved = kvGet(LOCALE_KEY) @@ -70,17 +93,8 @@ export const getLocale = (): string => i18n.global.locale.value as string export const isBuiltinLocale = (code: string) => !!BUILTIN[code] -// 取某语言当前完整文案(自定义优先,否则内置);用于编辑器预填 -export const getLocaleMessages = (code: string): Record => { - const custom = readCustom() - if (custom[code]) { - return custom[code].messages - } - if (BUILTIN[code]) { - return BUILTIN[code].messages - } - return {} -} +// 取某语言当前最终生效文案(JSON 底 + 数据库覆盖);用于编辑器预填,便于看到全部键 +export const getLocaleMessages = (code: string): Record => effectiveMessages(code) // 取内置默认文案(用于以某内置语言为模板新建) export const getBuiltinMessages = (code: string): Record => BUILTIN[code]?.messages ?? BUILTIN['zh-CN'].messages @@ -90,7 +104,8 @@ export const saveLocale = (code: string, name: string, messages: Record Date: Tue, 16 Jun 2026 12:01:58 +0800 Subject: [PATCH 13/28] =?UTF-8?q?fix(settings):=20=E8=AE=BE=E7=BD=AE=20Tab?= =?UTF-8?q?=20=E5=AF=BC=E8=88=AA=E8=B6=85=E5=AE=BD=E6=97=B6=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=A8=AA=E5=90=91=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 标签增多后导航行超出宽度;nav 改 flex-nowrap + overflow-x-auto, 按钮 flex-shrink-0,溢出时横向滚动而非挤压/截断。 --- src/components/Settings.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Settings.vue b/src/components/Settings.vue index b468ff1..1735fd2 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -3,8 +3,8 @@