Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src-electron/ipc/electron-hook.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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();
});
}
4 changes: 4 additions & 0 deletions src-electron/preload/electron-preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ export function copyToClipboard(value: string) {
export async function getEnvironmentVariables() {
return ipcRenderer.invoke('electron:getEnvironmentVariables');
}

export async function getPreferredSystemLanguages(): Promise<string[]> {
return ipcRenderer.invoke('electron:getPreferredSystemLanguages');
}
25 changes: 19 additions & 6 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
<div>
<ActivityDropdown>
<button type="button" class="activity-bar__action"
:aria-label="`${t('metadata.name', 0, { locale })}`"
:aria-label="selectedLocale === SYSTEM_LOCALE
? t('metadata.system')
: t('metadata.name', 0, { locale })"
@click.stop.prevent="focusOnLanguageOverlaySearch"
>
<i class="fas fa-globe-europe"></i> {{ locale }}
<i class="fas fa-globe-europe"></i>
{{ selectedLocale === SYSTEM_LOCALE ? t('metadata.system') : locale }}
</button>

<template #popper>
<SelectOverlay/>
<SelectOverlay @select-locale="selectLocale" />
</template>
</ActivityDropdown>
</div>
Expand Down Expand Up @@ -78,14 +82,21 @@ 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();
provideStoreImplementation(() => store);

const quasar = useQuasar();
const { t, locale, availableLocales, messages } = useI18n();
const { t, locale } = useI18n();

const selectedLocale = ref<string>(SYSTEM_LOCALE);

async function selectLocale(localeValue: string) {
await LocaleManager.save(localeValue);
selectedLocale.value = localeValue;
}

document.addEventListener('auxclick', e => {
const target = e.target! as any;
Expand Down Expand Up @@ -129,6 +140,8 @@ BindLoaderImpl.bind();
onMounted(async () => {
const settings: ManagerSettings = await store.dispatch('resetActiveGame');

selectedLocale.value = await settings.getLocale();

await LocaleManager.apply();

hookBackgroundUpdateThunderstoreModList(router);
Expand Down Expand Up @@ -255,4 +268,4 @@ main {
flex: 1;
}
}
</style>
</style>
33 changes: 30 additions & 3 deletions src/components/v2/language/SelectOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<script lang="ts" setup>
import LocaleManager from '@r2/r2mm/manager/LocaleManager';
import { SYSTEM_LOCALE } from '@r2/r2mm/manager/LocaleManager';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';

const { t, availableLocales, messages } = useI18n();
const emit = defineEmits<{
(event: 'select-locale', locale: string): void;
}>();

const searchTerm = ref<string>("");

Expand All @@ -22,6 +25,13 @@ const languageOptions = computed(() => {
);
})

const showSystemOption = computed(() => {
const term = searchTerm.value.trim().toLowerCase();

return SYSTEM_LOCALE.includes(term)
|| t('metadata.system').toLowerCase().includes(term);
});

</script>

<template>
Expand All @@ -30,9 +40,26 @@ const languageOptions = computed(() => {
</div>
<div class="light-separator"></div>
<ul class="menu-list" ref="listRef" :style="{ minHeight: listHeight ? `${listHeight}px` : undefined }">
<li v-if="languageOptions.length === 0" class="locale-list__empty">No languages found</li>
<li v-if="showSystemOption">
<a
href="#"
v-close-popper
@click.prevent="emit('select-locale', SYSTEM_LOCALE)"
>
<div class="locale-list__item">
<span class="locale-list__text">
{{ t('metadata.system') }}
</span>
</div>
</a>
</li>
<li v-if="languageOptions.length === 0 && !showSystemOption"
class="locale-list__empty"
>
No languages found
</li>
<li v-for="lang in languageOptions" :key="`locale-select-${lang}`">
<a href="#" v-close-popper @click="() => LocaleManager.save(lang)">
<a href="#" v-close-popper @click.prevent="emit('select-locale', lang)">
<div class="locale-list__item">
<span class="locale-list__text">{{ lang }} ({{ t('metadata.name', 0, {locale: lang}) }})</span>
<span class="locale-list__divider"></span>
Expand Down
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ declare global {
openExternal: (path: string) => void;
selectFile: (path: string) => void;
getEnvironmentVariables: () => Promise<string>;
getPreferredSystemLanguages: () => Promise<string[]>;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const message = {
metadata: {
name: 'Français',
locale: 'fr-FR',
system: 'Système',
wip: "WIP",
},
translations: {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const datetimeFormats = {
export type MessageMetadata = {
name: string;
locale: string;
system: string;
wip?: string;
}

Expand Down
1 change: 1 addition & 0 deletions src/i18n/ko/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const message: MessageFormat = {
name: '한국어',
locale: 'ko-KR',
wip: "번역 중",
system: '시스템',
},
translations: {
pages: {
Expand Down
75 changes: 70 additions & 5 deletions src/r2mm/manager/LocaleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
}
2 changes: 1 addition & 1 deletion src/r2mm/manager/SettingsDexieStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default class SettingsDexieStore extends Dexie {
defaultStore: undefined,
gameSelectionViewMode: GameSelectionViewMode.CARD,
previewPanelWidth: 450,
locale: 'en',
locale: 'system',
},
gameSpecific: {
version: 2,
Expand Down