diff --git a/web/projects/setup-wizard/src/app/pages/language.page.ts b/web/projects/setup-wizard/src/app/pages/language.page.ts index b14cc3d0b..f30953d1e 100644 --- a/web/projects/setup-wizard/src/app/pages/language.page.ts +++ b/web/projects/setup-wizard/src/app/pages/language.page.ts @@ -122,13 +122,13 @@ export default class LanguagePage { constructor() { if (this.selected) { - this.i18nService.setLanguage(this.selected.name) + this.i18nService.setLang(this.selected.name) } } onLanguageChange(language: Language) { if (language) { - this.i18nService.setLanguage(language.name) + this.i18nService.setLang(language.name) } } diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index 1aef6d5e3..a2bc6e74b 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -56,7 +56,7 @@ export const ENGLISH = { 'Beginning shutdown': 57, 'Add': 58, 'Ok': 59, - 'french': 60, + 'fr_FR': 60, 'This value cannot be changed once set': 61, 'Continue': 62, 'Click or drop file here': 63, @@ -462,10 +462,10 @@ export const ENGLISH = { 'StartOS UI': 485, 'WiFi': 486, 'Documentation': 487, // as in, a website to view documentation - 'spanish': 488, - 'polish': 489, - 'german': 490, - 'english': 491, + 'es_ES': 488, + 'pl_PL': 489, + 'de_DE': 490, + 'en_US': 491, 'Start Menu': 492, 'Install Progress': 493, 'Downloading': 494, @@ -670,9 +670,9 @@ export const ENGLISH = { 'Preserve': 706, 'Overwrite': 707, 'Unlock': 708, - 'Drive': 709, // as in, a storage device + 'Drive': 709, // the noun, a storage device 'Transfer': 710, // the verb 'The list is empty': 711, 'Restart now': 712, 'Later': 713, // as in, (do it) later -} as const +} as Record diff --git a/web/projects/shared/src/i18n/i18n.providers.ts b/web/projects/shared/src/i18n/i18n.providers.ts index cee7f1aa9..5aa38998c 100644 --- a/web/projects/shared/src/i18n/i18n.providers.ts +++ b/web/projects/shared/src/i18n/i18n.providers.ts @@ -6,7 +6,7 @@ import { TuiLanguageSwitcherService, } from '@taiga-ui/i18n' import { ENGLISH } from './dictionaries/en' -import { i18nService } from './i18n.service' +import { i18nService, Languages } from './i18n.service' export type i18nKey = keyof typeof ENGLISH export type i18n = Record<(typeof ENGLISH)[i18nKey], string> @@ -20,7 +20,7 @@ export const I18N_LOADER = new InjectionToken< >('') export const I18N_STORAGE = new InjectionToken< - (lang: TuiLanguageName) => Promise + (lang: Languages) => Promise >('', { factory: () => () => Promise.resolve(), }) diff --git a/web/projects/shared/src/i18n/i18n.service.ts b/web/projects/shared/src/i18n/i18n.service.ts index b627cddbf..28f1bc86a 100644 --- a/web/projects/shared/src/i18n/i18n.service.ts +++ b/web/projects/shared/src/i18n/i18n.service.ts @@ -2,6 +2,20 @@ import { inject, Injectable, signal } from '@angular/core' import { TuiLanguageName, TuiLanguageSwitcherService } from '@taiga-ui/i18n' import { I18N, I18N_LOADER, I18N_STORAGE } from './i18n.providers' +export const languages = ['en_US', 'es_ES', 'de_DE', 'fr_FR', 'pl_PL'] as const +export type Languages = (typeof languages)[number] + +/** + * Maps POSIX locale strings to TUI language names + */ +export const LANGUAGE_TO_TUI: Record = { + en_US: 'english', + es_ES: 'spanish', + de_DE: 'german', + fr_FR: 'french', + pl_PL: 'polish', +} + @Injectable({ providedIn: 'root', }) @@ -12,20 +26,32 @@ export class i18nService extends TuiLanguageSwitcherService { readonly loading = signal(false) - override setLanguage(language: TuiLanguageName = 'english'): void { + /** + * Current language as POSIX locale string + */ + get lang(): Languages { + return ( + (Object.entries(LANGUAGE_TO_TUI).find( + ([, tui]) => tui === this.language, + )?.[0] as Languages) || 'en_US' + ) + } + + setLang(language: Languages = 'en_US'): void { + const tuiLang = LANGUAGE_TO_TUI[language] const current = this.language - super.setLanguage(language) + super.setLanguage(tuiLang) this.loading.set(true) - if (current === language) { - this.i18nLoader(language).then(value => { + if (current === tuiLang) { + this.i18nLoader(tuiLang).then(value => { this.i18n.set(value) this.loading.set(false) }) } else { this.store(language).then(() => - this.i18nLoader(language).then(value => { + this.i18nLoader(tuiLang).then(value => { this.i18n.set(value) this.loading.set(false) }), @@ -33,12 +59,3 @@ export class i18nService extends TuiLanguageSwitcherService { } } } - -export const languages = [ - 'english', - 'spanish', - 'polish', - 'german', - 'french', -] as const -export type Languages = (typeof languages)[number] diff --git a/web/projects/shared/src/util/languages.ts b/web/projects/shared/src/util/languages.ts index b2b24c78a..baf1e03a4 100644 --- a/web/projects/shared/src/util/languages.ts +++ b/web/projects/shared/src/util/languages.ts @@ -18,22 +18,22 @@ export interface Language { * Available languages with their metadata */ export const LANGUAGES: Language[] = [ - { code: 'en', name: 'english', nativeName: 'English' }, - { code: 'es', name: 'spanish', nativeName: 'Español' }, - { code: 'de', name: 'german', nativeName: 'Deutsch' }, - { code: 'fr', name: 'french', nativeName: 'Français' }, - { code: 'pl', name: 'polish', nativeName: 'Polski' }, + { code: 'en', name: 'en_US', nativeName: 'English' }, + { code: 'es', name: 'es_ES', nativeName: 'Español' }, + { code: 'de', name: 'de_DE', nativeName: 'Deutsch' }, + { code: 'fr', name: 'fr_FR', nativeName: 'Français' }, + { code: 'pl', name: 'pl_PL', nativeName: 'Polski' }, ] /** - * Maps i18n language names to ISO language codes + * Maps POSIX locale strings to ISO language codes */ export const LANGUAGE_TO_CODE: Record = { - english: 'en', - spanish: 'es', - german: 'de', - french: 'fr', - polish: 'pl', + en_US: 'en', + es_ES: 'es', + de_DE: 'de', + fr_FR: 'fr', + pl_PL: 'pl', } /** diff --git a/web/projects/ui/src/app/app.component.ts b/web/projects/ui/src/app/app.component.ts index 0c359da07..1ef65c6fb 100644 --- a/web/projects/ui/src/app/app.component.ts +++ b/web/projects/ui/src/app/app.component.ts @@ -42,6 +42,6 @@ export class AppComponent { .watch$('serverInfo', 'language') .pipe(takeUntilDestroyed()) .subscribe(language => { - this.i18n.setLanguage(language || 'english') + this.i18n.setLang(language || 'en_US') }) } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts index d77640356..3adf6fc83 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts @@ -19,13 +19,13 @@ import { i18nService, Keyboard, KeyboardCode, - languages, - Languages, + Language, + LANGUAGES, LANGUAGE_TO_CODE, LoadingService, } from '@start9labs/shared' import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile' -import { TuiAnimated, TuiContext, TuiStringHandler } from '@taiga-ui/cdk' +import { TuiAnimated } from '@taiga-ui/cdk' import { TuiAppearance, TuiButton, @@ -110,20 +110,16 @@ import { KeyboardSelectComponent } from './keyboard-select.component' {{ 'Language' | i18n }} - - @if (language; as lang) { - {{ lang | i18n }} - } @else { - {{ i18nService.language }} - } + + {{ currentLanguage?.nativeName }} + + {{ item.nativeName }} +
@@ -297,17 +296,14 @@ export default class SystemGeneralComponent { readonly score = toSignal(this.patch.watch$('ui', 'snakeHighScore')) readonly os = inject(OSService) readonly i18nService = inject(i18nService) - readonly languages = languages - readonly translation: TuiStringHandler> = ({ - $implicit, - }) => { - const [head = '', ...result] = this.i18n.transform($implicit) || '' + readonly languages = LANGUAGES - return [head.toUpperCase(), ...result].join('') + get currentLanguage(): Language | undefined { + return LANGUAGES.find(lang => lang.name === this.i18nService.lang) } - get language(): Languages | undefined { - return this.languages.find(lang => lang === this.i18nService.language) + onLanguageChange(language: Language) { + this.i18nService.setLang(language.name) } // Expose shared utilities for template use diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 86e124d8a..06b1ef711 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -219,7 +219,7 @@ export const mockPatchData: DataModel = { ram: 8 * 1024 * 1024 * 1024, devices: [], kiosk: true, - language: 'english', + language: 'en_US', keyboard: { layout: 'us', model: null,