Skip to content

Commit 8ea9d06

Browse files
committed
重构i18n,支持全局多语言切换
1 parent 6c2733b commit 8ea9d06

28 files changed

+656
-351
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22
此项目的所有显著更改都将记录在此文件中。
33

4+
## [v1.1.0] - 2025-08-22
5+
### Added
6+
- Multi-Language Support
7+
### 添加
8+
- 多语言支持
9+
410
## [v1.0.1] - 2025-08-12
511
### Fixed
612
- 修复混剪片段与语音时长不一致问题

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@
4848
- 🎥 **自动剪辑**:支持多种视频格式,自动化批量处理视频剪辑任务
4949
- 🎙️ **语音合成**:将生成的文案转换为自然流畅的语音
5050
- 🎬 **字幕特效**:自动添加字幕和特效,提升视频质量
51+
- 📦 **批量处理**:支持批量任务,按预设自动持续合成视频
52+
- 🌐 **多语言支持**:支持中文、英文等多种语言,满足不同用户需求
5153
- 📦 **开箱即用**:无需复杂配置,用户可以快速上手
5254
- 📈 **持续更新**:定期发布新版本,修复bug并添加新功能
5355
- 🔒 **安全可靠**:完全本地本地化运行,确保用户数据安全
5456
- 🎨 **用户友好**:简洁直观的用户界面,易于操作
55-
- 🌐 **多平台支持**:支持Windows、macOS和Linux等多个操作系统
57+
- 💻 **多平台支持**:支持Windows、macOS和Linux等多个操作系统
5658

5759
<p align="right">(<a href="#readme-top">返回顶部</a>)</p>
5860

@@ -66,6 +68,9 @@
6668
- [x] 语音合成,支持EdgeTTS
6769
- [x] 视频剪辑,文案、视频、音频、字幕合成,自动混剪
6870
- [x] 批量处理,支持一个批量任务,按预设自动持续合成视频
71+
- [x] 多语言支持,能够支持中文、英文等多种语言
72+
- [ ] 更全面的参数调整
73+
- [ ] 更多的语音合成API
6974
- [ ] 字幕特效,支持多种字幕样式和特效
7075

7176

