/* * Copyright (C) 2022 Kristóf Marussy * * This file is part of Sophie. * * Sophie is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * SPDX-License-Identifier: AGPL-3.0-only */ import { FALLBACK_LOCALE, SYSTEM_LOCALE } from '@sophie/shared'; import i18next, { i18n } from 'i18next'; import { reaction } from 'mobx'; import { addDisposer } from 'mobx-state-tree'; import type MainStore from '../stores/MainStore'; import getLogger from '../utils/getLogger'; import I18nStore from './I18nStore'; import type LocatlizationRepository from './LocalizationRepository'; import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend'; import i18nLog from './i18nLog'; const log = getLogger('loadLocationzation'); const TEST_LOCALE = 'cimode'; function getLanguage( language: string, systemLocale: string, supportedLanguages: string[], ): string { const selectedLanguage = language === SYSTEM_LOCALE ? systemLocale : language; if (selectedLanguage === TEST_LOCALE) { return selectedLanguage; } // Even though i18next has a `supportedLngs` array from which it can pick a supported language, // we still have to do this ourselves to avoid spurious warnings like // https://github.com/i18next/i18next/issues/1564 if (supportedLanguages.includes(selectedLanguage)) { return selectedLanguage; } if (selectedLanguage.includes('-')) { const iso639 = selectedLanguage.split('-')[0]; if (supportedLanguages.includes(iso639)) { return iso639; } } return FALLBACK_LOCALE; } function updateSharedStoreLanguage(store: MainStore, i18nInstance: i18n): void { const resolvedLanguage = i18nInstance.language === TEST_LOCALE ? TEST_LOCALE : i18nInstance.resolvedLanguage; const dir = i18nInstance.dir(); // We do not want to pass the list of supported languages to the renderer process, // so we extract the resolved languages from `i18n` as pass only that to the renderer. // Thus, the renderer always selects a language that is actually supported. store.shared.setLanguage(resolvedLanguage, dir); log.debug('Loaded language', resolvedLanguage, 'with direction', dir); } export default async function loadLocalization( store: MainStore, systemLocale: string, supportedLanguages: string[], repository: LocatlizationRepository, devMode: boolean, ): Promise { const backend = new RepositoryBasedI18nBackend(repository, devMode); const i18nInstance = i18next .createInstance({ lng: getLanguage( store.settings.language, systemLocale, supportedLanguages, ), supportedLngs: supportedLanguages, fallbackLng: [FALLBACK_LOCALE], debug: devMode, saveMissing: devMode, }) .use(backend) .use(i18nLog); const i18nStore = new I18nStore(i18nInstance); store.setI18n(i18nStore); await i18nInstance.init(); updateSharedStoreLanguage(store, i18nInstance); const disposeChangeLanguage = reaction( () => store.settings.language, (languageSetting) => { (async () => { const languageToSet = getLanguage( languageSetting, systemLocale, supportedLanguages, ); if (i18nInstance.language !== languageToSet) { await i18nInstance.changeLanguage(languageToSet); updateSharedStoreLanguage(store, i18nInstance); } })().catch((error) => { log.error('Failed to change language', error); }); }, { fireImmediately: true, }, ); addDisposer(store, disposeChangeLanguage); }