diff --git a/backend-golang/app.go b/backend-golang/app.go index deffeab..21a404e 100644 --- a/backend-golang/app.go +++ b/backend-golang/app.go @@ -3,8 +3,11 @@ package backend_golang import ( "context" "net/http" + "os" + "os/exec" "github.com/minio/selfupdate" + "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct @@ -36,5 +39,11 @@ func (a *App) UpdateApp(url string) (broken bool, err error) { } return false, err } + name, err := os.Executable() + if err != nil { + return false, err + } + exec.Command(name, os.Args[1:]...).Start() + runtime.Quit(a.ctx) return false, nil } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ff1934e..df8c3c6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@fluentui/react-components": "^9.20.0", "@fluentui/react-icons": "^2.0.201", + "i18next": "^22.4.15", "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^12.2.2", "react-router": "^6.11.1", "react-router-dom": "^6.11.1", "react-toastify": "^9.1.3", @@ -2507,6 +2509,22 @@ "node": ">=4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "22.4.15", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-22.4.15.tgz", + "integrity": "sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==", + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", @@ -2999,6 +3017,27 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-i18next": { + "version": "12.2.2", + "resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-12.2.2.tgz", + "integrity": "sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==", + "dependencies": { + "@babel/runtime": "^7.20.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 19.0.0", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3514,6 +3553,14 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 82ef1c6..ca00b44 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,10 +11,12 @@ "dependencies": { "@fluentui/react-components": "^9.20.0", "@fluentui/react-icons": "^2.0.201", + "i18next": "^22.4.15", "mobx": "^6.9.0", "mobx-react-lite": "^3.4.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^12.2.2", "react-router": "^6.11.1", "react-router-dom": "^6.11.1", "react-toastify": "^9.1.3", diff --git a/frontend/src/_locales/i18n-react.ts b/frontend/src/_locales/i18n-react.ts new file mode 100644 index 0000000..de8e3b4 --- /dev/null +++ b/frontend/src/_locales/i18n-react.ts @@ -0,0 +1,13 @@ +import i18n, {changeLanguage} from 'i18next'; +import {initReactI18next} from 'react-i18next'; +import {resources} from './resources'; +import {getNavigatorLanguage} from '../utils'; + +i18n.use(initReactI18next).init({ + resources, + interpolation: { + escapeValue: false // not needed for react as it escapes by default + } +}).then(() => { + changeLanguage(getNavigatorLanguage()); +}); diff --git a/frontend/src/_locales/i18n.ts b/frontend/src/_locales/i18n.ts new file mode 100644 index 0000000..34ed77c --- /dev/null +++ b/frontend/src/_locales/i18n.ts @@ -0,0 +1,9 @@ +import i18n, {changeLanguage} from 'i18next'; +import {resources} from './resources'; +import {getNavigatorLanguage} from '../utils'; + +i18n.init({ + resources +}).then(() => { + changeLanguage(getNavigatorLanguage()); +}); diff --git a/frontend/src/_locales/resources.ts b/frontend/src/_locales/resources.ts new file mode 100644 index 0000000..bd327d2 --- /dev/null +++ b/frontend/src/_locales/resources.ts @@ -0,0 +1,37 @@ +import zhHans from './zh-hans/main.json' + +export const resources = { + zh: { + translation: zhHans + } + // de: { + // translation: de, + // }, + // es: { + // translation: es, + // }, + // fr: { + // translation: fr, + // }, + // in: { + // translation: inTrans, + // }, + // it: { + // translation: it, + // }, + // ja: { + // translation: ja, + // }, + // ko: { + // translation: ko, + // }, + // pt: { + // translation: pt, + // }, + // ru: { + // translation: ru, + // }, + // zhHant: { + // translation: zhHant, + // }, +} diff --git a/frontend/src/_locales/zh-hans/main.json b/frontend/src/_locales/zh-hans/main.json new file mode 100644 index 0000000..74bb69d --- /dev/null +++ b/frontend/src/_locales/zh-hans/main.json @@ -0,0 +1,3 @@ +{ + "Settings": "设置" +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2232394..c2cef27 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,7 @@ import 'react-toastify/dist/ReactToastify.css'; import App from './App'; import {HashRouter} from 'react-router-dom'; import {startup} from './startup'; +import './_locales/i18n-react'; startup().then(() => { const container = document.getElementById('root'); diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index b33489e..03fa7d5 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -4,21 +4,33 @@ import {Dropdown, Option, Switch} from '@fluentui/react-components'; import {Labeled} from '../components/Labeled'; import commonStore from '../stores/commonStore'; import {observer} from 'mobx-react-lite'; +import {UpdateApp} from '../../wailsjs/go/backend_golang/App'; +import {useTranslation} from 'react-i18next'; +import {Language, Languages} from '../utils'; export const Settings: FC = observer(() => { + const {t, i18n} = useTranslation(); + return ( - { - if (data.optionText) { + if (data.optionValue) { + const lang = data.optionValue as Language; + commonStore.setSettings({ + language: lang + }); + i18n.changeLanguage(lang); } }}> - - + { + Object.entries(Languages).map(([langKey, desc]) => + ) + } }/> { { + commonStore.setSettings({ + autoUpdatesCheck: data.checked + }); + if (data.checked) + UpdateApp('http://localhost:34115/dist/RWKV-Runner.exe'); //TODO }}/> }/> diff --git a/frontend/src/stores/commonStore.ts b/frontend/src/stores/commonStore.ts index 11e3e95..8b7d704 100644 --- a/frontend/src/stores/commonStore.ts +++ b/frontend/src/stores/commonStore.ts @@ -1,5 +1,5 @@ import {makeAutoObservable} from 'mobx'; -import {getNavigatorLanguage, isSystemLightMode, saveConfigs, Settings} from '../utils'; +import {getNavigatorLanguage, isSystemLightMode, Language, saveConfigs, Settings} from '../utils'; import {WindowSetDarkTheme, WindowSetLightTheme} from '../../wailsjs/runtime'; export enum ModelStatus { @@ -84,7 +84,7 @@ class CommonStore { modelSourceManifestList: string = 'https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner/manifest.json;'; modelSourceList: ModelSourceItem[] = []; settings: Settings = { - language: getNavigatorLanguage(), + language: getNavigatorLanguage() as Language, darkMode: !isSystemLightMode(), autoUpdatesCheck: true }; diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 74d5eb1..ab736b4 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -2,12 +2,19 @@ import {ListDirFiles, ReadJson, SaveJson} from '../../wailsjs/go/backend_golang/ import manifest from '../../../manifest.json'; import commonStore, {ModelConfig, ModelParameters, ModelSourceItem} from '../stores/commonStore'; +export const Languages = { + dev: 'English', // i18n default + zh: '简体中文' +}; + +export type Language = keyof typeof Languages; + export type Cache = { models: ModelSourceItem[] } export type Settings = { - language: string, + language: Language, darkMode: boolean autoUpdatesCheck: boolean }