feat(i18n): 后端添加国际化支持、优化前端界面

- 后端添加并实现国际化支持
- 前端引入 vue-i18n 模块(尚未添加国际化逻辑)
- 优化用户界面样式,统一输入框和标签样式
This commit is contained in:
himeditator
2025-07-03 20:36:09 +08:00
parent 3dcba07b6e
commit d608bf59c7
22 changed files with 344 additions and 229 deletions

View File

@@ -8,6 +8,8 @@
</p>
</div>
<p style="color:red;text-align:center;">新版本的开发正在进行中新特性包括本地字幕引擎、英日语国际化以及暗色主题还将修复已知bug和提示使用体验预计将于本月之内发布。</p>
![](./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 并配置到环境变量中才能正常使用该模型。

View File

@@ -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>
![](./assets/media/main.png)
## ⚠️ Attention
@@ -110,4 +112,4 @@ npm run build:win
npm run build:mac
# For Linux
npm run build:linux
```
```

110
package-lock.json generated
View File

@@ -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",

View File

@@ -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
View 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
View 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
View 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
View 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": "字幕引擎进程关闭失败:"
}

View File

@@ -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
}

View File

@@ -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}`)
}
});

View 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
}

View File

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

View File

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

View File

@@ -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;

View File

@@ -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;
}

View 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;

View File

@@ -0,0 +1,3 @@
export default {
}

View File

@@ -0,0 +1,3 @@
export default {
}

View File

@@ -0,0 +1,8 @@
export default {
example: {
"original": "This is a preview of subtitle styles.",
"translation": "这是字幕样式预览(翻译)"
},
general: {
}
}

View File

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

View File

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

View File

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