mirror of
https://github.com/YILS-LIN/short-video-factory.git
synced 2025-11-25 03:15:03 +08:00
重构i18n,支持全局多语言切换
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
此项目的所有显著更改都将记录在此文件中。
|
此项目的所有显著更改都将记录在此文件中。
|
||||||
|
|
||||||
|
## [v1.1.0] - 2025-08-22
|
||||||
|
### Added
|
||||||
|
- Multi-Language Support
|
||||||
|
### 添加
|
||||||
|
- 多语言支持
|
||||||
|
|
||||||
## [v1.0.1] - 2025-08-12
|
## [v1.0.1] - 2025-08-12
|
||||||
### Fixed
|
### Fixed
|
||||||
- 修复混剪片段与语音时长不一致问题
|
- 修复混剪片段与语音时长不一致问题
|
||||||
|
|||||||
@@ -48,11 +48,13 @@
|
|||||||
- 🎥 **自动剪辑**:支持多种视频格式,自动化批量处理视频剪辑任务
|
- 🎥 **自动剪辑**:支持多种视频格式,自动化批量处理视频剪辑任务
|
||||||
- 🎙️ **语音合成**:将生成的文案转换为自然流畅的语音
|
- 🎙️ **语音合成**:将生成的文案转换为自然流畅的语音
|
||||||
- 🎬 **字幕特效**:自动添加字幕和特效,提升视频质量
|
- 🎬 **字幕特效**:自动添加字幕和特效,提升视频质量
|
||||||
|
- 📦 **批量处理**:支持批量任务,按预设自动持续合成视频
|
||||||
|
- 🌐 **多语言支持**:支持中文、英文等多种语言,满足不同用户需求
|
||||||
- 📦 **开箱即用**:无需复杂配置,用户可以快速上手
|
- 📦 **开箱即用**:无需复杂配置,用户可以快速上手
|
||||||
- 📈 **持续更新**:定期发布新版本,修复bug并添加新功能
|
- 📈 **持续更新**:定期发布新版本,修复bug并添加新功能
|
||||||
- 🔒 **安全可靠**:完全本地本地化运行,确保用户数据安全
|
- 🔒 **安全可靠**:完全本地本地化运行,确保用户数据安全
|
||||||
- 🎨 **用户友好**:简洁直观的用户界面,易于操作
|
- 🎨 **用户友好**:简洁直观的用户界面,易于操作
|
||||||
- 🌐 **多平台支持**:支持Windows、macOS和Linux等多个操作系统
|
- 💻 **多平台支持**:支持Windows、macOS和Linux等多个操作系统
|
||||||
|
|
||||||
<p align="right">(<a href="#readme-top">返回顶部</a>)</p>
|
<p align="right">(<a href="#readme-top">返回顶部</a>)</p>
|
||||||
|
|
||||||
@@ -66,6 +68,9 @@
|
|||||||
- [x] 语音合成,支持EdgeTTS
|
- [x] 语音合成,支持EdgeTTS
|
||||||
- [x] 视频剪辑,文案、视频、音频、字幕合成,自动混剪
|
- [x] 视频剪辑,文案、视频、音频、字幕合成,自动混剪
|
||||||
- [x] 批量处理,支持一个批量任务,按预设自动持续合成视频
|
- [x] 批量处理,支持一个批量任务,按预设自动持续合成视频
|
||||||
|
- [x] 多语言支持,能够支持中文、英文等多种语言
|
||||||
|
- [ ] 更全面的参数调整
|
||||||
|
- [ ] 更多的语音合成API
|
||||||
- [ ] 字幕特效,支持多种字幕样式和特效
|
- [ ] 字幕特效,支持多种字幕样式和特效
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
directories: {
|
directories: {
|
||||||
output: 'release/${version}',
|
output: 'release/${version}',
|
||||||
},
|
},
|
||||||
files: ['dist', 'dist-electron', 'dist-native'],
|
files: ['dist', 'dist-electron', 'dist-native', 'locales'],
|
||||||
npmRebuild: false, // disable rebuild node_modules 使用包内自带预构建二进制,而不重新构建
|
npmRebuild: false, // disable rebuild node_modules 使用包内自带预构建二进制,而不重新构建
|
||||||
beforePack: './scripts/before-pack.js',
|
beforePack: './scripts/before-pack.js',
|
||||||
mac: {
|
mac: {
|
||||||
|
|||||||
5
electron/electron-env.d.ts
vendored
5
electron/electron-env.d.ts
vendored
@@ -24,6 +24,11 @@ declare namespace NodeJS {
|
|||||||
// 在渲染器进程中使用,在 `preload.ts` 中暴露方法
|
// 在渲染器进程中使用,在 `preload.ts` 中暴露方法
|
||||||
interface Window {
|
interface Window {
|
||||||
ipcRenderer: Pick<import('electron').IpcRenderer, 'on' | 'once' | 'off' | 'send' | 'invoke'>
|
ipcRenderer: Pick<import('electron').IpcRenderer, 'on' | 'once' | 'off' | 'send' | 'invoke'>
|
||||||
|
i18n: {
|
||||||
|
getLocalesPath: () => Promise<string>
|
||||||
|
getLanguage: () => Promise<string>
|
||||||
|
changeLanguage: (lng: string) => Promise<string>
|
||||||
|
}
|
||||||
electron: {
|
electron: {
|
||||||
isWinMaxed: () => Promise<boolean>
|
isWinMaxed: () => Promise<boolean>
|
||||||
winMin: () => void
|
winMin: () => void
|
||||||
|
|||||||
15
electron/i18n/common-options.ts
Normal file
15
electron/i18n/common-options.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { InitOptions } from 'i18next'
|
||||||
|
|
||||||
|
export const i18nLanguages = [
|
||||||
|
{ code: 'en', name: 'English' },
|
||||||
|
{ code: 'zh-CN', name: '简体中文' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const i18nCommonOptions: InitOptions = {
|
||||||
|
fallbackLng: i18nLanguages[0].code,
|
||||||
|
supportedLngs: i18nLanguages.map((l) => l.code),
|
||||||
|
load: 'currentOnly',
|
||||||
|
ns: ['common'],
|
||||||
|
defaultNS: 'common',
|
||||||
|
interpolation: { escapeValue: false },
|
||||||
|
}
|
||||||
42
electron/i18n/index.ts
Normal file
42
electron/i18n/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import i18next from 'i18next'
|
||||||
|
import Backend from 'i18next-fs-backend'
|
||||||
|
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { i18nCommonOptions } from './common-options'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
process.env.APP_ROOT = path.join(__dirname, '..')
|
||||||
|
|
||||||
|
const localesPath = path.join(process.env.APP_ROOT, 'locales/{{lng}}/{{ns}}.json')
|
||||||
|
|
||||||
|
export const initI18n = async () => {
|
||||||
|
await i18next.use(Backend).init({
|
||||||
|
// initAsync: false,
|
||||||
|
// debug: true,
|
||||||
|
...i18nCommonOptions,
|
||||||
|
lng: app.getLocale(), // 获取系统语言
|
||||||
|
backend: {
|
||||||
|
loadPath: localesPath,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取多语言文件路径
|
||||||
|
ipcMain.handle('i18n-getLocalesPath', () => localesPath)
|
||||||
|
|
||||||
|
// 读取当前语言
|
||||||
|
ipcMain.handle('i18n-getLanguage', () => i18next.language)
|
||||||
|
|
||||||
|
// 渲染进程切换语言
|
||||||
|
ipcMain.handle('i18n-changeLanguage', async (_, lng: string) => {
|
||||||
|
await changeAppLanguage(lng)
|
||||||
|
return lng
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changeAppLanguage = async (lng: string) => {
|
||||||
|
await i18next.changeLanguage(lng)
|
||||||
|
BrowserWindow.getAllWindows().forEach((win) => {
|
||||||
|
win.webContents.send('i18n-changeLanguage', lng)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
|
|||||||
? path.join(process.env.APP_ROOT, 'public')
|
? path.join(process.env.APP_ROOT, 'public')
|
||||||
: RENDERER_DIST
|
: RENDERER_DIST
|
||||||
|
|
||||||
export default function initIPC(win: BrowserWindow) {
|
export default function initIPC() {
|
||||||
// sqlite 查询
|
// sqlite 查询
|
||||||
ipcMain.handle('sqlite-query', (_event, params) => sqQuery(params))
|
ipcMain.handle('sqlite-query', (_event, params) => sqQuery(params))
|
||||||
// sqlite 插入
|
// sqlite 插入
|
||||||
@@ -33,15 +33,18 @@ export default function initIPC(win: BrowserWindow) {
|
|||||||
ipcMain.handle('sqlite-bulk-insert-or-update', (_event, params) => sqBulkInsertOrUpdate(params))
|
ipcMain.handle('sqlite-bulk-insert-or-update', (_event, params) => sqBulkInsertOrUpdate(params))
|
||||||
|
|
||||||
// 是否最大化
|
// 是否最大化
|
||||||
ipcMain.handle('is-win-maxed', () => {
|
ipcMain.handle('is-win-maxed', (event) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
return win?.isMaximized()
|
return win?.isMaximized()
|
||||||
})
|
})
|
||||||
//最小化
|
//最小化
|
||||||
ipcMain.on('win-min', () => {
|
ipcMain.on('win-min', (event) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
win?.minimize()
|
win?.minimize()
|
||||||
})
|
})
|
||||||
//最大化
|
//最大化
|
||||||
ipcMain.on('win-max', () => {
|
ipcMain.on('win-max', (event) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
if (win?.isMaximized()) {
|
if (win?.isMaximized()) {
|
||||||
win?.restore()
|
win?.restore()
|
||||||
} else {
|
} else {
|
||||||
@@ -49,7 +52,8 @@ export default function initIPC(win: BrowserWindow) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
//关闭程序
|
//关闭程序
|
||||||
ipcMain.on('win-close', () => {
|
ipcMain.on('win-close', (event) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
win?.close()
|
win?.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -59,7 +63,12 @@ export default function initIPC(win: BrowserWindow) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 选择文件夹
|
// 选择文件夹
|
||||||
ipcMain.handle('select-folder', async (_event, params?: SelectFolderParams) => {
|
ipcMain.handle('select-folder', async (event, params?: SelectFolderParams) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
if (!win) {
|
||||||
|
throw new Error('无法获取窗口')
|
||||||
|
}
|
||||||
|
|
||||||
const result = await dialog.showOpenDialog(win, {
|
const result = await dialog.showOpenDialog(win, {
|
||||||
properties: ['openDirectory'],
|
properties: ['openDirectory'],
|
||||||
title: params?.title || '选择文件夹',
|
title: params?.title || '选择文件夹',
|
||||||
|
|||||||
1
electron/lib/is-dev.ts
Normal file
1
electron/lib/is-dev.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const isDev = !!process.env['VITE_DEV_SERVER_URL']
|
||||||
@@ -2,8 +2,6 @@ import fs from 'node:fs'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
// import packageJson from '~/package.json'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成有序的唯一文件名,用于处理文件已存在的情况
|
* 生成有序的唯一文件名,用于处理文件已存在的情况
|
||||||
*/
|
*/
|
||||||
|
|||||||
103
electron/main.ts
103
electron/main.ts
@@ -1,10 +1,13 @@
|
|||||||
import { app, BrowserWindow, screen, Menu } from 'electron'
|
import { app, BrowserWindow, screen, Menu } from 'electron'
|
||||||
import type { MenuItemConstructorOptions } from 'electron'
|
import type { MenuItemConstructorOptions } from 'electron'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { isDev } from './lib/is-dev'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import GlobalSetting from '../setting.global'
|
|
||||||
import initIPC from './ipc'
|
import initIPC from './ipc'
|
||||||
import { initSqlite } from './sqlite'
|
import { initSqlite } from './sqlite'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import { changeAppLanguage, initI18n } from './i18n'
|
||||||
|
import { i18nLanguages } from './i18n/common-options'
|
||||||
import useCookieAllowCrossSite from './lib/cookie-allow-cross-site'
|
import useCookieAllowCrossSite from './lib/cookie-allow-cross-site'
|
||||||
|
|
||||||
// 用于引入 CommonJS 模块的方法
|
// 用于引入 CommonJS 模块的方法
|
||||||
@@ -29,9 +32,7 @@ export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
|
|||||||
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
|
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
|
||||||
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
|
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
|
||||||
|
|
||||||
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
|
process.env.VITE_PUBLIC = isDev ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
|
||||||
? path.join(process.env.APP_ROOT, 'public')
|
|
||||||
: RENDERER_DIST
|
|
||||||
|
|
||||||
let win: BrowserWindow | null
|
let win: BrowserWindow | null
|
||||||
|
|
||||||
@@ -39,7 +40,6 @@ function createWindow() {
|
|||||||
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize
|
||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
icon: path.join(process.env.VITE_PUBLIC, 'icon.png'),
|
icon: path.join(process.env.VITE_PUBLIC, 'icon.png'),
|
||||||
title: GlobalSetting.appName,
|
|
||||||
width: Math.ceil(width * 0.8),
|
width: Math.ceil(width * 0.8),
|
||||||
height: Math.ceil(height * 0.8),
|
height: Math.ceil(height * 0.8),
|
||||||
minWidth: 800,
|
minWidth: 800,
|
||||||
@@ -76,76 +76,63 @@ function buildMenu() {
|
|||||||
...(process.platform === 'darwin'
|
...(process.platform === 'darwin'
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: app.name,
|
label: i18next.t('app.name'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'about' },
|
{
|
||||||
|
label: i18next.t('menu.app.about'),
|
||||||
|
click: async () => {
|
||||||
|
const { shell } = await import('electron')
|
||||||
|
await shell.openExternal('https://github.com/YILS-LIN/short-video-factory')
|
||||||
|
},
|
||||||
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'services' },
|
{ label: i18next.t('menu.app.services'), role: 'services' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'hide' },
|
{ label: i18next.t('menu.app.hide'), role: 'hide' },
|
||||||
{ role: 'hideOthers' },
|
{ label: i18next.t('menu.app.hideOthers'), role: 'hideOthers' },
|
||||||
{ role: 'unhide' },
|
{ label: i18next.t('menu.app.unhide'), role: 'unhide' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'quit' },
|
{ label: i18next.t('menu.app.quit'), role: 'quit' },
|
||||||
] as MenuItemConstructorOptions[],
|
] as MenuItemConstructorOptions[],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
label: 'Language',
|
label: i18next.t('menu.language'),
|
||||||
submenu: [
|
submenu: i18nLanguages.map((lng) => ({
|
||||||
{
|
label: lng.name,
|
||||||
label: 'English',
|
type: 'radio',
|
||||||
type: 'radio',
|
checked: i18next.language === lng.code,
|
||||||
checked: true,
|
click: () => {
|
||||||
click: () => {
|
changeAppLanguage(lng.code)
|
||||||
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send('set-locale', 'en'))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
})) as MenuItemConstructorOptions[],
|
||||||
label: '中文',
|
|
||||||
type: 'radio',
|
|
||||||
click: () => {
|
|
||||||
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send('set-locale', 'zh-CN'))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as MenuItemConstructorOptions[],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: i18next.t('menu.view.root'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'undo' },
|
{ role: 'toggleDevTools', visible: false },
|
||||||
{ role: 'redo' },
|
{ label: i18next.t('menu.view.resetZoom'), role: 'resetZoom' },
|
||||||
|
{ label: i18next.t('menu.view.zoomIn'), role: 'zoomIn' },
|
||||||
|
{ label: i18next.t('menu.view.zoomOut'), role: 'zoomOut' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'cut' },
|
{ label: i18next.t('menu.view.toggleFullscreen'), role: 'togglefullscreen' },
|
||||||
{ role: 'copy' },
|
|
||||||
{ role: 'paste' },
|
|
||||||
{ role: 'selectAll' },
|
|
||||||
] as MenuItemConstructorOptions[],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'View',
|
|
||||||
submenu: [
|
|
||||||
{ role: 'reload' },
|
|
||||||
{ role: 'forceReload' },
|
|
||||||
{ role: 'toggleDevTools' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'resetZoom' },
|
|
||||||
{ role: 'zoomIn' },
|
|
||||||
{ role: 'zoomOut' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'togglefullscreen' },
|
|
||||||
] as MenuItemConstructorOptions[],
|
] as MenuItemConstructorOptions[],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
label: i18next.t('menu.window.root'),
|
||||||
role: 'window',
|
role: 'window',
|
||||||
submenu: [{ role: 'minimize' }, { role: 'close' }] as MenuItemConstructorOptions[],
|
submenu: [
|
||||||
|
{ label: i18next.t('menu.window.minimize'), role: 'minimize' },
|
||||||
|
{ label: i18next.t('menu.window.close'), role: 'close' },
|
||||||
|
] as MenuItemConstructorOptions[],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
label: i18next.t('menu.help.root'),
|
||||||
role: 'help',
|
role: 'help',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Learn More',
|
label: i18next.t('menu.help.learnMore'),
|
||||||
click: async () => {
|
click: async () => {
|
||||||
const { shell } = await import('electron')
|
const { shell } = await import('electron')
|
||||||
await shell.openExternal('https://github.com/YILS-LIN/short-video-factory')
|
await shell.openExternal('https://github.com/YILS-LIN/short-video-factory')
|
||||||
@@ -181,9 +168,14 @@ app.on('activate', () => {
|
|||||||
// app.disableHardwareAcceleration();
|
// app.disableHardwareAcceleration();
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createWindow()
|
|
||||||
initSqlite()
|
initSqlite()
|
||||||
initIPC(win as BrowserWindow)
|
initI18n()
|
||||||
|
initIPC()
|
||||||
|
createWindow()
|
||||||
|
|
||||||
|
i18next.on('languageChanged', () => {
|
||||||
|
buildMenu()
|
||||||
|
})
|
||||||
|
|
||||||
// 允许跨站请求携带cookie
|
// 允许跨站请求携带cookie
|
||||||
useCookieAllowCrossSite()
|
useCookieAllowCrossSite()
|
||||||
@@ -191,7 +183,4 @@ app.whenReady().then(() => {
|
|||||||
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
||||||
// 允许本地网络请求
|
// 允许本地网络请求
|
||||||
app.commandLine.appendSwitch('disable-features', 'BlockInsecurePrivateNetworkRequests')
|
app.commandLine.appendSwitch('disable-features', 'BlockInsecurePrivateNetworkRequests')
|
||||||
|
|
||||||
// Build application menu
|
|
||||||
buildMenu()
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('i18n', {
|
||||||
|
getLocalesPath: () => ipcRenderer.invoke('i18n-getLocalesPath'),
|
||||||
|
getLanguage: () => ipcRenderer.invoke('i18n-getLanguage'),
|
||||||
|
changeLanguage: (lng: string) => ipcRenderer.invoke('i18n-changeLanguage', lng),
|
||||||
|
})
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
isWinMaxed: () => ipcRenderer.invoke('is-win-maxed'),
|
isWinMaxed: () => ipcRenderer.invoke('is-win-maxed'),
|
||||||
winMin: () => ipcRenderer.send('win-min'),
|
winMin: () => ipcRenderer.send('win-min'),
|
||||||
|
|||||||
142
locales/en/common.json
Normal file
142
locales/en/common.json
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "AI Short Video Factory"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"app": {
|
||||||
|
"about": "About",
|
||||||
|
"services": "Services",
|
||||||
|
"hide": "Hide",
|
||||||
|
"hideOthers": "Hide Others",
|
||||||
|
"unhide": "Unhide",
|
||||||
|
"quit": "Quit"
|
||||||
|
},
|
||||||
|
"language": "Language",
|
||||||
|
"view": {
|
||||||
|
"root": "View",
|
||||||
|
"resetZoom": "Reset Zoom",
|
||||||
|
"zoomIn": "Zoom In",
|
||||||
|
"zoomOut": "Zoom Out",
|
||||||
|
"toggleFullscreen": "Toggle Full Screen"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"root": "Window",
|
||||||
|
"minimize": "Minimize",
|
||||||
|
"close": "Close"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"root": "Help",
|
||||||
|
"learnMore": "Learn More"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"label": "Prompt"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"generate": "Generate",
|
||||||
|
"stop": "Stop",
|
||||||
|
"config": "Configure",
|
||||||
|
"refreshAssets": "Refresh Assets"
|
||||||
|
},
|
||||||
|
"llm": {
|
||||||
|
"configTitle": "Configure LLM API",
|
||||||
|
"modelName": "Model Name",
|
||||||
|
"apiUrl": "API URL",
|
||||||
|
"apiKey": "API Key",
|
||||||
|
"compatibleNote": "Compatible with any OpenAI-compatible API",
|
||||||
|
"connectSuccess": "LLM connected successfully",
|
||||||
|
"connectFailedPrefix": "LLM connection failed, please check your configuration"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"close": "Close",
|
||||||
|
"test": "Test",
|
||||||
|
"save": "Save",
|
||||||
|
"select": "Select",
|
||||||
|
"noData": "No data"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"label": "Output Text (editable)"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"promptRequired": "Prompt cannot be empty",
|
||||||
|
"generateFailedPrefix": "Generation failed, please check LLM configuration",
|
||||||
|
"outputFileNameRequired": "Please set an output file name first",
|
||||||
|
"outputPathRequired": "Please set an export folder first",
|
||||||
|
"outputSizeRequired": "Please set output resolution (width x height) first",
|
||||||
|
"bgmListFailed": "Failed to load BGM list. Please check the folder exists",
|
||||||
|
"ttsFailedCorrupt": "TTS failed: audio file is corrupted",
|
||||||
|
"ttsZeroDuration": "TTS duration is 0s. Check TTS config and network connectivity",
|
||||||
|
"renderFailedPrefix": "Video rendering failed. Please check all configurations",
|
||||||
|
"assetsDurationInsufficient": "Total assets duration is insufficient",
|
||||||
|
"edgeTtsListFailed": "Failed to fetch Edge TTS voice list. Please check your network",
|
||||||
|
"ttsConfigInvalid": "TTS configuration is invalid",
|
||||||
|
"ttsSynthesisFailed": "TTS synthesis failed"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"renderSuccess": "Video rendered successfully"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"batchNext": "Start next render",
|
||||||
|
"renderCanceled": "Video rendering canceled"
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"noContent": "No content yet",
|
||||||
|
"hintSelectFolder": "Please choose a folder above with enough storyboard assets"
|
||||||
|
},
|
||||||
|
"videoManage": {
|
||||||
|
"assetsFolderLabel": "Storyboard assets folder",
|
||||||
|
"noMp4InFolder": "No MP4 video files found in the selected folder",
|
||||||
|
"emptyFolder": "The selected folder is empty",
|
||||||
|
"readSuccess": "Assets loaded successfully",
|
||||||
|
"readFailed": "Failed to read assets. Please check if the folder exists"
|
||||||
|
},
|
||||||
|
"dialogs": {
|
||||||
|
"selectAssetsFolderTitle": "Select storyboard assets folder",
|
||||||
|
"selectOutputFolderTitle": "Select video export folder",
|
||||||
|
"selectBgmFolderTitle": "Select background music folder",
|
||||||
|
"renderConfigTitle": "Configure render options"
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"status": {
|
||||||
|
"idle": "Idle, ready to render",
|
||||||
|
"generatingText": "Generating script with AI LLM",
|
||||||
|
"synthesizingSpeech": "Synthesizing speech with TTS",
|
||||||
|
"segmentingVideo": "Processing video segments",
|
||||||
|
"rendering": "Rendering video",
|
||||||
|
"success": "Rendered successfully, you can start the next one",
|
||||||
|
"failed": "Render failed, please try again"
|
||||||
|
},
|
||||||
|
"startRender": "Start Rendering",
|
||||||
|
"stopRender": "Stop Rendering",
|
||||||
|
"autoBatch": "Auto batch render",
|
||||||
|
"bgmFolderLabel": "Background music folder (.mp3, pick randomly)",
|
||||||
|
"output": {
|
||||||
|
"width": "Output width",
|
||||||
|
"height": "Output height",
|
||||||
|
"fileName": "Output file name",
|
||||||
|
"format": "Output format",
|
||||||
|
"folder": "Output folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tts": {
|
||||||
|
"language": "Language",
|
||||||
|
"gender": "Gender",
|
||||||
|
"voice": "Voice",
|
||||||
|
"speed": "Speed",
|
||||||
|
"tryText": "Try-listen text",
|
||||||
|
"tryListen": "Try listen",
|
||||||
|
"selectLanguageGenderFirst": "Please select language and gender first",
|
||||||
|
"selectVoiceWarning": "Please select a voice",
|
||||||
|
"tryTextEmptyWarning": "Try-listen text cannot be empty",
|
||||||
|
"playTryAudio": "Playing try-listen audio",
|
||||||
|
"trySynthesisFailedNetwork": "Failed to synthesize try-listen audio. Please check network",
|
||||||
|
"genderMale": "Male",
|
||||||
|
"genderFemale": "Female",
|
||||||
|
"speedSlow": "Slow",
|
||||||
|
"speedMedium": "Medium",
|
||||||
|
"speedFast": "Fast"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"poweredBy": "Powered by YILS (Blog: https://yils.blog)"
|
||||||
|
}
|
||||||
|
}
|
||||||
142
locales/zh-CN/common.json
Normal file
142
locales/zh-CN/common.json
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "AI Short Video Factory - 短视频工厂"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"app": {
|
||||||
|
"about": "关于",
|
||||||
|
"services": "服务",
|
||||||
|
"hide": "隐藏",
|
||||||
|
"hideOthers": "隐藏其他",
|
||||||
|
"unhide": "取消隐藏",
|
||||||
|
"quit": "退出"
|
||||||
|
},
|
||||||
|
"language": "语言",
|
||||||
|
"view": {
|
||||||
|
"root": "视图",
|
||||||
|
"resetZoom": "重置缩放",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"zoomOut": "缩小",
|
||||||
|
"toggleFullscreen": "切换全屏"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"root": "窗口",
|
||||||
|
"minimize": "最小化",
|
||||||
|
"close": "关闭"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"root": "帮助",
|
||||||
|
"learnMore": "了解更多"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"label": "提示词"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"generate": "生成",
|
||||||
|
"stop": "停止",
|
||||||
|
"config": "配置",
|
||||||
|
"refreshAssets": "刷新素材库"
|
||||||
|
},
|
||||||
|
"llm": {
|
||||||
|
"configTitle": "配置大语言模型接口",
|
||||||
|
"modelName": "模型名称",
|
||||||
|
"apiUrl": "API 地址",
|
||||||
|
"apiKey": "API Key",
|
||||||
|
"compatibleNote": "兼容任意 OpenAI 标准接口",
|
||||||
|
"connectSuccess": "大模型连接成功",
|
||||||
|
"connectFailedPrefix": "大模型连接失败,请检查配置是否正确"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"close": "关闭",
|
||||||
|
"test": "测试",
|
||||||
|
"save": "保存",
|
||||||
|
"select": "选择",
|
||||||
|
"noData": "无数据"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"label": "输出文案(可编辑)"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"promptRequired": "提示词不能为空",
|
||||||
|
"generateFailedPrefix": "生成失败,请检查大模型配置是否正确",
|
||||||
|
"outputFileNameRequired": "请先配置导出文件名",
|
||||||
|
"outputPathRequired": "请先配置导出文件夹",
|
||||||
|
"outputSizeRequired": "请先配置导出分辨率(宽高)",
|
||||||
|
"bgmListFailed": "获取背景音乐列表失败,请检查文件夹是否存在",
|
||||||
|
"ttsFailedCorrupt": "语音合成失败,音频文件损坏",
|
||||||
|
"ttsZeroDuration": "语音时长为0秒,检查TTS语音合成配置及网络连接是否正常",
|
||||||
|
"renderFailedPrefix": "视频合成失败,请检查各项配置是否正确",
|
||||||
|
"assetsDurationInsufficient": "素材总时长不足",
|
||||||
|
"edgeTtsListFailed": "获取EdgeTTS语音列表失败,请检查网络",
|
||||||
|
"ttsConfigInvalid": "TTS语音合成配置无效",
|
||||||
|
"ttsSynthesisFailed": "语音合成失败"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"renderSuccess": "视频合成成功"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"batchNext": "开始合成下一个",
|
||||||
|
"renderCanceled": "视频合成已终止"
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"noContent": "暂无内容",
|
||||||
|
"hintSelectFolder": "从上面选择一个包含足够分镜素材的文件夹"
|
||||||
|
},
|
||||||
|
"videoManage": {
|
||||||
|
"assetsFolderLabel": "分镜视频素材文件夹",
|
||||||
|
"noMp4InFolder": "选择的文件夹中不包含MP4视频文件",
|
||||||
|
"emptyFolder": "选择的文件夹为空",
|
||||||
|
"readSuccess": "素材读取成功",
|
||||||
|
"readFailed": "素材读取失败,请检查文件夹是否存在"
|
||||||
|
},
|
||||||
|
"dialogs": {
|
||||||
|
"selectAssetsFolderTitle": "选择分镜素材文件夹",
|
||||||
|
"selectOutputFolderTitle": "选择视频导出文件夹",
|
||||||
|
"selectBgmFolderTitle": "选择背景音乐文件夹",
|
||||||
|
"renderConfigTitle": "配置合成选项"
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"status": {
|
||||||
|
"idle": "空闲,可以开始合成",
|
||||||
|
"generatingText": "正在使用 AI 大模型生成文案",
|
||||||
|
"synthesizingSpeech": "正在使用 TTS 合成语音",
|
||||||
|
"segmentingVideo": "正在处理分镜素材",
|
||||||
|
"rendering": "正在渲染视频",
|
||||||
|
"success": "渲染成功,可以开始下一个",
|
||||||
|
"failed": "渲染失败,请重新尝试"
|
||||||
|
},
|
||||||
|
"startRender": "开始合成",
|
||||||
|
"stopRender": "停止合成",
|
||||||
|
"autoBatch": "自动批量合成",
|
||||||
|
"bgmFolderLabel": "背景音乐文件夹(.mp3格式,从中随机选取)",
|
||||||
|
"output": {
|
||||||
|
"width": "导出视频宽度",
|
||||||
|
"height": "导出视频高度",
|
||||||
|
"fileName": "导出文件名",
|
||||||
|
"format": "导出格式",
|
||||||
|
"folder": "导出文件夹"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tts": {
|
||||||
|
"language": "语言",
|
||||||
|
"gender": "性别",
|
||||||
|
"voice": "声音",
|
||||||
|
"speed": "语速",
|
||||||
|
"tryText": "试听文本",
|
||||||
|
"tryListen": "试听",
|
||||||
|
"selectLanguageGenderFirst": "请先选择语言和性别",
|
||||||
|
"selectVoiceWarning": "请选择一个声音",
|
||||||
|
"tryTextEmptyWarning": "试听文本不能为空",
|
||||||
|
"playTryAudio": "播放试听语音",
|
||||||
|
"trySynthesisFailedNetwork": "试听语音合成失败,请检查网络",
|
||||||
|
"genderMale": "男性",
|
||||||
|
"genderFemale": "女性",
|
||||||
|
"speedSlow": "慢",
|
||||||
|
"speedMedium": "中",
|
||||||
|
"speedFast": "快"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"poweredBy": "Powered by YILS(博客地址:https://yils.blog)"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "short-video-factory",
|
"name": "short-video-factory",
|
||||||
"description": "短视频工厂,一键生成产品营销与泛内容短视频,AI批量自动剪辑",
|
"description": "短视频工厂,一键生成产品营销与泛内容短视频,AI批量自动剪辑",
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "YILS",
|
"name": "YILS",
|
||||||
"developer": "YILS",
|
"developer": "YILS",
|
||||||
@@ -22,10 +22,11 @@
|
|||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"better-sqlite3": "9.6.0",
|
"better-sqlite3": "9.6.0",
|
||||||
"ffmpeg-static": "^5.2.0",
|
"ffmpeg-static": "^5.2.0",
|
||||||
|
"i18next": "^25.4.0",
|
||||||
|
"i18next-fs-backend": "^2.3.2",
|
||||||
"music-metadata": "^11.7.3",
|
"music-metadata": "^11.7.3",
|
||||||
"subtitle": "4.2.2-alpha.0",
|
"subtitle": "4.2.2-alpha.0",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3"
|
||||||
"vue-i18n": "^9.14.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/openai": "^1.3.23",
|
"@ai-sdk/openai": "^1.3.23",
|
||||||
@@ -40,6 +41,8 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"electron": "^22.3.27",
|
"electron": "^22.3.27",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"i18next-vue": "^5.3.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.4.1",
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
|
|||||||
137
pnpm-lock.yaml
generated
137
pnpm-lock.yaml
generated
@@ -17,15 +17,18 @@ importers:
|
|||||||
ffmpeg-static:
|
ffmpeg-static:
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
|
i18next:
|
||||||
|
specifier: ^25.4.0
|
||||||
|
version: 25.4.0(typescript@5.6.2)
|
||||||
|
i18next-fs-backend:
|
||||||
|
specifier: ^2.3.2
|
||||||
|
version: 2.3.2
|
||||||
music-metadata:
|
music-metadata:
|
||||||
specifier: ^11.7.3
|
specifier: ^11.7.3
|
||||||
version: 11.7.3
|
version: 11.7.3
|
||||||
subtitle:
|
subtitle:
|
||||||
specifier: 4.2.2-alpha.0
|
specifier: 4.2.2-alpha.0
|
||||||
version: 4.2.2-alpha.0
|
version: 4.2.2-alpha.0
|
||||||
vue-i18n:
|
|
||||||
specifier: ^9.14.0
|
|
||||||
version: 9.14.5(vue@3.5.17(typescript@5.6.2))
|
|
||||||
ws:
|
ws:
|
||||||
specifier: ^8.18.3
|
specifier: ^8.18.3
|
||||||
version: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
version: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||||
@@ -66,6 +69,12 @@ importers:
|
|||||||
electron-builder:
|
electron-builder:
|
||||||
specifier: ^24.13.3
|
specifier: ^24.13.3
|
||||||
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
version: 24.13.3(electron-builder-squirrel-windows@24.13.3)
|
||||||
|
i18next-http-backend:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2
|
||||||
|
i18next-vue:
|
||||||
|
specifier: ^5.3.0
|
||||||
|
version: 5.3.0(i18next@25.4.0(typescript@5.6.2))(vue@3.5.17(typescript@5.6.2))
|
||||||
mitt:
|
mitt:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
@@ -296,6 +305,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
|
'@babel/runtime@7.28.3':
|
||||||
|
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -500,18 +513,6 @@ packages:
|
|||||||
'@iconify/utils@2.3.0':
|
'@iconify/utils@2.3.0':
|
||||||
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
||||||
|
|
||||||
'@intlify/core-base@9.14.5':
|
|
||||||
resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==}
|
|
||||||
engines: {node: '>= 16'}
|
|
||||||
|
|
||||||
'@intlify/message-compiler@9.14.5':
|
|
||||||
resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==}
|
|
||||||
engines: {node: '>= 16'}
|
|
||||||
|
|
||||||
'@intlify/shared@9.14.5':
|
|
||||||
resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==}
|
|
||||||
engines: {node: '>= 16'}
|
|
||||||
|
|
||||||
'@isaacs/balanced-match@4.0.1':
|
'@isaacs/balanced-match@4.0.1':
|
||||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
@@ -1380,6 +1381,9 @@ packages:
|
|||||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
cross-fetch@4.0.0:
|
||||||
|
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -1824,6 +1828,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
i18next-fs-backend@2.3.2:
|
||||||
|
resolution: {integrity: sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==}
|
||||||
|
|
||||||
|
i18next-http-backend@3.0.2:
|
||||||
|
resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==}
|
||||||
|
|
||||||
|
i18next-vue@5.3.0:
|
||||||
|
resolution: {integrity: sha512-X5gYF1R9FadUdRyIze6p+mU4+kztIRWb1SYeoegB0eFBt/lDA++i0A235enWq5qdrRpWZIHlLV8gd/D5xakOsw==}
|
||||||
|
peerDependencies:
|
||||||
|
i18next: '>=23'
|
||||||
|
vue: ^3.4.38
|
||||||
|
|
||||||
|
i18next@25.4.0:
|
||||||
|
resolution: {integrity: sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: ^5
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
iconv-corefoundation@1.1.7:
|
iconv-corefoundation@1.1.7:
|
||||||
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
|
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
|
||||||
engines: {node: ^8.11.2 || >=10}
|
engines: {node: ^8.11.2 || >=10}
|
||||||
@@ -2152,6 +2176,15 @@ packages:
|
|||||||
node-fetch-native@1.6.6:
|
node-fetch-native@1.6.6:
|
||||||
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
|
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
|
||||||
|
|
||||||
|
node-fetch@2.7.0:
|
||||||
|
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||||
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-gyp-build@4.8.4:
|
node-gyp-build@4.8.4:
|
||||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2597,6 +2630,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
truncate-utf8-bytes@1.0.2:
|
truncate-utf8-bytes@1.0.2:
|
||||||
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
|
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
|
||||||
|
|
||||||
@@ -2769,12 +2805,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.4.37
|
vue: ^3.4.37
|
||||||
|
|
||||||
vue-i18n@9.14.5:
|
|
||||||
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
|
|
||||||
engines: {node: '>= 16'}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.0.0
|
|
||||||
|
|
||||||
vue-router@4.5.1:
|
vue-router@4.5.1:
|
||||||
resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
|
resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2815,6 +2845,12 @@ packages:
|
|||||||
webpack-plugin-vuetify:
|
webpack-plugin-vuetify:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1:
|
||||||
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -3104,6 +3140,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@babel/runtime@7.28.3': {}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
@@ -3283,18 +3321,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@intlify/core-base@9.14.5':
|
|
||||||
dependencies:
|
|
||||||
'@intlify/message-compiler': 9.14.5
|
|
||||||
'@intlify/shared': 9.14.5
|
|
||||||
|
|
||||||
'@intlify/message-compiler@9.14.5':
|
|
||||||
dependencies:
|
|
||||||
'@intlify/shared': 9.14.5
|
|
||||||
source-map-js: 1.2.1
|
|
||||||
|
|
||||||
'@intlify/shared@9.14.5': {}
|
|
||||||
|
|
||||||
'@isaacs/balanced-match@4.0.1': {}
|
'@isaacs/balanced-match@4.0.1': {}
|
||||||
|
|
||||||
'@isaacs/brace-expansion@5.0.0':
|
'@isaacs/brace-expansion@5.0.0':
|
||||||
@@ -4289,6 +4315,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
|
|
||||||
|
cross-fetch@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@@ -4822,6 +4854,25 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@8.0.1: {}
|
human-signals@8.0.1: {}
|
||||||
|
|
||||||
|
i18next-fs-backend@2.3.2: {}
|
||||||
|
|
||||||
|
i18next-http-backend@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
cross-fetch: 4.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
|
i18next-vue@5.3.0(i18next@25.4.0(typescript@5.6.2))(vue@3.5.17(typescript@5.6.2)):
|
||||||
|
dependencies:
|
||||||
|
i18next: 25.4.0(typescript@5.6.2)
|
||||||
|
vue: 3.5.17(typescript@5.6.2)
|
||||||
|
|
||||||
|
i18next@25.4.0(typescript@5.6.2):
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.3
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.6.2
|
||||||
|
|
||||||
iconv-corefoundation@1.1.7:
|
iconv-corefoundation@1.1.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
cli-truncate: 2.1.0
|
cli-truncate: 2.1.0
|
||||||
@@ -5100,6 +5151,10 @@ snapshots:
|
|||||||
|
|
||||||
node-fetch-native@1.6.6: {}
|
node-fetch-native@1.6.6: {}
|
||||||
|
|
||||||
|
node-fetch@2.7.0:
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: 5.0.0
|
||||||
|
|
||||||
node-gyp-build@4.8.4:
|
node-gyp-build@4.8.4:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -5571,6 +5626,8 @@ snapshots:
|
|||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
truncate-utf8-bytes@1.0.2:
|
truncate-utf8-bytes@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
utf8-byte-length: 1.0.5
|
utf8-byte-length: 1.0.5
|
||||||
@@ -5745,13 +5802,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.17(typescript@5.6.2)
|
vue: 3.5.17(typescript@5.6.2)
|
||||||
|
|
||||||
vue-i18n@9.14.5(vue@3.5.17(typescript@5.6.2)):
|
|
||||||
dependencies:
|
|
||||||
'@intlify/core-base': 9.14.5
|
|
||||||
'@intlify/shared': 9.14.5
|
|
||||||
'@vue/devtools-api': 6.6.4
|
|
||||||
vue: 3.5.17(typescript@5.6.2)
|
|
||||||
|
|
||||||
vue-router@4.5.1(vue@3.5.17(typescript@5.6.2)):
|
vue-router@4.5.1(vue@3.5.17(typescript@5.6.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-api': 6.6.4
|
'@vue/devtools-api': 6.6.4
|
||||||
@@ -5783,6 +5833,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.6.2
|
typescript: 5.6.2
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1: {}
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 0.0.3
|
||||||
|
webidl-conversions: 3.0.1
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export default {
|
|
||||||
appName: 'AI Short Video Factory - 短视频工厂',
|
|
||||||
}
|
|
||||||
200
src/i18n.ts
200
src/i18n.ts
@@ -1,200 +0,0 @@
|
|||||||
import { createI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
en: {
|
|
||||||
app: {
|
|
||||||
name: 'AI Short Video Factory',
|
|
||||||
},
|
|
||||||
prompt: { label: 'Prompt' },
|
|
||||||
actions: { generate: 'Generate', stop: 'Stop', config: 'Configure', refreshAssets: 'Refresh Assets' },
|
|
||||||
llm: {
|
|
||||||
configTitle: 'Configure LLM API',
|
|
||||||
modelName: 'Model Name',
|
|
||||||
apiUrl: 'API URL',
|
|
||||||
apiKey: 'API Key',
|
|
||||||
compatibleNote: 'Compatible with any OpenAI-compatible API',
|
|
||||||
connectSuccess: 'LLM connected successfully',
|
|
||||||
connectFailedPrefix: 'LLM connection failed, please check your configuration',
|
|
||||||
},
|
|
||||||
common: { close: 'Close', test: 'Test', save: 'Save', select: 'Select', noData: 'No data' },
|
|
||||||
output: { label: 'Output Text (editable)' },
|
|
||||||
errors: {
|
|
||||||
promptRequired: 'Prompt cannot be empty',
|
|
||||||
generateFailedPrefix: 'Generation failed, please check LLM configuration',
|
|
||||||
outputFileNameRequired: 'Please set an output file name first',
|
|
||||||
outputPathRequired: 'Please set an export folder first',
|
|
||||||
outputSizeRequired: 'Please set output resolution (width x height) first',
|
|
||||||
bgmListFailed: 'Failed to load BGM list. Please check the folder exists',
|
|
||||||
ttsFailedCorrupt: 'TTS failed: audio file is corrupted',
|
|
||||||
ttsZeroDuration: 'TTS duration is 0s. Check TTS config and network connectivity',
|
|
||||||
renderFailedPrefix: 'Video rendering failed. Please check all configurations',
|
|
||||||
assetsDurationInsufficient: 'Total assets duration is insufficient',
|
|
||||||
edgeTtsListFailed: 'Failed to fetch Edge TTS voice list. Please check your network',
|
|
||||||
ttsConfigInvalid: 'TTS configuration is invalid',
|
|
||||||
ttsSynthesisFailed: 'TTS synthesis failed',
|
|
||||||
},
|
|
||||||
success: { renderSuccess: 'Video rendered successfully' },
|
|
||||||
info: { batchNext: 'Start next render', renderCanceled: 'Video rendering canceled' },
|
|
||||||
empty: {
|
|
||||||
noContent: 'No content yet',
|
|
||||||
hintSelectFolder: 'Please choose a folder above with enough storyboard assets',
|
|
||||||
},
|
|
||||||
videoManage: {
|
|
||||||
assetsFolderLabel: 'Storyboard assets folder',
|
|
||||||
noMp4InFolder: 'No MP4 video files found in the selected folder',
|
|
||||||
emptyFolder: 'The selected folder is empty',
|
|
||||||
readSuccess: 'Assets loaded successfully',
|
|
||||||
readFailed: 'Failed to read assets. Please check if the folder exists',
|
|
||||||
},
|
|
||||||
dialogs: {
|
|
||||||
selectAssetsFolderTitle: 'Select storyboard assets folder',
|
|
||||||
selectOutputFolderTitle: 'Select video export folder',
|
|
||||||
selectBgmFolderTitle: 'Select background music folder',
|
|
||||||
renderConfigTitle: 'Configure render options',
|
|
||||||
},
|
|
||||||
render: {
|
|
||||||
status: {
|
|
||||||
idle: 'Idle, ready to render',
|
|
||||||
generatingText: 'Generating script with AI LLM',
|
|
||||||
synthesizingSpeech: 'Synthesizing speech with TTS',
|
|
||||||
segmentingVideo: 'Processing video segments',
|
|
||||||
rendering: 'Rendering video',
|
|
||||||
success: 'Rendered successfully, you can start the next one',
|
|
||||||
failed: 'Render failed, please try again',
|
|
||||||
},
|
|
||||||
startRender: 'Start Rendering',
|
|
||||||
stopRender: 'Stop Rendering',
|
|
||||||
autoBatch: 'Auto batch render',
|
|
||||||
bgmFolderLabel: 'Background music folder (.mp3, pick randomly)',
|
|
||||||
output: {
|
|
||||||
width: 'Output width',
|
|
||||||
height: 'Output height',
|
|
||||||
fileName: 'Output file name',
|
|
||||||
format: 'Output format',
|
|
||||||
folder: 'Output folder',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tts: {
|
|
||||||
language: 'Language',
|
|
||||||
gender: 'Gender',
|
|
||||||
voice: 'Voice',
|
|
||||||
speed: 'Speed',
|
|
||||||
tryText: 'Try-listen text',
|
|
||||||
tryListen: 'Try listen',
|
|
||||||
selectLanguageGenderFirst: 'Please select language and gender first',
|
|
||||||
selectVoiceWarning: 'Please select a voice',
|
|
||||||
tryTextEmptyWarning: 'Try-listen text cannot be empty',
|
|
||||||
playTryAudio: 'Playing try-listen audio',
|
|
||||||
trySynthesisFailedNetwork: 'Failed to synthesize try-listen audio. Please check network',
|
|
||||||
genderMale: 'Male',
|
|
||||||
genderFemale: 'Female',
|
|
||||||
speedSlow: 'Slow',
|
|
||||||
speedMedium: 'Medium',
|
|
||||||
speedFast: 'Fast',
|
|
||||||
},
|
|
||||||
footer: { poweredBy: 'Powered by YILS (Blog: https://yils.blog)' },
|
|
||||||
},
|
|
||||||
'zh-CN': {
|
|
||||||
app: { name: 'AI Short Video Factory - 短视频工厂' },
|
|
||||||
prompt: { label: '提示词' },
|
|
||||||
actions: { generate: '生成', stop: '停止', config: '配置', refreshAssets: '刷新素材库' },
|
|
||||||
llm: {
|
|
||||||
configTitle: '配置大语言模型接口',
|
|
||||||
modelName: '模型名称',
|
|
||||||
apiUrl: 'API 地址',
|
|
||||||
apiKey: 'API Key',
|
|
||||||
compatibleNote: '兼容任意 OpenAI 标准接口',
|
|
||||||
connectSuccess: '大模型连接成功',
|
|
||||||
connectFailedPrefix: '大模型连接失败,请检查配置是否正确',
|
|
||||||
},
|
|
||||||
common: { close: '关闭', test: '测试', save: '保存', select: '选择', noData: '无数据' },
|
|
||||||
output: { label: '输出文案(可编辑)' },
|
|
||||||
errors: {
|
|
||||||
promptRequired: '提示词不能为空',
|
|
||||||
generateFailedPrefix: '生成失败,请检查大模型配置是否正确',
|
|
||||||
outputFileNameRequired: '请先配置导出文件名',
|
|
||||||
outputPathRequired: '请先配置导出文件夹',
|
|
||||||
outputSizeRequired: '请先配置导出分辨率(宽高)',
|
|
||||||
bgmListFailed: '获取背景音乐列表失败,请检查文件夹是否存在',
|
|
||||||
ttsFailedCorrupt: '语音合成失败,音频文件损坏',
|
|
||||||
ttsZeroDuration: '语音时长为0秒,检查TTS语音合成配置及网络连接是否正常',
|
|
||||||
renderFailedPrefix: '视频合成失败,请检查各项配置是否正确',
|
|
||||||
assetsDurationInsufficient: '素材总时长不足',
|
|
||||||
edgeTtsListFailed: '获取EdgeTTS语音列表失败,请检查网络',
|
|
||||||
ttsConfigInvalid: 'TTS语音合成配置无效',
|
|
||||||
ttsSynthesisFailed: '语音合成失败',
|
|
||||||
},
|
|
||||||
success: { renderSuccess: '视频合成成功' },
|
|
||||||
info: { batchNext: '开始合成下一个', renderCanceled: '视频合成已终止' },
|
|
||||||
empty: {
|
|
||||||
noContent: '暂无内容',
|
|
||||||
hintSelectFolder: '从上面选择一个包含足够分镜素材的文件夹',
|
|
||||||
},
|
|
||||||
videoManage: {
|
|
||||||
assetsFolderLabel: '分镜视频素材文件夹',
|
|
||||||
noMp4InFolder: '选择的文件夹中不包含MP4视频文件',
|
|
||||||
emptyFolder: '选择的文件夹为空',
|
|
||||||
readSuccess: '素材读取成功',
|
|
||||||
readFailed: '素材读取失败,请检查文件夹是否存在',
|
|
||||||
},
|
|
||||||
dialogs: {
|
|
||||||
selectAssetsFolderTitle: '选择分镜素材文件夹',
|
|
||||||
selectOutputFolderTitle: '选择视频导出文件夹',
|
|
||||||
selectBgmFolderTitle: '选择背景音乐文件夹',
|
|
||||||
renderConfigTitle: '配置合成选项',
|
|
||||||
},
|
|
||||||
render: {
|
|
||||||
status: {
|
|
||||||
idle: '空闲,可以开始合成',
|
|
||||||
generatingText: '正在使用 AI 大模型生成文案',
|
|
||||||
synthesizingSpeech: '正在使用 TTS 合成语音',
|
|
||||||
segmentingVideo: '正在处理分镜素材',
|
|
||||||
rendering: '正在渲染视频',
|
|
||||||
success: '渲染成功,可以开始下一个',
|
|
||||||
failed: '渲染失败,请重新尝试',
|
|
||||||
},
|
|
||||||
startRender: '开始合成',
|
|
||||||
stopRender: '停止合成',
|
|
||||||
autoBatch: '自动批量合成',
|
|
||||||
bgmFolderLabel: '背景音乐文件夹(.mp3格式,从中随机选取)',
|
|
||||||
output: {
|
|
||||||
width: '导出视频宽度',
|
|
||||||
height: '导出视频高度',
|
|
||||||
fileName: '导出文件名',
|
|
||||||
format: '导出格式',
|
|
||||||
folder: '导出文件夹',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tts: {
|
|
||||||
language: '语言',
|
|
||||||
gender: '性别',
|
|
||||||
voice: '声音',
|
|
||||||
speed: '语速',
|
|
||||||
tryText: '试听文本',
|
|
||||||
tryListen: '试听',
|
|
||||||
selectLanguageGenderFirst: '请先选择语言和性别',
|
|
||||||
selectVoiceWarning: '请选择一个声音',
|
|
||||||
tryTextEmptyWarning: '试听文本不能为空',
|
|
||||||
playTryAudio: '播放试听语音',
|
|
||||||
trySynthesisFailedNetwork: '试听语音合成失败,请检查网络',
|
|
||||||
genderMale: '男性',
|
|
||||||
genderFemale: '女性',
|
|
||||||
speedSlow: '慢',
|
|
||||||
speedMedium: '中',
|
|
||||||
speedFast: '快',
|
|
||||||
},
|
|
||||||
footer: { poweredBy: 'Powered by YILS(博客地址:https://yils.blog)' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const stored = (typeof localStorage !== 'undefined' && localStorage.getItem('locale')) || undefined
|
|
||||||
const locale = stored || 'en'
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
|
||||||
legacy: false,
|
|
||||||
locale,
|
|
||||||
fallbackLocale: 'en',
|
|
||||||
messages,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default i18n
|
|
||||||
@@ -5,6 +5,32 @@
|
|||||||
<span>{{ t('app.name') }}</span>
|
<span>{{ t('app.name') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="window-control-bar">
|
<div class="window-control-bar">
|
||||||
|
<div class="window-no-drag">
|
||||||
|
<v-menu location="bottom right">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<div class="control-btn control-btn-translate" v-bind="props">
|
||||||
|
<v-icon icon="mdi-translate" size="small" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<v-list
|
||||||
|
class="p-2 space-y-1"
|
||||||
|
activatable
|
||||||
|
:activated="i18next.language"
|
||||||
|
@update:activated="handleChangeLanguage"
|
||||||
|
>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(item, index) in i18nLanguages"
|
||||||
|
:key="index"
|
||||||
|
:value="item.code"
|
||||||
|
color="primary"
|
||||||
|
density="compact"
|
||||||
|
rounded
|
||||||
|
>
|
||||||
|
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
<div class="control-btn control-btn-min" @click="handleMin">
|
<div class="control-btn control-btn-min" @click="handleMin">
|
||||||
<v-icon icon="mdi-window-minimize" size="small" />
|
<v-icon icon="mdi-window-minimize" size="small" />
|
||||||
</div>
|
</div>
|
||||||
@@ -23,11 +49,24 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useTranslation } from 'i18next-vue'
|
||||||
|
import { i18nLanguages } from '~/electron/i18n/common-options'
|
||||||
|
|
||||||
|
const { i18next, t } = useTranslation()
|
||||||
|
// const lang = ref(i18next.language)
|
||||||
|
// console.log('i18next.language', i18next.language)
|
||||||
|
|
||||||
|
document.title = t('app.name')
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const windowIsMaxed = ref(false)
|
const windowIsMaxed = ref(false)
|
||||||
const { t } = useI18n()
|
|
||||||
|
const handleChangeLanguage = (lng: unknown) => {
|
||||||
|
console.log('handleChangeLanguage', lng)
|
||||||
|
if ((lng as string[])[0]) {
|
||||||
|
window.i18n.changeLanguage((lng as string[])[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', async () => {
|
window.addEventListener('resize', async () => {
|
||||||
windowIsMaxed.value = await window.electron.isWinMaxed()
|
windowIsMaxed.value = await window.electron.isWinMaxed()
|
||||||
|
|||||||
27
src/lib/i18n.ts
Normal file
27
src/lib/i18n.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { useAppStore } from '@/store'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import Backend from 'i18next-http-backend'
|
||||||
|
import { toRaw } from 'vue'
|
||||||
|
import { i18nCommonOptions } from '~/electron/i18n/common-options'
|
||||||
|
|
||||||
|
const i18nInitialized = async () => {
|
||||||
|
const appStore = useAppStore()
|
||||||
|
if (appStore.locale) {
|
||||||
|
await window.i18n.changeLanguage(toRaw(appStore.locale))
|
||||||
|
} else {
|
||||||
|
const systemLocale = await window.i18n.getLanguage()
|
||||||
|
appStore.updateLocale(systemLocale)
|
||||||
|
}
|
||||||
|
return i18next.use(Backend).init({
|
||||||
|
// debug: true,
|
||||||
|
...i18nCommonOptions,
|
||||||
|
lng: appStore.locale,
|
||||||
|
backend: {
|
||||||
|
loadPath: 'file:///' + (await window.i18n.getLocalesPath()),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const i18n = i18next
|
||||||
|
|
||||||
|
export default i18nInitialized
|
||||||
37
src/main.ts
37
src/main.ts
@@ -12,11 +12,14 @@ import 'virtual:uno.css'
|
|||||||
import './assets/base.scss'
|
import './assets/base.scss'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import i18n from './i18n'
|
|
||||||
import router from './router/index.ts'
|
import router from './router/index.ts'
|
||||||
import store from './store/index.ts'
|
import store, { useAppStore } from './store/index.ts'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
import i18next from 'i18next'
|
||||||
|
import I18NextVue from 'i18next-vue'
|
||||||
|
import i18nInitialized from './lib/i18n.ts'
|
||||||
|
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
@@ -29,30 +32,26 @@ const vuetify = createVuetify({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set initial document title from i18n
|
|
||||||
document.title = i18n.global.t('app.name') as string
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(vuetify)
|
app.use(vuetify)
|
||||||
app.use(Toast, { position: 'bottom-left', pauseOnFocusLoss: false } as PluginOptions)
|
app.use(Toast, { position: 'bottom-left', pauseOnFocusLoss: false } as PluginOptions)
|
||||||
app.use(i18n)
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
|
||||||
app.mount('#app').$nextTick(() => {
|
// 初始化并应用国际化
|
||||||
// Use contextBridge
|
i18nInitialized().then(() => {
|
||||||
window.ipcRenderer.on('main-process-message', (_event, message) => {
|
app.use(I18NextVue, { i18next })
|
||||||
console.log(message)
|
app.mount('#app').$nextTick(() => {
|
||||||
})
|
// 测试消息
|
||||||
|
window.ipcRenderer.on('main-process-message', (_event, message) => {
|
||||||
|
console.log(message)
|
||||||
|
})
|
||||||
|
|
||||||
// Language switching from Electron menu
|
// 监听主进程切换语言
|
||||||
window.ipcRenderer.on('set-locale', (_event, locale: string) => {
|
window.ipcRenderer.on('i18n-changeLanguage', (_event, lng) => {
|
||||||
// @ts-ignore
|
i18next.changeLanguage(lng)
|
||||||
i18n.global.locale.value = locale
|
useAppStore().updateLocale(lng)
|
||||||
try {
|
})
|
||||||
localStorage.setItem('locale', locale)
|
|
||||||
} catch {}
|
|
||||||
document.title = i18n.global.t('app.name') as string
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ export enum RenderStatus {
|
|||||||
export const useAppStore = defineStore(
|
export const useAppStore = defineStore(
|
||||||
'app',
|
'app',
|
||||||
() => {
|
() => {
|
||||||
|
// 国际化区域设置
|
||||||
|
const locale = ref('')
|
||||||
|
const updateLocale = (newLocale: string) => {
|
||||||
|
locale.value = newLocale
|
||||||
|
}
|
||||||
|
|
||||||
// 大模型文案生成
|
// 大模型文案生成
|
||||||
const prompt = ref('')
|
const prompt = ref('')
|
||||||
const llmConfig = ref({
|
const llmConfig = ref({
|
||||||
@@ -72,6 +78,9 @@ export const useAppStore = defineStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
locale,
|
||||||
|
updateLocale,
|
||||||
|
|
||||||
prompt,
|
prompt,
|
||||||
llmConfig,
|
llmConfig,
|
||||||
updateLLMConfig,
|
updateLLMConfig,
|
||||||
|
|||||||
@@ -34,7 +34,9 @@
|
|||||||
|
|
||||||
<v-dialog v-model="configDialogShow" max-width="600" persistent>
|
<v-dialog v-model="configDialogShow" max-width="600" persistent>
|
||||||
<template v-slot:activator="{ props: activatorProps }">
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
<v-btn v-bind="activatorProps" :disabled="disabled"> {{ t('actions.config') }} </v-btn>
|
<v-btn v-bind="activatorProps" :disabled="disabled">
|
||||||
|
{{ t('actions.config') }}
|
||||||
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card prepend-icon="mdi-text-box-edit-outline" :title="t('llm.configTitle')">
|
<v-card prepend-icon="mdi-text-box-edit-outline" :title="t('llm.configTitle')">
|
||||||
@@ -58,7 +60,9 @@
|
|||||||
required
|
required
|
||||||
clearable
|
clearable
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<small class="text-caption text-medium-emphasis">{{ t('llm.compatibleNote') }}</small>
|
<small class="text-caption text-medium-emphasis">{{
|
||||||
|
t('llm.compatibleNote')
|
||||||
|
}}</small>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -102,11 +106,11 @@ import { nextTick, ref, toRaw } from 'vue'
|
|||||||
import { createOpenAI } from '@ai-sdk/openai'
|
import { createOpenAI } from '@ai-sdk/openai'
|
||||||
import { generateText, streamText } from 'ai'
|
import { generateText, streamText } from 'ai'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useTranslation } from 'i18next-vue'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useI18n()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@@ -151,7 +155,9 @@ const handleGenerate = async (oprions?: { noToast?: boolean }) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const errorMessage = error?.message || error?.error?.message
|
const errorMessage = error?.message || error?.error?.message
|
||||||
!oprions?.noToast &&
|
!oprions?.noToast &&
|
||||||
toast.error(`${t('errors.generateFailedPrefix')}\n${errorMessage ? 'Error: ' + errorMessage : ''}`)
|
toast.error(
|
||||||
|
`${t('errors.generateFailedPrefix')}\n${errorMessage ? 'Error: ' + errorMessage : ''}`,
|
||||||
|
)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -58,13 +58,13 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { useAppStore } from '@/store'
|
import { useAppStore } from '@/store'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
|
import { useTranslation } from 'i18next-vue'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useI18n()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, toRaw } from 'vue'
|
import { ref, toRaw } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useTranslation } from 'i18next-vue'
|
||||||
import { useAppStore } from '@/store'
|
import { useAppStore } from '@/store'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { ListFilesFromFolderRecord } from '~/electron/types'
|
import { ListFilesFromFolderRecord } from '~/electron/types'
|
||||||
@@ -76,7 +76,7 @@ import random from 'random'
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useI18n()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-0 flex-1 relative">
|
<div class="h-0 flex-1 relative">
|
||||||
<div class="absolute top-1/12 w-full flex justify-center cursor-default select-none">
|
<div class="absolute top-1/12 w-full flex justify-center cursor-default select-none">
|
||||||
<v-chip v-if="appStore.renderStatus === RenderStatus.None"> {{ t('render.status.idle') }} </v-chip>
|
<v-chip v-if="appStore.renderStatus === RenderStatus.None">
|
||||||
|
{{ t('render.status.idle') }}
|
||||||
|
</v-chip>
|
||||||
<v-chip v-if="appStore.renderStatus === RenderStatus.GenerateText" variant="elevated">
|
<v-chip v-if="appStore.renderStatus === RenderStatus.GenerateText" variant="elevated">
|
||||||
{{ t('render.status.generatingText') }}
|
{{ t('render.status.generatingText') }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
@@ -59,10 +61,15 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
<v-dialog v-model="configDialogShow" max-width="600" persistent>
|
<v-dialog v-model="configDialogShow" max-width="600" persistent>
|
||||||
<template v-slot:activator="{ props: activatorProps }">
|
<template v-slot:activator="{ props: activatorProps }">
|
||||||
<v-btn v-bind="activatorProps" :disabled="taskInProgress"> {{ t('actions.config') }} </v-btn>
|
<v-btn v-bind="activatorProps" :disabled="taskInProgress">
|
||||||
|
{{ t('actions.config') }}
|
||||||
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-card prepend-icon="mdi-text-box-edit-outline" :title="t('dialogs.renderConfigTitle')">
|
<v-card
|
||||||
|
prepend-icon="mdi-text-box-edit-outline"
|
||||||
|
:title="t('dialogs.renderConfigTitle')"
|
||||||
|
>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div class="w-full flex gap-2 mb-4 items-center">
|
<div class="w-full flex gap-2 mb-4 items-center">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@@ -166,11 +173,11 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, toRaw, nextTick, computed } from 'vue'
|
import { ref, toRaw, nextTick, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useTranslation } from 'i18next-vue'
|
||||||
import { RenderStatus, useAppStore } from '@/store'
|
import { RenderStatus, useAppStore } from '@/store'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useI18n()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'renderVideo'): void
|
(e: 'renderVideo'): void
|
||||||
|
|||||||
@@ -35,15 +35,15 @@ import TtsControl from './components/tts-control.vue'
|
|||||||
import VideoRender from './components/video-render.vue'
|
import VideoRender from './components/video-render.vue'
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
import { RenderStatus, useAppStore } from '@/store'
|
import { RenderStatus, useAppStore } from '@/store'
|
||||||
|
import { useTranslation } from 'i18next-vue'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { ListFilesFromFolderRecord } from '~/electron/types'
|
import { ListFilesFromFolderRecord } from '~/electron/types'
|
||||||
import random from 'random'
|
import random from 'random'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useI18n()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
// 渲染合成视频
|
// 渲染合成视频
|
||||||
const TextGenerateInstance = ref<InstanceType<typeof TextGenerate> | null>()
|
const TextGenerateInstance = ref<InstanceType<typeof TextGenerate> | null>()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default defineConfig({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '0',
|
top: '0',
|
||||||
right: '0',
|
right: '0',
|
||||||
width: '120px',
|
width: '168px',
|
||||||
height: '35px',
|
height: '35px',
|
||||||
'-webkit-app-region': 'no-drag',
|
'-webkit-app-region': 'no-drag',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
'~': fileURLToPath(new URL('./', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
Reference in New Issue
Block a user