electron-builder.json5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
directories: {
88
output: 'release/${version}',
99
},
10-
files: ['dist', 'dist-electron', 'dist-native'],
10+
files: ['dist', 'dist-electron', 'dist-native', 'locales'],
1111
npmRebuild: false, // disable rebuild node_modules 使用包内自带预构建二进制,而不重新构建
1212
beforePack: './scripts/before-pack.js',
1313
mac: {

electron/electron-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ declare namespace NodeJS {
2424
// 在渲染器进程中使用,在 `preload.ts` 中暴露方法
2525
interface Window {
2626
ipcRenderer: Pick<import('electron').IpcRenderer, 'on' | 'once' | 'off' | 'send' | 'invoke'>
27+
i18n: {
28+
getLocalesPath: () => Promise<string>
29+
getLanguage: () => Promise<string>
30+
changeLanguage: (lng: string) => Promise<string>
31+
}
2732
electron: {
2833
isWinMaxed: () => Promise<boolean>
2934
winMin: () => void

electron/i18n/common-options.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { InitOptions } from 'i18next'
2+
3+
export const i18nLanguages = [
4+
{ code: 'en', name: 'English' },
5+
{ code: 'zh-CN', name: '简体中文' },
6+
]
7+
8+
export const i18nCommonOptions: InitOptions = {
9+
fallbackLng: i18nLanguages[0].code,
10+
supportedLngs: i18nLanguages.map((l) => l.code),
11+
load: 'currentOnly',
12+
ns: ['common'],
13+
defaultNS: 'common',
14+
interpolation: { escapeValue: false },
15+
}

electron/i18n/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import i18next from 'i18next'
2+
import Backend from 'i18next-fs-backend'
3+
import { app, BrowserWindow, ipcMain } from 'electron'
4+
import { fileURLToPath } from 'node:url'
5+
import path from 'node:path'
6+
import { i18nCommonOptions } from './common-options'
7+
8+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
9+
process.env.APP_ROOT = path.join(__dirname, '..')
10+
11+
const localesPath = path.join(process.env.APP_ROOT, 'locales/{{lng}}/{{ns}}.json')
12+
13+
export const initI18n = async () => {
14+
await i18next.use(Backend).init({
15+
// initAsync: false,
16+
// debug: true,
17+
...i18nCommonOptions,
18+
lng: app.getLocale(), // 获取系统语言
19+
backend: {
20+
loadPath: localesPath,
21+
},
22+
})
23+
24+
// 获取多语言文件路径
25+
ipcMain.handle('i18n-getLocalesPath', () => localesPath)
26+
27+
// 读取当前语言
28+
ipcMain.handle('i18n-getLanguage', () => i18next.language)
29+
30+
// 渲染进程切换语言
31+
ipcMain.handle('i18n-changeLanguage', async (_, lng: string) => {
32+
await changeAppLanguage(lng)
33+
return lng
34+
})
35+
}
36+
37+
export const changeAppLanguage = async (lng: string) => {
38+
await i18next.changeLanguage(lng)
39+
BrowserWindow.getAllWindows().forEach((win) => {
40+
win.webContents.send('i18n-changeLanguage', lng)
41+
})
42+
}

electron/ipc.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
2020
? path.join(process.env.APP_ROOT, 'public')
2121
: RENDERER_DIST
2222

23-
export default function initIPC(win: BrowserWindow) {
23+
export default function initIPC() {
2424
// sqlite 查询
2525
ipcMain.handle('sqlite-query', (_event, params) => sqQuery(params))
2626
// sqlite 插入
@@ -33,23 +33,27 @@ export default function initIPC(win: BrowserWindow) {
3333
ipcMain.handle('sqlite-bulk-insert-or-update', (_event, params) => sqBulkInsertOrUpdate(params))
3434

3535
// 是否最大化
36-
ipcMain.handle('is-win-maxed', () => {
36+
ipcMain.handle('is-win-maxed', (event) => {
37+
const win = BrowserWindow.fromWebContents(event.sender)
3738
return win?.isMaximized()
3839
})
3940
//最小化
40-
ipcMain.on('win-min', () => {
41+
ipcMain.on('win-min', (event) => {
42+
const win = BrowserWindow.fromWebContents(event.sender)
4143
win?.minimize()
4244
})
4345
//最大化
44-
ipcMain.on('win-max', () => {
46+
ipcMain.on('win-max', (event) => {
47+
const win = BrowserWindow.fromWebContents(event.sender)
4548
if (win?.isMaximized()) {
4649
win?.restore()
4750
} else {
4851
win?.maximize()
4952
}
5053
})
5154
//关闭程序
52-
ipcMain.on('win-close', () => {
55+
ipcMain.on('win-close', (event) => {
56+
const win = BrowserWindow.fromWebContents(event.sender)
5357
win?.close()
5458
})
5559

@@ -59,7 +63,12 @@ export default function initIPC(win: BrowserWindow) {
5963
})
6064

6165
// 选择文件夹
62-
ipcMain.handle('select-folder', async (_event, params?: SelectFolderParams) => {
66+
ipcMain.handle('select-folder', async (event, params?: SelectFolderParams) => {
67+
const win = BrowserWindow.fromWebContents(event.sender)
68+
if (!win) {
69+
throw new Error('无法获取窗口')
70+
}
71+
6372
const result = await dialog.showOpenDialog(win, {
6473
properties: ['openDirectory'],
6574
title: params?.title || '选择文件夹',

electron/lib/is-dev.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const isDev = !!process.env['VITE_DEV_SERVER_URL']

electron/lib/tools.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import fs from 'node:fs'
22
import path from 'node:path'
33
import { app } from 'electron'
44

5-
// import packageJson from '~/package.json'
6-
75
/**
86
* 生成有序的唯一文件名,用于处理文件已存在的情况
97
*/

electron/main.ts

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { app, BrowserWindow, screen, Menu } from 'electron'
22
import type { MenuItemConstructorOptions } from 'electron'
33
import { fileURLToPath } from 'node:url'
4+
import { isDev } from './lib/is-dev'
45
import path from 'node:path'
5-
import GlobalSetting from '../setting.global'
66
import initIPC from './ipc'
77
import { initSqlite } from './sqlite'
8+
import i18next from 'i18next'
9+
import { changeAppLanguage, initI18n } from './i18n'
10+
import { i18nLanguages } from './i18n/common-options'
811
import useCookieAllowCrossSite from './lib/cookie-allow-cross-site'
912

1013
// 用于引入 CommonJS 模块的方法
@@ -29,17 +32,14 @@ export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
2932
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
3033
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
3134

32-
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
33-
? path.join(process.env.APP_ROOT, 'public')
34-
: RENDERER_DIST
35+
process.env.VITE_PUBLIC = isDev ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
3536

3637
let win: BrowserWindow | null
3738

3839
function createWindow() {
3940
const { width, height } = screen.getPrimaryDisplay().workAreaSize
4041
win = new BrowserWindow({
4142
icon: path.join(process.env.VITE_PUBLIC, 'icon.png'),
42-
title: GlobalSetting.appName,
4343
width: Math.ceil(width * 0.8),
4444
height: Math.ceil(height * 0.8),
4545
minWidth: 800,
@@ -76,76 +76,63 @@ function buildMenu() {
7676
...(process.platform === 'darwin'
7777
? [
7878
{
79-
label: app.name,
79+
label: i18next.t('app.name'),
8080
submenu: [
81-
{ role: 'about' },
81+
{
82+
label: i18next.t('menu.app.about'),
83+
click: async () => {
84+
const { shell } = await import('electron')
85+
await shell.openExternal('https://github.com/YILS-LIN/short-video-factory')
86+
},
87+
},
8288
{ type: 'separator' },
83-
{ role: 'services' },
89+
{ label: i18next.t('menu.app.services'), role: 'services' },
8490
{ type: 'separator' },
85-
{ role: 'hide' },
86-
{ role: 'hideOthers' },
87-
{ role: 'unhide' },
91+
{ label: i18next.t('menu.app.hide'), role: 'hide' },
92+
{ label: i18next.t('menu.app.hideOthers'), role: 'hideOthers' },
93+
{ label: i18next.t('menu.app.unhide'), role: 'unhide' },
8894
{ type: 'separator' },
89-
{ role: 'quit' },
95+
{ label: i18next.t('menu.app.quit'), role: 'quit' },
9096
] as MenuItemConstructorOptions[],
9197
},
9298
]
9399
: []),
94100
{
95-
label: 'Language',
96-
submenu: [
97-
{
98-
label: 'English',
99-
type: 'radio',
100-
checked: true,
101-
click: () => {
102-
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send('set-locale', 'en'))
103-
},
101+
label: i18next.t('menu.language'),
102+
submenu: i18nLanguages.map((lng) => ({
103+
label: lng.name,
104+
type: 'radio',
105+
checked: i18next.language === lng.code,
106+
click: () => {
107+
changeAppLanguage(lng.code)
104108
},
105-
{
106-
label: '中文',
107-
type: 'radio',
108-
click: () => {
109-
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send('set-locale', 'zh-CN'))
110-
},
111-
},
112-
] as MenuItemConstructorOptions[],
109+
})) as MenuItemConstructorOptions[],
113110
},
114111
{
115-
label: 'Edit',
112+
label: i18next.t('menu.view.root'),
116113
submenu: [
117-
{ role: 'undo' },
118-
{ role: 'redo' },
114+
{ role: 'toggleDevTools', visible: false },
115+
{ label: i18next.t('menu.view.resetZoom'), role: 'resetZoom' },
116+
{ label: i18next.t('menu.view.zoomIn'), role: 'zoomIn' },
117+
{ label: i18next.t('menu.view.zoomOut'), role: 'zoomOut' },
119118
{ type: 'separator' },
120-
{ role: 'cut' },
121-
{ role: 'copy' },
122-
{ role: 'paste' },
123-
{ role: 'selectAll' },
119+
{ label: i18next.t('menu.view.toggleFullscreen'), role: 'togglefullscreen' },
124120
] as MenuItemConstructorOptions[],
125121
},
126122
{
127-
label: 'View',
123+
label: i18next.t('menu.window.root'),
124+
role: 'window',
128125
submenu: [
129-
{ role: 'reload' },
130-
{ role: 'forceReload' },
131-
{ role: 'toggleDevTools' },
132-
{ type: 'separator' },
133-
{ role: 'resetZoom' },
134-
{ role: 'zoomIn' },
135-
{ role: 'zoomOut' },
136-
{ type: 'separator' },
137-
{ role: 'togglefullscreen' },
126+
{ label: i18next.t('menu.window.minimize'), role: 'minimize' },
127+
{ label: i18next.t('menu.window.close'), role: 'close' },
138128
] as MenuItemConstructorOptions[],
139129
},
140130
{
141-
role: 'window',
142-
submenu: [{ role: 'minimize' }, { role: 'close' }] as MenuItemConstructorOptions[],
143-
},
144-
{
131+
label: i18next.t('menu.help.root'),
145132
role: 'help',
146133
submenu: [
147134
{
148-
label: 'Learn More',
135+
label: i18next.t('menu.help.learnMore'),
149136
click: async () => {
150137
const { shell } = await import('electron')
151138
await shell.openExternal('https://github.com/YILS-LIN/short-video-factory')
@@ -181,17 +168,19 @@ app.on('activate', () => {
181168
// app.disableHardwareAcceleration();
182169

183170
app.whenReady().then(() => {
184-
createWindow()
185171
initSqlite()
186-
initIPC(win as BrowserWindow)
172+
initI18n()
173+
initIPC()
174+
createWindow()
175+
176+
i18next.on('languageChanged', () => {
177+
buildMenu()
178+
})
187179

188180
// 允许跨站请求携带cookie
189181
useCookieAllowCrossSite()
190182
// 禁用 CORS
191183
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
192184
// 允许本地网络请求
193185
app.commandLine.appendSwitch('disable-features', 'BlockInsecurePrivateNetworkRequests')
194-
195-
// Build application menu
196-
buildMenu()
197186
})

0 commit comments

Comments
 (0)