From d608bf59c7bd84f0e9b3cc116af8cd0169198b24 Mon Sep 17 00:00:00 2001 From: himeditator Date: Thu, 3 Jul 2025 20:36:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E5=90=8E=E7=AB=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF=E6=8C=81=E3=80=81?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=89=8D=E7=AB=AF=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端添加并实现国际化支持 - 前端引入 vue-i18n 模块(尚未添加国际化逻辑) - 优化用户界面样式,统一输入框和标签样式 --- README.md | 4 +- README_en.md | 4 +- package-lock.json | 110 +++++++++++------ package.json | 1 + src/main/i18n/index.ts | 11 ++ src/main/i18n/lang/en.ts | 8 ++ src/main/i18n/lang/ja.ts | 8 ++ src/main/i18n/lang/zh.ts | 8 ++ src/main/utils/AllConfig.ts | 9 +- src/main/utils/CaptionEngine.ts | 17 +-- src/renderer/src/assets/input.css | 22 ++++ src/renderer/src/components/CaptionStyle.vue | 114 +++++++----------- src/renderer/src/components/EngineControl.vue | 65 ++++------ src/renderer/src/components/EngineStatus.vue | 79 ++++++++++++ .../src/components/GeneralSetting.vue | 46 ++----- src/renderer/src/i18n/index.ts | 17 +++ src/renderer/src/i18n/lang/en.ts | 3 + src/renderer/src/i18n/lang/ja.ts | 3 + src/renderer/src/i18n/lang/zh.ts | 8 ++ src/renderer/src/main.ts | 2 + src/renderer/src/views/CaptionPage.vue | 4 +- src/renderer/src/views/ControlPage.vue | 30 ----- 22 files changed, 344 insertions(+), 229 deletions(-) create mode 100644 src/main/i18n/index.ts create mode 100644 src/main/i18n/lang/en.ts create mode 100644 src/main/i18n/lang/ja.ts create mode 100644 src/main/i18n/lang/zh.ts create mode 100644 src/renderer/src/assets/input.css create mode 100644 src/renderer/src/i18n/index.ts create mode 100644 src/renderer/src/i18n/lang/en.ts create mode 100644 src/renderer/src/i18n/lang/ja.ts create mode 100644 src/renderer/src/i18n/lang/zh.ts diff --git a/README.md b/README.md index 81171e0..1994c57 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@

+

新版本的开发正在进行中,新特性包括本地字幕引擎、英日语国际化以及暗色主题,还将修复已知bug和提示使用体验,预计将于本月之内发布。

+ ![](./assets/media/main.png) ## 📥 下载 @@ -50,7 +52,7 @@ npm install > #### 背景介绍 > > 如果你是开发者,想开发自定义字幕引擎,请查看[字幕引擎说明文档](./assets/engine-manual_zh.md)。 -> +> > 所谓的字幕引擎实际上是一个子程序,它会实时获取系统音频输入(录音)或输出(播放声音)的流式数据,并调用音频转文字的模型生成对应音频的字幕。生成的字幕通过 IPC 输出为转换为字符串的 JSON 数据,并返回给主程序。主程序读取字幕数据,处理后显示在窗口上。 > >目前项目默认使用[阿里云 Gummy 模型](https://help.aliyun.com/zh/model-studio/gummy-speech-recognition-translation/),需要获取阿里云百炼平台的 API KEY 并配置到环境变量中才能正常使用该模型。 diff --git a/README_en.md b/README_en.md index 9ad63e0..3edc398 100644 --- a/README_en.md +++ b/README_en.md @@ -8,6 +8,8 @@

+

The development of the new version is underway, featuring a local subtitle engine, English/Japanese internationalization, and a dark theme. It will also include fixes for known bugs and improvements to the user experience. It is expected to be released within this month.

