aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/i18n/loadLocalization.ts
blob: 19da9e2c3c41bb00588a56b548592f4c5a22c8ce (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * Copyright (C)  2022 Kristóf Marussy <kristof@marussy.com>
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * 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.js';
import getLogger from '../utils/getLogger.js';

import I18nStore from './I18nStore.js';
import type LocatlizationRepository from './LocalizationRepository.js';
import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend.js';
import i18nLog from './i18nLog.js';

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<void> {
  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);
}