From f091f46c38ba3ec74ef643cf7614562d3365bfba Mon Sep 17 00:00:00 2001 From: whiteline7 Date: Thu, 8 Oct 2026 12:50:17 +0900 Subject: [PATCH 1/4] Add System lang selection Add a System selection to the Language. It will detect the device lang and apply it. If r2modman don't support that lang, it will show English by default. --- src-electron/ipc/electron-hook.ts | 8 ++- src-electron/preload/electron-preload.ts | 4 ++ src/App.vue | 3 +- src/env.d.ts | 1 + src/i18n/en/index.ts | 3 +- src/i18n/fr/index.ts | 1 + src/i18n/index.ts | 1 + src/i18n/ko/index.ts | 1 + src/r2mm/manager/LocaleManager.ts | 75 ++++++++++++++++++++++-- src/r2mm/manager/SettingsDexieStore.ts | 2 +- 10 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src-electron/ipc/electron-hook.ts b/src-electron/ipc/electron-hook.ts index 28af58f81..476265f3e 100644 --- a/src-electron/ipc/electron-hook.ts +++ b/src-electron/ipc/electron-hook.ts @@ -1,4 +1,4 @@ -import { BrowserWindow, ipcMain, shell, clipboard } from 'electron'; +import { BrowserWindow, ipcMain, shell, clipboard, app } from 'electron'; export function hookElectronIpc(browserWindow: BrowserWindow) { ipcMain.on('electron:shell:openExternal', (event, url) => { @@ -20,5 +20,9 @@ export function hookElectronIpc(browserWindow: BrowserWindow) { ipcMain.handle('electron:getEnvironmentVariables', (event) => { return JSON.stringify(process.env); - }) + }); + + ipcMain.handle('electron:getPreferredSystemLanguages', () => { + return app.getPreferredSystemLanguages(); + }); } diff --git a/src-electron/preload/electron-preload.ts b/src-electron/preload/electron-preload.ts index f87cbc366..5a3b2415f 100644 --- a/src-electron/preload/electron-preload.ts +++ b/src-electron/preload/electron-preload.ts @@ -34,3 +34,7 @@ export function copyToClipboard(value: string) { export async function getEnvironmentVariables() { return ipcRenderer.invoke('electron:getEnvironmentVariables'); } + +export async function getPreferredSystemLanguages(): Promise { + return ipcRenderer.invoke('electron:getPreferredSystemLanguages'); +} diff --git a/src/App.vue b/src/App.vue index da06c5f06..b6408b440 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,6 +17,7 @@ > {{ locale }} + @@ -78,7 +79,7 @@ import GameImageProvider, { provideGameImageImplementation } from './providers/g import { GameImageProviderImplementation } from './r2mm/image/GameImageProviderImpl'; import { useI18n } from 'vue-i18n'; import { ActivityDropdown, SelectOverlay } from './components/all'; -import LocaleManager from './r2mm/manager/LocaleManager'; +import LocaleManager, { SYSTEM_LOCALE } from './r2mm/manager/LocaleManager'; const store = baseStore; const router = useRouter(); diff --git a/src/env.d.ts b/src/env.d.ts index a69be8a4a..7282bcaea 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -68,6 +68,7 @@ declare global { openExternal: (path: string) => void; selectFile: (path: string) => void; getEnvironmentVariables: () => Promise; + getPreferredSystemLanguages: () => Promise; } } } diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index 32bd2148d..c6941bb21 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -18,7 +18,8 @@ import { Error404Translation } from './pages/Error404Translation'; const message: MessageFormat = { metadata: { name: 'English', - locale: 'en-US' + locale: 'en-US', + system: 'System', }, translations: { pages: { diff --git a/src/i18n/fr/index.ts b/src/i18n/fr/index.ts index 98aa4b8ce..64bb657b4 100644 --- a/src/i18n/fr/index.ts +++ b/src/i18n/fr/index.ts @@ -19,6 +19,7 @@ const message = { metadata: { name: 'Français', locale: 'fr-FR', + system: 'Système', wip: "WIP", }, translations: { diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 9ad57b897..e7c53fbb5 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -36,6 +36,7 @@ export const datetimeFormats = { export type MessageMetadata = { name: string; locale: string; + system: string; wip?: string; } diff --git a/src/i18n/ko/index.ts b/src/i18n/ko/index.ts index da33c344a..95e088745 100644 --- a/src/i18n/ko/index.ts +++ b/src/i18n/ko/index.ts @@ -20,6 +20,7 @@ const message: MessageFormat = { name: '한국어', locale: 'ko-KR', wip: "번역 중", + system: '시스템', }, translations: { pages: { diff --git a/src/r2mm/manager/LocaleManager.ts b/src/r2mm/manager/LocaleManager.ts index 598192774..492f886d1 100644 --- a/src/r2mm/manager/LocaleManager.ts +++ b/src/r2mm/manager/LocaleManager.ts @@ -3,25 +3,90 @@ import GameManager from '../../model/game/GameManager'; import { i18n } from '../../i18n/instance'; export const FALLBACK_LOCALE = 'en'; +export const SYSTEM_LOCALE = 'system'; export default class LocaleManager { public static async apply() { const settings = await ManagerSettings.getSingleton(GameManager.activeGame); await settings.load(); - LocaleManager.set(await settings.getLocale()); + + const locale = await settings.getLocale(); + + if (locale === SYSTEM_LOCALE) { + await LocaleManager.applySystemLocale(); + return; + } + + LocaleManager.set(locale); } public static async save(locale: string) { const settings = await ManagerSettings.getSingleton(GameManager.activeGame); await settings.setLocale(locale); + + if (locale === SYSTEM_LOCALE) { + await LocaleManager.applySystemLocale(); + return; + } + LocaleManager.set(locale); } + private static async applySystemLocale() { + try { + const preferredLanguages = await window.electron.getPreferredSystemLanguages(); + const locale = LocaleManager.findSupportedLocale(preferredLanguages); + + LocaleManager.set(locale); + } catch (error) { + console.error('Failed to apply system locale:', error); + LocaleManager.set(FALLBACK_LOCALE); + } + } + + private static findSupportedLocale(preferredLanguages: string[]): string { + const availableLocales = i18n.global.availableLocales; + + for (const language of preferredLanguages) { + const normalizedLanguage = language + .replace('_', '-') + .toLowerCase(); + + const languageCode = normalizedLanguage.split('-')[0]; + + for (const locale of availableLocales) { + const message = i18n.global.getLocaleMessage(locale) as any; + const metadataLocale = message?.metadata?.locale; + + if (typeof metadataLocale !== 'string') { + continue; + } + + const normalizedMetadataLocale = metadataLocale + .replace('_', '-') + .toLowerCase(); + + const metadataLanguageCode = normalizedMetadataLocale.split('-')[0]; + + if (normalizedMetadataLocale === normalizedLanguage) { + return locale; + } + + if (metadataLanguageCode === languageCode) { + return locale; + } + } + } + + return FALLBACK_LOCALE; + } + private static set(locale: string | undefined) { - i18n.global.locale.value = (locale !== undefined && i18n.global.availableLocales.includes(locale)) - ? locale - : FALLBACK_LOCALE; + i18n.global.locale.value = + (locale !== undefined && i18n.global.availableLocales.includes(locale)) + ? locale + : FALLBACK_LOCALE; } -} +} \ No newline at end of file diff --git a/src/r2mm/manager/SettingsDexieStore.ts b/src/r2mm/manager/SettingsDexieStore.ts index 4c59b772f..cb95a5235 100644 --- a/src/r2mm/manager/SettingsDexieStore.ts +++ b/src/r2mm/manager/SettingsDexieStore.ts @@ -122,7 +122,7 @@ export default class SettingsDexieStore extends Dexie { defaultStore: undefined, gameSelectionViewMode: GameSelectionViewMode.CARD, previewPanelWidth: 450, - locale: 'en', + locale: 'system', }, gameSpecific: { version: 2, From 102a948fadd4c62464ede10f49ef5aa465475dc5 Mon Sep 17 00:00:00 2001 From: whiteline7 Date: Thu, 8 Oct 2026 12:58:11 +0900 Subject: [PATCH 2/4] Add system selection Show "System" words in UI at every lang properly --- src/App.vue | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/App.vue b/src/App.vue index b6408b440..4079a5606 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,10 +12,13 @@