+ ![](./assets/media/main.png) ## ⚠️ Attention @@ -110,4 +112,4 @@ npm run build:win npm run build:mac # For Linux npm run build:linux -``` \ No newline at end of file +``` diff --git a/package-lock.json b/package-lock.json index f7dce7d..a1a32aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,26 @@ { "name": "auto-caption", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "auto-caption", - "version": "0.0.1", + "version": "0.1.0", "hasInstallScript": true, "dependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", "ant-design-vue": "^4.2.6", "pinia": "^3.0.2", - "vue-router": "^4.5.1", - "ws": "^8.18.2" + "vue-i18n": "^11.1.9", + "vue-router": "^4.5.1" }, "devDependencies": { "@electron-toolkit/eslint-config-prettier": "3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", "@electron-toolkit/tsconfig": "^1.0.1", "@types/node": "^22.14.1", - "@types/ws": "^8.18.1", "@vitejs/plugin-vue": "^5.2.3", "electron": "^35.1.5", "electron-builder": "^25.1.8", @@ -1492,6 +1491,50 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@intlify/core-base": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.9.tgz", + "integrity": "sha512-Lrdi4wp3XnGhWmB/mMD/XtfGUw1Jt+PGpZI/M63X1ZqhTDjNHRVCs/i8vv8U1cwaj1A9fb0bkCQHLSL0SK+pIQ==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "11.1.9", + "@intlify/shared": "11.1.9" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-11.1.9.tgz", + "integrity": "sha512-84SNs3Ikjg0rD1bOuchzb3iK1vR2/8nxrkyccIl5DjFTeMzE/Fxv6X+A7RN5ZXjEWelc1p5D4kHA6HEOhlKL5Q==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.1.9", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-11.1.9.tgz", + "integrity": "sha512-H/83xgU1l8ox+qG305p6ucmoy93qyjIPnvxGWRA7YdOoHe1tIiW9IlEu4lTdsOR7cfP1ecrwyflQSqXdXBacXA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2281,16 +2324,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -9430,6 +9463,32 @@ "node": ">=10" } }, + "node_modules/vue-i18n": { + "version": "11.1.9", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-11.1.9.tgz", + "integrity": "sha512-N9ZTsXdRmX38AwS9F6Rh93RtPkvZTkSy/zNv63FTIwZCUbLwwrpqlKz9YQuzFLdlvRdZTnWAUE5jMxr8exdl7g==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.1.9", + "@intlify/shared": "11.1.9", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/vue-router": { "version": "4.5.1", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", @@ -9581,27 +9640,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/package.json b/package.json index dda2ca1..421fa50 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@electron-toolkit/utils": "^4.0.0", "ant-design-vue": "^4.2.6", "pinia": "^3.0.2", + "vue-i18n": "^11.1.9", "vue-router": "^4.5.1" }, "devDependencies": { diff --git a/src/main/i18n/index.ts b/src/main/i18n/index.ts new file mode 100644 index 0000000..7ba61bb --- /dev/null +++ b/src/main/i18n/index.ts @@ -0,0 +1,11 @@ +import zh from './lang/zh' +import en from './lang/en' +import ja from './lang/ja' +import { allConfig } from '../utils/AllConfig' + +export function i18n(key: string): string{ + if(allConfig.uiLanguage === 'zh') return zh[key] || key + else if(allConfig.uiLanguage === 'en') return en[key] || key + else if(allConfig.uiLanguage === 'ja') return ja[key] || key + else return key +} diff --git a/src/main/i18n/lang/en.ts b/src/main/i18n/lang/en.ts new file mode 100644 index 0000000..87a969e --- /dev/null +++ b/src/main/i18n/lang/en.ts @@ -0,0 +1,8 @@ +export default { + "gummy.env.missing": "DASHSCOPE_API_KEY environment variable not detected. To use the gummy engine, you need to obtain an API Key from Alibaba Cloud's Bailian platform and add it to your local environment variables.", + "platform.unsupported": "Unsupported platform: ", + "engine.start.error": "Caption engine failed to start: ", + "engine.output.parse.error": "Unable to parse caption engine output as a JSON object: ", + "engine.error": "Caption engine error: ", + "engine.shutdown.error": "Failed to shut down the caption engine process: " +} diff --git a/src/main/i18n/lang/ja.ts b/src/main/i18n/lang/ja.ts new file mode 100644 index 0000000..13122bc --- /dev/null +++ b/src/main/i18n/lang/ja.ts @@ -0,0 +1,8 @@ +export default { + "gummy.env.missing": "DASHSCOPE_API_KEY 環境変数が検出されませんでした。Gummy エンジンを使用するには、Alibaba Cloud の百煉プラットフォームから API Key を取得し、ローカル環境変数に追加する必要があります。", + "platform.unsupported": "サポートされていないプラットフォーム: ", + "engine.start.error": "字幕エンジンの起動に失敗しました: ", + "engine.output.parse.error": "字幕エンジンの出力を JSON オブジェクトとして解析できませんでした: ", + "engine.error": "字幕エンジンエラー: ", + "engine.shutdown.error": "字幕エンジンプロセスの終了に失敗しました: " +} diff --git a/src/main/i18n/lang/zh.ts b/src/main/i18n/lang/zh.ts new file mode 100644 index 0000000..608c6d0 --- /dev/null +++ b/src/main/i18n/lang/zh.ts @@ -0,0 +1,8 @@ +export default { + "gummy.env.missing": "没有检测到 DASHSCOPE_API_KEY 环境变量,如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API Key 并添加到本机环境变量", + "platform.unsupported": "不支持的平台:", + "engine.start.error": "字幕引擎启动失败:", + "engine.output.parse.error": "字幕引擎输出内容无法解析为 JSON 对象:", + "engine.error": "字幕引擎错误:", + "engine.shutdown.error": "字幕引擎进程关闭失败:" +} diff --git a/src/main/utils/AllConfig.ts b/src/main/utils/AllConfig.ts index a67b74d..202c7ec 100644 --- a/src/main/utils/AllConfig.ts +++ b/src/main/utils/AllConfig.ts @@ -1,4 +1,4 @@ -import { Styles, CaptionItem, Controls } from '../types' +import { UILanguage, Styles, CaptionItem, Controls } from '../types' import { app, BrowserWindow } from 'electron' import * as path from 'path' import * as fs from 'fs' @@ -29,6 +29,7 @@ const defaultControls: Controls = { class AllConfig { + uiLanguage: UILanguage = 'ja' styles: Styles = {...defaultStyles}; controls: Controls = {...defaultControls}; captionLog: CaptionItem[] = []; @@ -39,14 +40,16 @@ class AllConfig { const configPath = path.join(app.getPath('userData'), 'config.json') if(fs.existsSync(configPath)){ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')) - this.setStyles(config.styles) - this.setControls(config.controls) + if(config.uiLanguage) this.uiLanguage = config.uiLanguage + if(config.styles) this.setStyles(config.styles) + if(config.controls) this.setControls(config.controls) console.log('[INFO] Read Config from:', configPath) } } public writeConfig() { const config = { + uiLanguage: this.uiLanguage, controls: this.controls, styles: this.styles } diff --git a/src/main/utils/CaptionEngine.ts b/src/main/utils/CaptionEngine.ts index b3c9001..30b262a 100644 --- a/src/main/utils/CaptionEngine.ts +++ b/src/main/utils/CaptionEngine.ts @@ -4,6 +4,7 @@ import { is } from '@electron-toolkit/utils' import path from 'path' import { controlWindow } from '../ControlWindow' import { allConfig } from './AllConfig' +import { i18n } from '../i18n' export class CaptionEngine { appPath: string = '' @@ -18,7 +19,7 @@ export class CaptionEngine { else if (allConfig.controls.engine === 'gummy') { allConfig.controls.customized = false if(!process.env.DASHSCOPE_API_KEY) { - controlWindow.sendErrorMessage('没有检测到 DASHSCOPE_API_KEY 环境变量,如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API Key 并添加到本机环境变量') + controlWindow.sendErrorMessage(i18n('gummy.env.missing')) return false } let gummyName = '' @@ -29,8 +30,8 @@ export class CaptionEngine { gummyName = 'main-gummy' } else { - controlWindow.sendErrorMessage('Unsupported platform: ' + process.platform) - throw new Error('Unsupported platform') + controlWindow.sendErrorMessage(i18n('platform.unsupported') + process.platform) + throw new Error(i18n('platform.unsupported')) } if (is.dev) { this.appPath = path.join( @@ -66,7 +67,7 @@ export class CaptionEngine { this.process = spawn(this.appPath, this.command) } catch (e) { - controlWindow.sendErrorMessage('字幕引擎启动失败:' + e) + controlWindow.sendErrorMessage(i18n('engine.start.error') + e) console.error('[ERROR] Error starting subprocess:', e) return } @@ -80,7 +81,7 @@ export class CaptionEngine { controlWindow.window.webContents.send('control.engine.started') } - this.process.stdout.on('data', (data) => { + this.process.stdout.on('data', (data: any) => { const lines = data.toString().split('\n'); lines.forEach((line: string) => { if (line.trim()) { @@ -88,7 +89,7 @@ export class CaptionEngine { const caption = JSON.parse(line); allConfig.updateCaptionLog(caption); } catch (e) { - controlWindow.sendErrorMessage('字幕引擎输出内容无法解析为 JSON 对象:' + e) + controlWindow.sendErrorMessage(i18n('engine.output.parse.error') + e) console.error('[ERROR] Error parsing JSON:', e); } } @@ -96,7 +97,7 @@ export class CaptionEngine { }); this.process.stderr.on('data', (data) => { - controlWindow.sendErrorMessage('字幕引擎错误:' + data) + controlWindow.sendErrorMessage(i18n('engine.error') + data) console.error(`[ERROR] Subprocess Error: ${data}`); }); @@ -115,7 +116,7 @@ export class CaptionEngine { if (process.platform === "win32" && this.process.pid) { exec(`taskkill /pid ${this.process.pid} /t /f`, (error) => { if (error) { - controlWindow.sendErrorMessage('字幕引擎进程关闭失败:' + error) + controlWindow.sendErrorMessage(i18n('engine.shutdown.error') + error) console.error(`[ERROR] Failed to kill process: ${error}`) } }); diff --git a/src/renderer/src/assets/input.css b/src/renderer/src/assets/input.css new file mode 100644 index 0000000..534c7b7 --- /dev/null +++ b/src/renderer/src/assets/input.css @@ -0,0 +1,22 @@ +.input-item { + margin: 10px 0; +} + +.input-label { + display: inline-block; + width: 80px; + text-align: right; + margin-right: 10px; +} + +.input-area { + width: calc(100% - 100px); + min-width: 100px; +} + +.input-item-value { + width: 80px; + text-align: right; + font-size: 12px; + color: #666 +} diff --git a/src/renderer/src/components/CaptionStyle.vue b/src/renderer/src/components/CaptionStyle.vue index 8d202b3..81226ad 100644 --- a/src/renderer/src/components/CaptionStyle.vue +++ b/src/renderer/src/components/CaptionStyle.vue @@ -5,57 +5,57 @@ 取消更改 | 恢复默认 -
- 字体族 +
+ 字体族 + />
-
- 字体颜色 +
+ 字体颜色 -
{{ currentFontColor }}
+
{{ currentFontColor }}
-
- 字体大小 +
+ 字体大小 -
{{ currentFontSize }}px
+ /> +
{{ currentFontSize }}px
-
- 背景颜色 +
+ 背景颜色 -
{{ currentBackground }}
+
{{ currentBackground }}
-
- 背景透明度 +
+ 背景透明度 -
{{ currentOpacity }}
+
{{ currentOpacity }}
- -
- 显示预览 + +
+ 显示预览 - 显示翻译 + 显示翻译
@@ -64,31 +64,31 @@ -
- 翻译字体 +
+ 翻译字体 + />
-
- 翻译颜色 +
+ 翻译颜色 -
{{ currentTransFontColor }}
+
{{ currentTransFontColor }}
-
- 翻译大小 +
+ 翻译大小 -
{{ currentTransFontSize }}px
+ /> +
{{ currentTransFontSize }}px
@@ -115,10 +115,10 @@ :style="{ fontFamily: currentTransFontFamily, fontSize: currentTransFontSize + 'px', - color: currentTransFontColor + color: currentTransFontColor }" >这是字幕样式预览(翻译)

