mirror of
https://github.com/HiMeditator/auto-caption.git
synced 2026-02-04 12:24:42 +08:00
feat(i18n): 后端添加国际化支持、优化前端界面
- 后端添加并实现国际化支持 - 前端引入 vue-i18n 模块(尚未添加国际化逻辑) - 优化用户界面样式,统一输入框和标签样式
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="color:red;text-align:center;">新版本的开发正在进行中,新特性包括本地字幕引擎、英日语国际化以及暗色主题,还将修复已知bug和提示使用体验,预计将于本月之内发布。</p>
|
||||
|
||||

|
||||
|
||||
## 📥 下载
|
||||
@@ -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 并配置到环境变量中才能正常使用该模型。
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="color:red;text-align:center;">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.</p>
|
||||
|
||||

|
||||
|
||||
## ⚠️ Attention
|
||||
@@ -110,4 +112,4 @@ npm run build:win
|
||||
npm run build:mac
|
||||
# For Linux
|
||||
npm run build:linux
|
||||
```
|
||||
```
|
||||
|
||||
110
package-lock.json
generated
110
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
11
src/main/i18n/index.ts
Normal file
11
src/main/i18n/index.ts
Normal file
@@ -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
|
||||
}
|
||||
8
src/main/i18n/lang/en.ts
Normal file
8
src/main/i18n/lang/en.ts
Normal file
@@ -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: "
|
||||
}
|
||||
8
src/main/i18n/lang/ja.ts
Normal file
8
src/main/i18n/lang/ja.ts
Normal file
@@ -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": "字幕エンジンプロセスの終了に失敗しました: "
|
||||
}
|
||||
8
src/main/i18n/lang/zh.ts
Normal file
8
src/main/i18n/lang/zh.ts
Normal file
@@ -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": "字幕引擎进程关闭失败:"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
});
|
||||
|
||||
22
src/renderer/src/assets/input.css
Normal file
22
src/renderer/src/assets/input.css
Normal file
@@ -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
|
||||
}
|
||||
@@ -5,57 +5,57 @@
|
||||
<a @click="backStyle">取消更改</a> |
|
||||
<a @click="resetStyle">恢复默认</a>
|
||||
</template>
|
||||
<div class="style-item">
|
||||
<span class="style-label">字体族</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">字体族</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
v-model:value="currentFontFamily"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">字体颜色</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">字体颜色</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="color"
|
||||
v-model:value="currentFontColor"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentFontColor }}</div>
|
||||
<div class="input-item-value">{{ currentFontColor }}</div>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">字体大小</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">字体大小</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="range"
|
||||
min="0" max="64"
|
||||
v-model:value="currentFontSize"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentFontSize }}px</div>
|
||||
/>
|
||||
<div class="input-item-value">{{ currentFontSize }}px</div>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">背景颜色</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">背景颜色</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="color"
|
||||
v-model:value="currentBackground"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentBackground }}</div>
|
||||
<div class="input-item-value">{{ currentBackground }}</div>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">背景透明度</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">背景透明度</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
v-model:value="currentOpacity"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentOpacity }}</div>
|
||||
<div class="input-item-value">{{ currentOpacity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="style-item">
|
||||
<span class="style-label">显示预览</span>
|
||||
|
||||
<div class="input-item">
|
||||
<span class="input-label">显示预览</span>
|
||||
<a-switch v-model:checked="displayPreview" />
|
||||
<span class="style-label">显示翻译</span>
|
||||
<span class="input-label">显示翻译</span>
|
||||
<a-switch v-model:checked="currentTransDisplay" />
|
||||
</div>
|
||||
|
||||
@@ -64,31 +64,31 @@
|
||||
<template #extra>
|
||||
<a @click="useSameStyle">使用相同样式</a>
|
||||
</template>
|
||||
<div class="style-item">
|
||||
<span class="style-label">翻译字体</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">翻译字体</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
v-model:value="currentTransFontFamily"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">翻译颜色</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">翻译颜色</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="color"
|
||||
v-model:value="currentTransFontColor"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentTransFontColor }}</div>
|
||||
<div class="input-item-value">{{ currentTransFontColor }}</div>
|
||||
</div>
|
||||
<div class="style-item">
|
||||
<span class="style-label">翻译大小</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">翻译大小</span>
|
||||
<a-input
|
||||
class="style-input"
|
||||
class="input-area"
|
||||
type="range"
|
||||
min="0" max="64"
|
||||
v-model:value="currentTransFontSize"
|
||||
/>
|
||||
<div class="style-item-value">{{ currentTransFontSize }}px</div>
|
||||
/>
|
||||
<div class="input-item-value">{{ currentTransFontSize }}px</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
@@ -115,10 +115,10 @@
|
||||
:style="{
|
||||
fontFamily: currentTransFontFamily,
|
||||
fontSize: currentTransFontSize + 'px',
|
||||
color: currentTransFontColor
|
||||
color: currentTransFontColor
|
||||
}"
|
||||
>这是字幕样式预览(翻译)</p>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
</template>
|
||||
@@ -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) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.caption-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.style-item {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.style-label {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.style-input {
|
||||
width: calc(100% - 100px);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.style-item-value {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666
|
||||
}
|
||||
@import url(../assets/input.css);
|
||||
|
||||
.preview-container {
|
||||
line-height: 2em;
|
||||
@@ -239,4 +213,4 @@ watch(changeSignal, (val) => {
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -5,58 +5,58 @@
|
||||
<a @click="applyChange">更改设置</a> |
|
||||
<a @click="cancelChange">取消更改</a>
|
||||
</template>
|
||||
<div class="control-item">
|
||||
<span class="control-label">源语言</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">源语言</span>
|
||||
<a-select
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentSourceLang"
|
||||
:options="langList"
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="control-item">
|
||||
<span class="control-label">翻译语言</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">翻译语言</span>
|
||||
<a-select
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentTargetLang"
|
||||
:options="langList.filter((item) => item.value !== 'auto')"
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="control-item">
|
||||
<span class="control-label">字幕引擎</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">字幕引擎</span>
|
||||
<a-select
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentEngine"
|
||||
:options="captionEngine"
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="control-item">
|
||||
<span class="control-label">音频选择</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">音频选择</span>
|
||||
<a-select
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentAudio"
|
||||
:options="audioType"
|
||||
></a-select>
|
||||
</div>
|
||||
<div class="control-item">
|
||||
<span class="control-label">启用翻译</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">启用翻译</span>
|
||||
<a-switch v-model:checked="currentTranslation" />
|
||||
<span class="control-label">自定义引擎</span>
|
||||
<span class="input-label">自定义引擎</span>
|
||||
<a-switch v-model:checked="currentCustomized" />
|
||||
</div>
|
||||
<div v-show="currentCustomized">
|
||||
<a-card size="small" title="自定义字幕引擎">
|
||||
<p class="customize-note">说明:允许用户使用自定义字幕引擎提供字幕。提供的引擎要能通过 <code>child_process.spawn()</code> 进行启动,且需要通过 IPC 与项目 node.js 后端进行通信。具体通信接口见后端实现。</p>
|
||||
<div class="control-item">
|
||||
<span class="control-label">引擎路径</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">引擎路径</span>
|
||||
<a-input
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentCustomizedApp"
|
||||
></a-input>
|
||||
</div>
|
||||
<div class="control-item">
|
||||
<span class="control-label">引擎指令</span>
|
||||
<div class="input-item">
|
||||
<span class="input-label">引擎指令</span>
|
||||
<a-input
|
||||
class="control-input"
|
||||
class="input-area"
|
||||
v-model:value="currentCustomizedCommand"
|
||||
></a-input>
|
||||
</div>
|
||||
@@ -134,32 +134,11 @@ watch(changeSignal, (val) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.control-item {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@import url(../assets/input.css);
|
||||
|
||||
.customize-note {
|
||||
padding: 0 20px;
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.control-input {
|
||||
width: calc(100% - 100px);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.control-item-value {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
<a-col :span="6">
|
||||
<a-statistic title="已记录字幕" :value="captionData.length" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<div class="about-tag">关于本项目</div>
|
||||
<GithubOutlined class="proj-info" @click="showAbout = true"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
@@ -28,12 +32,45 @@
|
||||
@click="stopEngine"
|
||||
>关闭字幕引擎</a-button>
|
||||
</div>
|
||||
|
||||
<a-modal v-model:open="showAbout" title="关于本项目" :footer="null">
|
||||
<div class="about-modal-content">
|
||||
<h2 class="about-title">Auto Caption 项目</h2>
|
||||
<p class="about-desc">一个跨平台的实时字幕显示软件。</p>
|
||||
<a-divider />
|
||||
<div class="about-info">
|
||||
<p><b>作者:</b>HiMeditator</p>
|
||||
<p><b>版本:</b>v0.1.0</p>
|
||||
<p>
|
||||
<b>项目地址:</b>
|
||||
<a href="https://github.com/HiMeditator/auto-caption" target="_blank">
|
||||
GitHub | auto-caption
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<b>用户手册:</b>
|
||||
<a
|
||||
href="https://github.com/HiMeditator/auto-caption/blob/main/assets/user-manual_zh.md"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub | user-manual_zh.md
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="about-date">2026 年 6 月 26 日</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCaptionLogStore } from '@renderer/stores/captionLog'
|
||||
import { useEngineControlStore } from '@renderer/stores/engineControl'
|
||||
import { GithubOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const showAbout = ref(false)
|
||||
|
||||
const captionLog = useCaptionLogStore()
|
||||
const { captionData } = storeToRefs(captionLog)
|
||||
const engineControl = useEngineControlStore()
|
||||
@@ -53,6 +90,48 @@ function stopEngine() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about-tag {
|
||||
color: rgba(0,0,0,0.45);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.proj-info {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
.about-modal-content {
|
||||
text-align: center;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.about-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.about-desc {
|
||||
color: #666;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.about-info {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.about-date {
|
||||
margin-top: 1.5em;
|
||||
color: #aaa;
|
||||
font-size: 0.95em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.caption-control {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,53 +1,29 @@
|
||||
<template>
|
||||
<a-card size="small" title="页面宽度">
|
||||
<template #extra>
|
||||
<a-button type="link" @click="showAbout = true">关于本项目</a-button>
|
||||
</template>
|
||||
<a-card size="small" title="通用设置">
|
||||
<div>
|
||||
<a-input type="range" class="span-input" min="6" max="18" v-model:value="leftBarWidth" />
|
||||
<div class="input-item">
|
||||
<span class="input-label">边栏宽度</span>
|
||||
<a-input
|
||||
type="range" class="span-input"
|
||||
min="6" max="12" v-model:value="leftBarWidth"
|
||||
/>
|
||||
<div class="input-item-value">{{ (leftBarWidth * 100 / 24).toFixed(0) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-modal v-model:open="showAbout" title="关于本项目" :footer="null">
|
||||
<div class="about-modal-content">
|
||||
<h2 class="about-title">Auto Caption 项目</h2>
|
||||
<p class="about-desc">一个跨平台的实时字幕显示软件。</p>
|
||||
<a-divider />
|
||||
<div class="about-info">
|
||||
<p><b>作者:</b>HiMeditator</p>
|
||||
<p><b>版本:</b>v0.1.0</p>
|
||||
<p>
|
||||
<b>项目地址:</b>
|
||||
<a href="https://github.com/HiMeditator/auto-caption" target="_blank">
|
||||
GitHub | auto-caption
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<b>用户手册:</b>
|
||||
<a
|
||||
href="https://github.com/HiMeditator/auto-caption/blob/main/assets/user-manual_zh.md"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub | user-manual_zh.md
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="about-date">2026 年 6 月 26 日</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
|
||||
|
||||
const generalSettingStore = useGeneralSettingStore()
|
||||
const { leftBarWidth } = storeToRefs(generalSettingStore)
|
||||
const showAbout = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url(../assets/input.css);
|
||||
|
||||
.span-input {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
17
src/renderer/src/i18n/index.ts
Normal file
17
src/renderer/src/i18n/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
import zh from './lang/zh';
|
||||
import en from './lang/en';
|
||||
import ja from './lang/ja';
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'zh',
|
||||
messages: {
|
||||
zh,
|
||||
en,
|
||||
ja
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
3
src/renderer/src/i18n/lang/en.ts
Normal file
3
src/renderer/src/i18n/lang/en.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
|
||||
}
|
||||
3
src/renderer/src/i18n/lang/ja.ts
Normal file
3
src/renderer/src/i18n/lang/ja.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
|
||||
}
|
||||
8
src/renderer/src/i18n/lang/zh.ts
Normal file
8
src/renderer/src/i18n/lang/zh.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
example: {
|
||||
"original": "This is a preview of subtitle styles.",
|
||||
"translation": "这是字幕样式预览(翻译)"
|
||||
},
|
||||
general: {
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
import Antd from 'ant-design-vue';
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(Antd)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
color: captionStyle.fontColor
|
||||
}">
|
||||
<span v-if="captionData.length">{{ captionData[captionData.length-1].text }}</span>
|
||||
<span v-else>{{ "This is a preview of subtitle styles." }}</span>
|
||||
<span v-else>{{ $t('example.original') }}</span>
|
||||
</p>
|
||||
<p class="preview-translation" v-if="captionStyle.transDisplay" :style="{
|
||||
fontFamily: captionStyle.transFontFamily,
|
||||
@@ -34,7 +34,7 @@
|
||||
color: captionStyle.transFontColor
|
||||
}">
|
||||
<span v-if="captionData.length">{{ captionData[captionData.length-1].translation }}</span>
|
||||
<span v-else>{{ "这是字幕样式预览(翻译)" }}</span>
|
||||
<span v-else>{{ $t('example.translation') }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,34 +44,4 @@ const { leftBarWidth } = storeToRefs(generalSettingStore)
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.about-modal-content {
|
||||
text-align: center;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.about-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.about-desc {
|
||||
color: #666;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.about-info {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.about-date {
|
||||
margin-top: 1.5em;
|
||||
color: #aaa;
|
||||
font-size: 0.95em;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user