Skip to content

Commit

Permalink
feat: support i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
linxiaodong authored and buxuku committed Oct 10, 2024
1 parent a6a0000 commit 8fa8722
Show file tree
Hide file tree
Showing 22 changed files with 991 additions and 833 deletions.
4 changes: 2 additions & 2 deletions main/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ if (isProd) {
});

if (isProd) {
await mainWindow.loadURL(`app://./${userLanguage}/home`);
await mainWindow.loadURL(`app://./${userLanguage}/home/`);
} else {
const port = process.argv[2];
await mainWindow.loadURL(`http://localhost:${port}/${userLanguage}/home`);
await mainWindow.loadURL(`http://localhost:${port}/${userLanguage}/home/`);
mainWindow.webContents.openDevTools();
}

Expand Down
5 changes: 4 additions & 1 deletion main/helpers/storeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export function setupStoreHandlers() {
});

ipcMain.handle('getUserConfig', async () => {
return store.get('userConfig');
const storedConfig = store.get('userConfig');
// 合并默认配置和存储的配置
const mergedConfig = { ...defaultUserConfig, ...storedConfig };
return mergedConfig;
});

ipcMain.handle('setSettings', async (event, settings) => {
Expand Down
7 changes: 4 additions & 3 deletions main/helpers/systemInfoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,20 @@ export function setupSystemInfoManager(mainWindow: BrowserWindow) {
ipcMain.handle('downloadModel', async (event, { model, source }) => {
downloadingModels.add(model);
const onProcess = (data) => {
const match = data?.match(/(\d+)%/);
const match = data?.match?.(/(\d+)/);
console.log(match, model, 'match');
if (match) {
event.sender.send('downloadProgress', model, +match[1]);
}
if (data?.includes('Done') || data?.includes('main')) {
if (data?.includes?.('Done') || data?.includes?.('main')) {
event.sender.send('downloadProgress', model, 100);
}
};
try {
await downloadModelSync(model?.toLowerCase(), source, onProcess);
downloadingModels.delete(model);
} catch (error) {
event.sender.send('message', '下载失败,请切换下载源重试');
event.sender.send('message', 'download error, please try again');
downloadingModels.delete(model);
return false;
}
Expand Down
1 change: 1 addition & 0 deletions main/helpers/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function translate(
sourceLanguage,
targetLanguage,
} = formData || {};
console.log(formData, 'formData');

const renderContentTemplate = contentTemplate[translateContent];
const proof = provider;
Expand Down
6 changes: 3 additions & 3 deletions main/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function runCommand(command, args, onProcess = undefined) {
child.on("close", (code) => {
if (code !== 0) {
reject(
new Error(`${command} ${args.join(" ")} 进程退出,退出码 ${code}`),
new Error(`${command} ${args.join(" ")} process error ${code}`),
);
} else {
resolve(true);
Expand Down Expand Up @@ -77,8 +77,8 @@ function throttle(func, limit) {
export const defaultUserConfig = {
sourceLanguage: 'en',
targetLanguage: 'zh',
targetSrtSaveFileName: '${fileName}.${targetLanguage}',
sourceSrtSaveFileName: '${fileName}.${sourceLanguage}',
customTargetSrtFileName: '${fileName}.${targetLanguage}',
customSourceSrtFileName: '${fileName}.${sourceLanguage}',
model: 'tiny',
translateProvider: 'baidu',
translateContent: 'onlyTranslate',
Expand Down
81 changes: 49 additions & 32 deletions main/helpers/whisper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import path from "path";
import fs from "fs";
import git from "isomorphic-git";
import http from "isomorphic-git/http/node";
import replaceModelSource from "./model-source";
import { isDarwin, isWin32, runCommand } from "./utils";
import { isWin32 } from "./utils";
import { BrowserWindow, DownloadItem } from 'electron';

export const getPath = (key?: string) => {
const userDataPath = app.getPath("userData");
Expand Down Expand Up @@ -120,40 +120,57 @@ export const deleteModel = async (model) => {
});
};

export const downloadModelSync = async (model, source, onProcess) => {
export const downloadModelSync = async (model: string, source: string, onProcess: (message: string) => void) => {
const modelsPath = getPath("modelsPath");
const modelPath = path.join(modelsPath, `ggml-${model}.bin`);
if (fs.existsSync(modelPath)) return;
if (!checkWhisperInstalled()) {
throw Error("whisper.cpp 未下载,请先下载 whisper.cpp");

if (fs.existsSync(modelPath)) {
return;
}
try {
let downShellPath;
let shell: string;
let args = [];
if (isDarwin()) {
downShellPath = path.join(modelsPath, "download-ggml-model.sh");
shell = "bash";
args = [`${downShellPath}`, `${model}`];
} else if (isWin32()) {
downShellPath = path.join(modelsPath, "download-ggml-model.cmd");
shell = "cmd";
args = [`/c`, `${downShellPath}`, `${model}`];
} else {
throw Error("platform does not support! ");
}
await replaceModelSource(`${downShellPath}`, source);
console.log("完成模型下载地址替换", model);
console.log("正在安装 whisper.cpp 模型");
try {
await runCommand(`${shell}`, args, (data) => onProcess(data));
} catch (error) {
await deleteModel(model);
throw error;
}
} catch (error) {
throw error;
if (!checkWhisperInstalled()) {
throw Error("whisper.cpp 未安装,请先安装 whisper.cpp");
}

const url = `https://${source === 'huggingface' ? 'huggingface.co' : 'hf-mirror.com'}/ggerganov/whisper.cpp/resolve/main/ggml-${model}.bin`;

return new Promise((resolve, reject) => {
const win = new BrowserWindow({ show: false });

const willDownloadHandler = (event, item: DownloadItem) => {
if (item.getFilename() !== `ggml-${model}.bin`) {
return; // 忽略不匹配的下载项
}

item.setSavePath(modelPath);

item.on('updated', (event, state) => {
if (state === 'progressing' && !item.isPaused()) {
const percent = item.getReceivedBytes() / item.getTotalBytes() * 100;
onProcess(`${model}: ${percent.toFixed(2)}%`);
}
});

item.once('done', (event, state) => {
if (state === 'completed') {
onProcess(`${model} 完成`);
cleanup();
resolve(1);
} else {
fs.unlink(modelPath, () => {});
cleanup();
reject(new Error(`${model} download error: ${state}`));
}
});
};

const cleanup = () => {
win.webContents.session.removeListener('will-download', willDownloadHandler);
win.destroy();
};

win.webContents.session.on('will-download', willDownloadHandler);
win.webContents.downloadURL(url);
});
};

export async function checkOpenAiWhisper(): Promise<boolean> {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@
"@types/node": "^20.11.16",
"@types/react": "^18.2.52",
"autoprefixer": "^10.4.19",
"electron": "^29.3.0",
"electron": "^30.1.0",
"electron-builder": "^24.13.3",
"next": "^13.5.6",
"nextron": "^8.15.0",
"next": "^14.2.4",
"nextron": "^9.1.0",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
4 changes: 2 additions & 2 deletions renderer/components/DownModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const DownModel: FC<IProps> = (props) => {
const [loading, setLoading] = React.useState(false);
const [progress, setProgress] = React.useState(0);
useEffect(() => {
window?.ipc?.on("downloadProgress", (model, progress: number) => {
if (model === modelName) {
window?.ipc?.on("downloadProgress", (model: string, progress: number) => {
if (model?.toLowerCase() === modelName?.toLowerCase()) {
setProgress(progress);
setLoading(progress < 100);
if (progress >= 100) {
Expand Down
20 changes: 11 additions & 9 deletions renderer/components/DownModelLink.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTranslation } from "next-i18next";
import React, { FC } from "react";
import { useTranslation } from 'next-i18next';
import React, { FC } from 'react';

interface IProps {
loading?: boolean;
Expand All @@ -10,16 +10,18 @@ const DownModelLink: FC<IProps> = ({ loading, progress, handleDownModel }) => {
const { t } = useTranslation('common');
return (
<span className="inline-block">
{t('modelNotDownloaded')}
{loading ? (
`${t('downloading')} ${progress}%...`
) : (
<a
className="cursor-pointer text-blue-500"
onClick={() => handleDownModel()}
>
{t('downloadNow')}
</a>
<>
{t('modelNotDownloaded')}
<a
className="cursor-pointer text-blue-500 ml-4"
onClick={() => handleDownModel()}
>
{t('downloadNow')}
</a>
</>
)}
</span>
);
Expand Down
2 changes: 1 addition & 1 deletion renderer/components/Guide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const Guide: FC<IProps> = ({ systemInfo, updateSystemInfo }) => {
};

const changeLanguage = (lang: string) => {
router.push(router.pathname, router.asPath, { locale: lang });
router.push(`/${lang}/home/`);
window?.ipc?.invoke('setSettings', { language: lang });
};

Expand Down
10 changes: 5 additions & 5 deletions renderer/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useTranslation } from 'next-i18next';


const Layout = ({ children }) => {
const { t } = useTranslation('common');
const { t, i18n: { language: locale } } = useTranslation('common');
const { asPath } = useRouter();
useEffect(() => {
window?.ipc?.on('message', (res: string) => {
Expand All @@ -41,7 +41,7 @@ const Layout = ({ children }) => {
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Link href="/home">
<Link href={`/${locale}/home`}>
<Button
variant="ghost"
size="icon"
Expand All @@ -60,7 +60,7 @@ const Layout = ({ children }) => {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Link href="/modelsControl">
<Link href={`/${locale}/modelsControl`}>
<Button
variant="ghost"
size="icon"
Expand All @@ -79,7 +79,7 @@ const Layout = ({ children }) => {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Link href="/translateControl">
<Link href={`/${locale}/translateControl`}>
<Button
variant="ghost"
size="icon"
Expand All @@ -98,7 +98,7 @@ const Layout = ({ children }) => {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Link href="/settings">
<Link href={`/${locale}/settings`}>
<Button
variant="ghost"
size="icon"
Expand Down
2 changes: 1 addition & 1 deletion renderer/components/Models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Models: FC<SelectPrimitive.SelectProps & IProps> = (props) => {
{item.name}
{!props?.modelsInstalled?.includes(
item.name?.toLowerCase()
) && `(${t('notInstalled')})`}
) && `(${t('modelNotDownloaded')})`}
</p>
<p className="text-xs" data-description>
{t(item.desc.key)} {item.desc.size}
Expand Down
5 changes: 3 additions & 2 deletions renderer/components/TaskConfigForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,12 @@ const TaskConfigForm = ({
modelsInstalled={systemInfo.modelsInstalled}
/>
</FormControl>
{!isInstalledModel && (
{!isInstalledModel && field.value && (
<FormDescription>
<DownModel
modelName={formData.model}
modelName={field.value}
callBack={updateSystemInfo}
key={field.value}
>
<DownModelLink />
</DownModel>
Expand Down
33 changes: 33 additions & 0 deletions renderer/lib/get-static.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import i18next from '../../next-i18next.config.js'

export function getI18nPaths() {
return ['en', 'zh'].map((locale) => ({
params: {
locale,
},
}))
}

export function getStaticPaths() {
return {
fallback: false,
paths: getI18nPaths(),
}
}

export async function getI18nProperties(context, namespaces = ['common']) {
const locale = context?.params?.locale ?? i18next.i18n.defaultLocale
return {
...(await serverSideTranslations(locale, namespaces)),
}
}

export function makeStaticProperties(namespaces = []) {
return async function (context) {
return {
props: await getI18nProperties(context, namespaces),
}
}
}
2 changes: 1 addition & 1 deletion renderer/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
4 changes: 2 additions & 2 deletions renderer/next.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/** @type {import('next').NextConfig} */
const { i18n } = require('../next-i18next.config')

module.exports = {
i18n,
trailingSlash: true,
images: {
unoptimized: true,
},
output: 'export',
distDir: process.env.NODE_ENV === 'production' ? '../app' : '.next',
webpack: (config) => {
return config
},
Expand Down
Loading

0 comments on commit 8fa8722

Please sign in to comment.