-
+
@@ -154,7 +154,7 @@ function useSameStyle(){ currentTransFontColor.value = currentFontColor.value; } -function applyStyle(){ +function applyStyle(){ captionStyle.fontFamily = currentFontFamily.value; captionStyle.fontSize = currentFontSize.value; captionStyle.fontColor = currentFontColor.value; @@ -182,7 +182,7 @@ function backStyle(){ currentTransFontColor.value = captionStyle.transFontColor; } -function resetStyle() { +function resetStyle() { captionStyle.sendStyleReset(); } @@ -195,33 +195,7 @@ watch(changeSignal, (val) => { \ No newline at end of file + diff --git a/src/renderer/src/components/EngineControl.vue b/src/renderer/src/components/EngineControl.vue index 9f5b2a8..f191c85 100644 --- a/src/renderer/src/components/EngineControl.vue +++ b/src/renderer/src/components/EngineControl.vue @@ -5,58 +5,58 @@ 更改设置 | 取消更改 -
- 源语言 +
+ 源语言
-
- 翻译语言 +
+ 翻译语言
-
- 字幕引擎 +
+ 字幕引擎
-
- 音频选择 +
+ 音频选择
-
- 启用翻译 +
+ 启用翻译 - 自定义引擎 + 自定义引擎

说明:允许用户使用自定义字幕引擎提供字幕。提供的引擎要能通过 child_process.spawn() 进行启动,且需要通过 IPC 与项目 node.js 后端进行通信。具体通信接口见后端实现。

-
- 引擎路径 +
+ 引擎路径
-
- 引擎指令 +
+ 引擎指令
@@ -134,32 +134,11 @@ watch(changeSignal, (val) => { diff --git a/src/renderer/src/components/EngineStatus.vue b/src/renderer/src/components/EngineStatus.vue index 4b9ee2b..bb6e592 100644 --- a/src/renderer/src/components/EngineStatus.vue +++ b/src/renderer/src/components/EngineStatus.vue @@ -10,6 +10,10 @@ + +
关于本项目
+ +
@@ -28,12 +32,45 @@ @click="stopEngine" >关闭字幕引擎
+ + +
+

Auto Caption 项目

+

一个跨平台的实时字幕显示软件。

+ +
+

作者:HiMeditator

+

版本:v0.1.0

+

+ 项目地址: + + GitHub | auto-caption + +

+

+ 用户手册: + + GitHub | user-manual_zh.md + +

+
+
2026 年 6 月 26 日
+
+