diff --git a/assets/jsons/translations/en-EN.json b/assets/jsons/translations/en-EN.json
new file mode 100644
index 00000000..89ade0a1
--- /dev/null
+++ b/assets/jsons/translations/en-EN.json
@@ -0,0 +1,18 @@
+{
+ "pages":{
+ "settings":{
+ "languages":{
+ "en-EN": "English, UK",
+ "en-US": "English, US",
+ "fr-FR": "Français",
+ "es-ES": "Español",
+ "translated":{
+ "en-EN": "English, United Kingdom",
+ "en-US": "English, USA",
+ "fr-FR": "French",
+ "es-ES": "Spanish"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/jsons/translations/en-US.json b/assets/jsons/translations/en-US.json
new file mode 100644
index 00000000..ab1f6aa0
--- /dev/null
+++ b/assets/jsons/translations/en-US.json
@@ -0,0 +1,18 @@
+{
+ "pages":{
+ "settings":{
+ "languages":{
+ "en-EN": "English, UK",
+ "en-US": "English, US",
+ "fr-FR": "Français",
+ "es-ES": "Español",
+ "translated":{
+ "en-EN": "English, United Kingdom",
+ "en-US": "English, United States",
+ "fr-FR": "French",
+ "es-ES": "Spanish"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/jsons/translations/es-ES.json b/assets/jsons/translations/es-ES.json
new file mode 100644
index 00000000..4391c47b
--- /dev/null
+++ b/assets/jsons/translations/es-ES.json
@@ -0,0 +1,18 @@
+{
+ "pages":{
+ "settings":{
+ "languages":{
+ "en-EN": "English, UK",
+ "en-US": "English, US",
+ "fr-FR": "Français",
+ "es-ES": "Español",
+ "translated":{
+ "en-EN": "Inglés, Reino Unido",
+ "en-US": "Inglés, Estados Unidos",
+ "fr-FR": "Francés",
+ "es-ES": "Español"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/jsons/translations/fr-FR.json b/assets/jsons/translations/fr-FR.json
new file mode 100644
index 00000000..fc928fee
--- /dev/null
+++ b/assets/jsons/translations/fr-FR.json
@@ -0,0 +1,18 @@
+{
+ "pages":{
+ "settings":{
+ "languages":{
+ "en-EN": "English, UK",
+ "en-US": "English, US",
+ "fr-FR": "Français",
+ "es-ES": "Español",
+ "translated":{
+ "en-EN": "Anglais, Royaume-Uni",
+ "en-US": "Anglais, États-Unis",
+ "fr-FR": "Français",
+ "es-ES": "Espagnol"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 617f00a2..0bdc8fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4141,6 +4141,14 @@
"semver": "^7.3.5"
},
"dependencies": {
+ "dot-prop": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+ "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "requires": {
+ "is-obj": "^2.0.0"
+ }
+ },
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@@ -5011,11 +5019,11 @@
}
},
"dot-prop": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
- "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz",
+ "integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==",
"requires": {
- "is-obj": "^2.0.0"
+ "type-fest": "^2.11.2"
}
},
"dotenv": {
diff --git a/package.json b/package.json
index a6e4a528..7ab7aaa2 100644
--- a/package.json
+++ b/package.json
@@ -227,6 +227,7 @@
"@node-steam/vdf": "^2.2.0",
"@reduxjs/toolkit": "^1.8.3",
"dateformat": "^5.0.3",
+ "dot-prop": "^7.2.0",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.7",
"electron-store": "^8.0.2",
diff --git a/src/renderer/components/settings/setting-radio-array.component.tsx b/src/renderer/components/settings/setting-radio-array.component.tsx
index 82028594..e085cb67 100644
--- a/src/renderer/components/settings/setting-radio-array.component.tsx
+++ b/src/renderer/components/settings/setting-radio-array.component.tsx
@@ -14,12 +14,10 @@ export function SettingRadioArray({items, selectedItem = items[0].id, onItemSele
{i.text}
{i.icon && (
- <>
-
- {i.textIcon && {i.textIcon}}
-
+
+ {i.textIcon && {i.textIcon}}
- >
+
)}
)) }
diff --git a/src/renderer/config/default-configuration.config.ts b/src/renderer/config/default-configuration.config.ts
index 8b46b84b..8034f70c 100644
--- a/src/renderer/config/default-configuration.config.ts
+++ b/src/renderer/config/default-configuration.config.ts
@@ -1,9 +1,11 @@
export const defaultConfiguration: {[key in DefaultConfigKey]: any} = {
"first-color": "#3b82ff",
"second-color": "#ff4444",
- "theme": "os"
+ "theme": "os",
+ "language": window.navigator.language.length <= 2 ? `${window.navigator.language}-${window.navigator.language.toLocaleUpperCase()}` : window.navigator.language,
+ "supported_languages": ["en-US", "en-EN", "fr-FR", "es-ES"],
}
-export type DefaultConfigKey = "first-color" | "second-color" | "theme";
+export type DefaultConfigKey = "first-color" | "second-color" | "theme" | "language" | "supported_languages";
export type ThemeConfig = "dark" | "light" | "os"
\ No newline at end of file
diff --git a/src/renderer/hooks/use-translation.hook.ts b/src/renderer/hooks/use-translation.hook.ts
new file mode 100644
index 00000000..9484b475
--- /dev/null
+++ b/src/renderer/hooks/use-translation.hook.ts
@@ -0,0 +1,11 @@
+import { useMemo } from "react";
+import { I18nService } from "renderer/services/i18n.service"
+
+export function useTranslation(): (translationKey: string) => string{
+
+ const i18nService = I18nService.getInstance();
+
+ return (key: string) => {
+ return useMemo(() => i18nService.translate(key), [key, i18nService.currentLanguage]);
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx
index 1665b853..b0f99012 100644
--- a/src/renderer/pages/settings-page.component.tsx
+++ b/src/renderer/pages/settings-page.component.tsx
@@ -5,9 +5,11 @@ import { RadioItem, SettingRadioArray } from "renderer/components/settings/setti
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config";
import { useObservable } from "renderer/hooks/use-observable.hook";
+import { useTranslation } from "renderer/hooks/use-translation.hook";
import { AuthUserService } from "renderer/services/auth-user.service";
import { BsDownloaderService } from "renderer/services/bs-downloader.service";
import { ConfigurationService } from "renderer/services/configuration.service"
+import { I18nService } from "renderer/services/i18n.service";
import { IpcService } from "renderer/services/ipc.service";
import { ModalExitCode, ModalService, ModalType } from "renderer/services/modale.service";
import { NotificationService } from "renderer/services/notification.service";
@@ -24,6 +26,12 @@ export function SettingsPage() {
const progressBarService: ProgressBarService = ProgressBarService.getInstance();
const authService: AuthUserService = AuthUserService.getInstance();
const notificationService: NotificationService = NotificationService.getInstance();
+ const i18nService: I18nService = I18nService.getInstance();
+
+ const firstColor = useObservable(configService.watch("first-color"));
+ const secondColor = useObservable(configService.watch("second-color"));
+ const sessionExist = useObservable(authService.sessionExist$);
+ const t = useTranslation();
const themeItem: RadioItem[] = [
{id: 0, text: "Dark", value: "dark" as ThemeConfig},
@@ -31,12 +39,16 @@ export function SettingsPage() {
{id: 3, text: "Operating System", value: "os" as ThemeConfig}
];
- const firstColor = useObservable(configService.watch("first-color"));
- const secondColor = useObservable(configService.watch("second-color"));
- const sessionExist = useObservable(authService.sessionExist$);
+ const languagesItems: RadioItem[] = i18nService.getSupportedLanguages().map((l, index) => {
+ return {id: index, text: t(`pages.settings.languages.${l}`), value: l, textIcon:t(`pages.settings.languages.translated.${l}`), icon: "trash"};
+ });
+
const[themeIdSelected, setThemeIdSelected]= useState(themeItem.find(e => e.value === themeService.getTheme()).id);
+ const[languageSelected, setLanguageSelected]= useState(languagesItems.find(e => e.value === i18nService.currentLanguage).id);
const [installationFolder, setInstallationFolder] = useState(null);
+ console.log(t("test"));
+
useEffect(() => {
loadInstallationFolder();
}, []);
@@ -58,6 +70,12 @@ export function SettingsPage() {
themeService.setTheme(themeItem.find(e => e.id === id).value);
}
+ const handleChangeLanguage = (id: number) => {
+ const selectedLanguage = languagesItems.find(l => l.id === id).value;
+ i18nService.setLanguage(selectedLanguage);
+ setLanguageSelected(languagesItems.find(l => l.value === i18nService.currentLanguage).id);
+ }
+
const setDefaultInstallationFolder = () => {
modalService.openModal(ModalType.INSTALLATION_FOLDER).then(async res => {
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
@@ -87,9 +105,9 @@ export function SettingsPage() {
};
return (
-
+
-
+
@@ -115,6 +133,12 @@ export function SettingsPage() {
+
+
+
+
+
+
)
diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx
index 14e08a65..ff46f9e1 100644
--- a/src/renderer/pages/version-viewer.component.tsx
+++ b/src/renderer/pages/version-viewer.component.tsx
@@ -37,8 +37,6 @@ export function VersionViewer() {
const bsDownloaderService = BsDownloaderService.getInstance();
const progressService = ProgressBarService.getInstance();
- const [launchRes, setLaunchRes] = useState(null);
-
useEffect(() => {
setOculusMode(!!configService.get
(LaunchMods.OCULUS_MOD));
setDesktopMode(!!configService.get(LaunchMods.DESKTOP_MOD));
diff --git a/src/renderer/services/i18n.service.ts b/src/renderer/services/i18n.service.ts
new file mode 100644
index 00000000..05ffc81e
--- /dev/null
+++ b/src/renderer/services/i18n.service.ts
@@ -0,0 +1,56 @@
+import { DefaultConfigKey } from "renderer/config/default-configuration.config";
+import { distinctUntilChanged, filter, map, Observable } from "rxjs";
+import { ConfigurationService } from "./configuration.service";
+import { getProperty } from "dot-prop";
+
+export class I18nService {
+
+ private static instance: I18nService;
+
+ private readonly LANG_CONFIG_KEY: DefaultConfigKey = "language";
+ private readonly LANG_FALLBACK = "en-EN";
+
+ private readonly configService: ConfigurationService;
+
+ private dictionary: Object;
+
+ public static getInstance(): I18nService{
+ if(!I18nService.instance){ I18nService.instance = new I18nService(); }
+ return I18nService.instance;
+ }
+
+ private constructor(){
+ this.configService = ConfigurationService.getInstance();
+
+ this.currentLanguage$.pipe(filter(l => !!l), distinctUntilChanged()).subscribe(lang => {
+ this.dictionary = require(`../../../assets/jsons/translations/${lang}.json`);
+ });
+ }
+
+ public getSupportedLanguages(): string[]{ return this.configService.get("supported_languages" as DefaultConfigKey); }
+ public getFallbackLanguage(): string{ return this.LANG_FALLBACK; }
+
+ public get currentLanguage(): string{
+ return this.getSupportedLanguages().includes(this.configService.get(this.LANG_CONFIG_KEY)) ? this.configService.get(this.LANG_CONFIG_KEY) : this.LANG_FALLBACK;
+ }
+ public get currentLanguage$(): Observable{
+ return this.configService.watch("language" as DefaultConfigKey).pipe(map(l => this.getSupportedLanguages().includes(l) ? l : this.LANG_FALLBACK));
+ }
+
+ public setLanguage(lang: string){
+ this.configService.set("language" as DefaultConfigKey, this.getSupportedLanguages().includes(lang) ? lang : this.LANG_FALLBACK);
+ }
+
+ public translate(translationKey: string): string{
+ return getProperty(this.dictionary, translationKey) || translationKey;
+ }
+
+
+
+
+
+
+
+
+
+}
\ No newline at end of file