diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-04-03 18:56:00 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-05-16 00:54:59 +0200 |
commit | 7a926c4b223c8be46a1defab4a86104d617eaaf9 (patch) | |
tree | 5e50a787397ff72268c22c6dffc67432dc76e184 | |
parent | fix(main): Inconsistent RendererBridge snapshot (diff) | |
download | sophie-7a926c4b223c8be46a1defab4a86104d617eaaf9.tar.gz sophie-7a926c4b223c8be46a1defab4a86104d617eaaf9.tar.zst sophie-7a926c4b223c8be46a1defab4a86104d617eaaf9.zip |
refactor: Use i18next for language resolution
Due to https://github.com/i18next/i18next/issues/1564 we still have to
implement our own language resolution, but we can rely on
resolvedLanguage to determine which language to pass through to the
renderer.
We will use the language detected by chromium as the system locale, so
there is no need to use os-locale for detection any more.
We use i18next in the main process do resolve the language, then set the
resolve (not requested!) language in the renderer process to avoid doing
resolution twice. This avoids the need in the renderer process to know
the list of supported languages.
We set the language and the writing direction in HTML in the renderer.
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
-rw-r--r-- | packages/main/package.json | 1 | ||||
-rw-r--r-- | packages/main/src/i18n/loadLocalization.ts | 93 | ||||
-rw-r--r-- | packages/main/src/i18n/synchronizeLocalizationSettings.ts | 99 | ||||
-rw-r--r-- | packages/main/src/initReactions.ts | 21 | ||||
-rw-r--r-- | packages/main/src/stores/SharedStore.ts | 5 | ||||
-rw-r--r-- | packages/renderer/src/components/ThemeProvider.tsx | 3 | ||||
-rw-r--r-- | packages/renderer/src/i18n/loadRendererLoalization.ts | 27 | ||||
-rw-r--r-- | packages/renderer/src/index.tsx | 9 | ||||
-rw-r--r-- | packages/shared/src/index.ts | 2 | ||||
-rw-r--r-- | packages/shared/src/stores/SharedStoreBase.ts | 2 | ||||
-rw-r--r-- | packages/shared/src/stores/WritingDirection.ts | 32 | ||||
-rw-r--r-- | yarn.lock | 26 |
12 files changed, 150 insertions, 170 deletions
diff --git a/packages/main/package.json b/packages/main/package.json index c84816d..491e83a 100644 --- a/packages/main/package.json +++ b/packages/main/package.json | |||
@@ -23,7 +23,6 @@ | |||
23 | "mobx-state-tree": "^5.1.3", | 23 | "mobx-state-tree": "^5.1.3", |
24 | "ms": "^2.1.3", | 24 | "ms": "^2.1.3", |
25 | "nanoid": "^3.3.2", | 25 | "nanoid": "^3.3.2", |
26 | "os-locale": "^6.0.2", | ||
27 | "os-name": "^5.0.1", | 26 | "os-name": "^5.0.1", |
28 | "slug": "^5.3.0" | 27 | "slug": "^5.3.0" |
29 | }, | 28 | }, |
diff --git a/packages/main/src/i18n/loadLocalization.ts b/packages/main/src/i18n/loadLocalization.ts index 0413373..507075d 100644 --- a/packages/main/src/i18n/loadLocalization.ts +++ b/packages/main/src/i18n/loadLocalization.ts | |||
@@ -18,49 +18,108 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { FALLBACK_LOCALE } from '@sophie/shared'; | 21 | import { FALLBACK_LOCALE, SYSTEM_LOCALE } from '@sophie/shared'; |
22 | import i18next from 'i18next'; | 22 | import i18next, { i18n } from 'i18next'; |
23 | import { autorun } from 'mobx'; | 23 | import { reaction } from 'mobx'; |
24 | import { addDisposer } from 'mobx-state-tree'; | 24 | import { addDisposer } from 'mobx-state-tree'; |
25 | 25 | ||
26 | import type MainStore from '../stores/MainStore'; | 26 | import type MainStore from '../stores/MainStore'; |
27 | import { getLogger } from '../utils/log'; | 27 | import { getLogger } from '../utils/log'; |
28 | 28 | ||
29 | import I18nStore from './I18nStore'; | 29 | import I18nStore from './I18nStore'; |
30 | import LocatlizationRepository from './LocalizationRepository'; | 30 | import type LocatlizationRepository from './LocalizationRepository'; |
31 | import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend'; | 31 | import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend'; |
32 | import i18nLog from './i18nLog'; | 32 | import i18nLog from './i18nLog'; |
33 | 33 | ||
34 | const log = getLogger('loadLocationzation'); | 34 | const log = getLogger('loadLocationzation'); |
35 | 35 | ||
36 | const TEST_LOCALE = 'cimode'; | ||
37 | |||
38 | function getLanguage( | ||
39 | language: string, | ||
40 | systemLocale: string, | ||
41 | supportedLanguages: string[], | ||
42 | ): string { | ||
43 | const selectedLanguage = language === SYSTEM_LOCALE ? systemLocale : language; | ||
44 | if (selectedLanguage === TEST_LOCALE) { | ||
45 | return selectedLanguage; | ||
46 | } | ||
47 | // Even though i18next has a `supportedLngs` array from which it can pick a supported language, | ||
48 | // we still have to do this ourselves to avoid spurious warnings like | ||
49 | // https://github.com/i18next/i18next/issues/1564 | ||
50 | if (supportedLanguages.includes(selectedLanguage)) { | ||
51 | return selectedLanguage; | ||
52 | } | ||
53 | if (selectedLanguage.includes('-')) { | ||
54 | const iso639 = selectedLanguage.split('-')[0]; | ||
55 | if (supportedLanguages.includes(iso639)) { | ||
56 | return iso639; | ||
57 | } | ||
58 | } | ||
59 | return FALLBACK_LOCALE; | ||
60 | } | ||
61 | |||
62 | function updateSharedStoreLanguage(store: MainStore, i18nInstance: i18n): void { | ||
63 | const resolvedLanguage = | ||
64 | i18nInstance.language === TEST_LOCALE | ||
65 | ? TEST_LOCALE | ||
66 | : i18nInstance.resolvedLanguage; | ||
67 | const dir = i18nInstance.dir(); | ||
68 | // We do not want to pass the list of supported languages to the renderer process, | ||
69 | // so we extract the resolved languages from `i18n` as pass only that to the renderer. | ||
70 | // Thus, the renderer always selects a language that is actually supported. | ||
71 | store.shared.setLanguage(resolvedLanguage, dir); | ||
72 | log.debug('Loaded language', resolvedLanguage, 'with direction', dir); | ||
73 | } | ||
74 | |||
36 | export default async function loadLocalization( | 75 | export default async function loadLocalization( |
37 | store: MainStore, | 76 | store: MainStore, |
77 | systemLocale: string, | ||
78 | supportedLanguages: string[], | ||
38 | repository: LocatlizationRepository, | 79 | repository: LocatlizationRepository, |
39 | devMode: boolean, | 80 | devMode: boolean, |
40 | ): Promise<void> { | 81 | ): Promise<void> { |
41 | const backend = new RepositoryBasedI18nBackend(repository, devMode); | 82 | const backend = new RepositoryBasedI18nBackend(repository, devMode); |
42 | const i18n = i18next | 83 | const i18nInstance = i18next |
43 | .createInstance({ | 84 | .createInstance({ |
44 | lng: store.shared.language, | 85 | lng: getLanguage( |
86 | store.settings.language, | ||
87 | systemLocale, | ||
88 | supportedLanguages, | ||
89 | ), | ||
90 | supportedLngs: supportedLanguages, | ||
45 | fallbackLng: [FALLBACK_LOCALE], | 91 | fallbackLng: [FALLBACK_LOCALE], |
46 | debug: devMode, | 92 | debug: devMode, |
47 | saveMissing: devMode, | 93 | saveMissing: devMode, |
48 | }) | 94 | }) |
49 | .use(backend) | 95 | .use(backend) |
50 | .use(i18nLog); | 96 | .use(i18nLog); |
51 | const i18nStore = new I18nStore(i18n); | 97 | const i18nStore = new I18nStore(i18nInstance); |
52 | store.setI18n(i18nStore); | 98 | store.setI18n(i18nStore); |
53 | await i18n.init(); | 99 | |
54 | const disposeChangeLanguage = autorun(() => { | 100 | await i18nInstance.init(); |
55 | const { | 101 | updateSharedStoreLanguage(store, i18nInstance); |
56 | shared: { language }, | 102 | |
57 | } = store; | 103 | const disposeChangeLanguage = reaction( |
58 | if (i18n.language !== language) { | 104 | () => store.settings.language, |
59 | log.debug('Setting language', language); | 105 | (languageSetting) => { |
60 | i18n.changeLanguage(language).catch((error) => { | 106 | (async () => { |
107 | const languageToSet = getLanguage( | ||
108 | languageSetting, | ||
109 | systemLocale, | ||
110 | supportedLanguages, | ||
111 | ); | ||
112 | if (i18nInstance.language !== languageToSet) { | ||
113 | await i18nInstance.changeLanguage(languageToSet); | ||
114 | updateSharedStoreLanguage(store, i18nInstance); | ||
115 | } | ||
116 | })().catch((error) => { | ||
61 | log.error('Failed to change language', error); | 117 | log.error('Failed to change language', error); |
62 | }); | 118 | }); |
63 | } | 119 | }, |
64 | }); | 120 | { |
121 | fireImmediately: true, | ||
122 | }, | ||
123 | ); | ||
65 | addDisposer(store, disposeChangeLanguage); | 124 | addDisposer(store, disposeChangeLanguage); |
66 | } | 125 | } |
diff --git a/packages/main/src/i18n/synchronizeLocalizationSettings.ts b/packages/main/src/i18n/synchronizeLocalizationSettings.ts deleted file mode 100644 index 971a593..0000000 --- a/packages/main/src/i18n/synchronizeLocalizationSettings.ts +++ /dev/null | |||
@@ -1,99 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { FALLBACK_LOCALE, SYSTEM_LOCALE } from '@sophie/shared'; | ||
22 | import { reaction } from 'mobx'; | ||
23 | import { addDisposer } from 'mobx-state-tree'; | ||
24 | |||
25 | import type MainStore from '../stores/MainStore'; | ||
26 | import { getLogger } from '../utils/log'; | ||
27 | |||
28 | const log = getLogger('synchronizeLocalizationSettings'); | ||
29 | |||
30 | export const TEST_LOCALE = 'cimode'; | ||
31 | |||
32 | /** | ||
33 | * Finds the closes requested supported language for the requested one. | ||
34 | * | ||
35 | * If `language` is supported, this function will return it. | ||
36 | * Otherwise, it returns a supported language with the same ISO639 code but | ||
37 | * no country code (e.g., `en` for `en-GB`) if it exists. | ||
38 | * If no supported language matches, `FALLBACK_LOCALE` will be returned. | ||
39 | * | ||
40 | * @param language The requested language. | ||
41 | * @param supportedLanguages The set of supported languages. | ||
42 | * @returns The language to load. | ||
43 | */ | ||
44 | function getMatchingLocale( | ||
45 | language: string, | ||
46 | supportedLanguages: Set<string>, | ||
47 | ): string { | ||
48 | // Also let the test locale (i.e., show localization keys directly) through. | ||
49 | if (language === TEST_LOCALE || supportedLanguages.has(language)) { | ||
50 | return language; | ||
51 | } | ||
52 | const separatorIndex = language.indexOf('-'); | ||
53 | if (separatorIndex < 0) { | ||
54 | return FALLBACK_LOCALE; | ||
55 | } | ||
56 | const iso639 = language.slice(0, Math.max(0, separatorIndex)); | ||
57 | if (supportedLanguages.has(iso639)) { | ||
58 | return iso639; | ||
59 | } | ||
60 | return FALLBACK_LOCALE; | ||
61 | } | ||
62 | |||
63 | export async function synchronizeLocalizationSettings( | ||
64 | store: MainStore, | ||
65 | supportedLanguages: string[], | ||
66 | osLocale: () => Promise<string>, | ||
67 | ): Promise<void> { | ||
68 | const { settings, shared } = store; | ||
69 | const supportedLangaugesSet = new Set(supportedLanguages); | ||
70 | const setLanguageAsync = async (languageSetting: string) => { | ||
71 | const requestedLanguage = | ||
72 | languageSetting === SYSTEM_LOCALE ? await osLocale() : languageSetting; | ||
73 | const matchingLanguage = getMatchingLocale( | ||
74 | requestedLanguage, | ||
75 | supportedLangaugesSet, | ||
76 | ); | ||
77 | log.debug( | ||
78 | 'Setting language', | ||
79 | matchingLanguage, | ||
80 | 'for requested language', | ||
81 | requestedLanguage, | ||
82 | ); | ||
83 | shared.setLanguage(matchingLanguage); | ||
84 | }; | ||
85 | const disposer = reaction( | ||
86 | () => settings.language, | ||
87 | (languageSetting) => { | ||
88 | setLanguageAsync(languageSetting).catch((error) => { | ||
89 | log.error('Failed to update language', error); | ||
90 | }); | ||
91 | }, | ||
92 | { | ||
93 | fireImmediately: false, | ||
94 | }, | ||
95 | ); | ||
96 | addDisposer(store, disposer); | ||
97 | // Make sure that the language is already set when we resolve. | ||
98 | await setLanguageAsync(settings.language); | ||
99 | } | ||
diff --git a/packages/main/src/initReactions.ts b/packages/main/src/initReactions.ts index b383c8f..94b1f06 100644 --- a/packages/main/src/initReactions.ts +++ b/packages/main/src/initReactions.ts | |||
@@ -19,11 +19,9 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { app, session } from 'electron'; | 21 | import { app, session } from 'electron'; |
22 | import { osLocale } from 'os-locale'; | ||
23 | 22 | ||
24 | import LocalizationFiles from './i18n/impl/LocaltizationFiles'; | 23 | import LocalizationFiles from './i18n/impl/LocaltizationFiles'; |
25 | import loadLocalization from './i18n/loadLocalization'; | 24 | import loadLocalization from './i18n/loadLocalization'; |
26 | import { synchronizeLocalizationSettings } from './i18n/synchronizeLocalizationSettings'; | ||
27 | import ConfigFile from './infrastructure/config/impl/ConfigFile'; | 25 | import ConfigFile from './infrastructure/config/impl/ConfigFile'; |
28 | import UserAgents from './infrastructure/electron/UserAgents'; | 26 | import UserAgents from './infrastructure/electron/UserAgents'; |
29 | import ElectronViewFactory from './infrastructure/electron/impl/ElectronViewFactory'; | 27 | import ElectronViewFactory from './infrastructure/electron/impl/ElectronViewFactory'; |
@@ -48,18 +46,17 @@ export default async function initReactions( | |||
48 | configRepository, | 46 | configRepository, |
49 | ); | 47 | ); |
50 | const resources = getDistResources(devMode); | 48 | const resources = getDistResources(devMode); |
51 | const localizationLoaded = (async () => { | 49 | // Ideally, we would the the chromium `--lang` according to the settings store here, |
52 | const localizationFiles = new LocalizationFiles(resources); | ||
53 | await synchronizeLocalizationSettings( | ||
54 | store, | ||
55 | import.meta.env.SUPPORTED_LOCALES, | ||
56 | osLocale, | ||
57 | ); | ||
58 | await loadLocalization(store, localizationFiles, devMode); | ||
59 | })(); | ||
60 | // Ideally, we would the the chromium `--lang` here, | ||
61 | // but `app.isReady()` is often already `true`, so we're too late to do that. | 50 | // but `app.isReady()` is often already `true`, so we're too late to do that. |
62 | await app.whenReady(); | 51 | await app.whenReady(); |
52 | const localizationFiles = new LocalizationFiles(resources); | ||
53 | const localizationLoaded = loadLocalization( | ||
54 | store, | ||
55 | app.getLocale(), | ||
56 | import.meta.env.SUPPORTED_LOCALES, | ||
57 | localizationFiles, | ||
58 | devMode, | ||
59 | ); | ||
63 | const disposeNativeThemeController = synchronizeNativeTheme(store.shared); | 60 | const disposeNativeThemeController = synchronizeNativeTheme(store.shared); |
64 | hardenSession(resources, devMode, session.defaultSession); | 61 | hardenSession(resources, devMode, session.defaultSession); |
65 | const userAgents = new UserAgents(app.userAgentFallback); | 62 | const userAgents = new UserAgents(app.userAgentFallback); |
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts index 960b65e..3aa8c73 100644 --- a/packages/main/src/stores/SharedStore.ts +++ b/packages/main/src/stores/SharedStore.ts | |||
@@ -18,7 +18,7 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { defineSharedStoreModel } from '@sophie/shared'; | 21 | import { defineSharedStoreModel, WritingDirection } from '@sophie/shared'; |
22 | import { getSnapshot, Instance } from 'mobx-state-tree'; | 22 | import { getSnapshot, Instance } from 'mobx-state-tree'; |
23 | 23 | ||
24 | import { getLogger } from '../utils/log'; | 24 | import { getLogger } from '../utils/log'; |
@@ -64,8 +64,9 @@ const SharedStore = defineSharedStoreModel(GlobalSettings, Profile, Service) | |||
64 | loadConfig(config: Config): void { | 64 | loadConfig(config: Config): void { |
65 | loadConfig(self, config); | 65 | loadConfig(self, config); |
66 | }, | 66 | }, |
67 | setLanguage(langauge: string): void { | 67 | setLanguage(langauge: string, writingDirection: WritingDirection): void { |
68 | self.language = langauge; | 68 | self.language = langauge; |
69 | self.writingDirection = writingDirection; | ||
69 | }, | 70 | }, |
70 | setShouldUseDarkColors(shouldUseDarkColors: boolean): void { | 71 | setShouldUseDarkColors(shouldUseDarkColors: boolean): void { |
71 | self.shouldUseDarkColors = shouldUseDarkColors; | 72 | self.shouldUseDarkColors = shouldUseDarkColors; |
diff --git a/packages/renderer/src/components/ThemeProvider.tsx b/packages/renderer/src/components/ThemeProvider.tsx index 18dc2a8..581563b 100644 --- a/packages/renderer/src/components/ThemeProvider.tsx +++ b/packages/renderer/src/components/ThemeProvider.tsx | |||
@@ -30,10 +30,11 @@ import { useStore } from './StoreProvider'; | |||
30 | export default observer( | 30 | export default observer( |
31 | ({ children }: { children: JSX.Element | JSX.Element[] }) => { | 31 | ({ children }: { children: JSX.Element | JSX.Element[] }) => { |
32 | const { | 32 | const { |
33 | shared: { shouldUseDarkColors }, | 33 | shared: { shouldUseDarkColors, writingDirection }, |
34 | } = useStore(); | 34 | } = useStore(); |
35 | 35 | ||
36 | const theme = createTheme({ | 36 | const theme = createTheme({ |
37 | direction: writingDirection, | ||
37 | palette: { | 38 | palette: { |
38 | mode: shouldUseDarkColors ? 'dark' : 'light', | 39 | mode: shouldUseDarkColors ? 'dark' : 'light', |
39 | }, | 40 | }, |
diff --git a/packages/renderer/src/i18n/loadRendererLoalization.ts b/packages/renderer/src/i18n/loadRendererLoalization.ts index b078aeb..4d7b84a 100644 --- a/packages/renderer/src/i18n/loadRendererLoalization.ts +++ b/packages/renderer/src/i18n/loadRendererLoalization.ts | |||
@@ -20,7 +20,7 @@ | |||
20 | 20 | ||
21 | import { FALLBACK_LOCALE, SophieRenderer } from '@sophie/shared'; | 21 | import { FALLBACK_LOCALE, SophieRenderer } from '@sophie/shared'; |
22 | import i18next from 'i18next'; | 22 | import i18next from 'i18next'; |
23 | import { autorun } from 'mobx'; | 23 | import { reaction } from 'mobx'; |
24 | import { addDisposer } from 'mobx-state-tree'; | 24 | import { addDisposer } from 'mobx-state-tree'; |
25 | import { initReactI18next } from 'react-i18next'; | 25 | import { initReactI18next } from 'react-i18next'; |
26 | 26 | ||
@@ -68,17 +68,20 @@ export default function loadRendererLocalization( | |||
68 | } | 68 | } |
69 | 69 | ||
70 | await i18n.init(); | 70 | await i18n.init(); |
71 | const disposeChangeLanguage = autorun(() => { | 71 | |
72 | const { | 72 | const disposeChangeLanguage = reaction( |
73 | shared: { language }, | 73 | () => store.shared.language, |
74 | } = store; | 74 | (languageToSet) => { |
75 | if (i18n.language !== language) { | 75 | if (i18n.language !== languageToSet) { |
76 | log.debug('Setting language', language); | 76 | i18n.changeLanguage(languageToSet).catch((error) => { |
77 | i18n.changeLanguage(language).catch((error) => { | 77 | log.error('Failed to change language', error); |
78 | log.error('Failed to change language', error); | 78 | }); |
79 | }); | 79 | } |
80 | } | 80 | }, |
81 | }); | 81 | { |
82 | fireImmediately: true, | ||
83 | }, | ||
84 | ); | ||
82 | addDisposer(store, disposeChangeLanguage); | 85 | addDisposer(store, disposeChangeLanguage); |
83 | }; | 86 | }; |
84 | 87 | ||
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx index 37116db..60ef714 100644 --- a/packages/renderer/src/index.tsx +++ b/packages/renderer/src/index.tsx | |||
@@ -56,6 +56,15 @@ if (isDevelopment) { | |||
56 | 56 | ||
57 | loadRendererLocalization(store, ipc, isDevelopment); | 57 | loadRendererLocalization(store, ipc, isDevelopment); |
58 | 58 | ||
59 | const disposeSetHtmlLang = autorun(() => { | ||
60 | const { | ||
61 | shared: { language, writingDirection }, | ||
62 | } = store; | ||
63 | document.documentElement.lang = language; | ||
64 | document.documentElement.dir = writingDirection; | ||
65 | }); | ||
66 | addDisposer(store, disposeSetHtmlLang); | ||
67 | |||
59 | const disposeSetTitle = autorun(() => { | 68 | const disposeSetTitle = autorun(() => { |
60 | const titlePrefix = isDevelopment ? '[dev] ' : ''; | 69 | const titlePrefix = isDevelopment ? '[dev] ' : ''; |
61 | const serviceTitle = store.settings.selectedService?.title; | 70 | const serviceTitle = store.settings.selectedService?.title; |
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index c4de885..95af73a 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts | |||
@@ -80,3 +80,5 @@ export { | |||
80 | defineSharedStoreModel, | 80 | defineSharedStoreModel, |
81 | FALLBACK_LOCALE, | 81 | FALLBACK_LOCALE, |
82 | } from './stores/SharedStoreBase'; | 82 | } from './stores/SharedStoreBase'; |
83 | |||
84 | export { default as WritingDirection } from './stores/WritingDirection'; | ||
diff --git a/packages/shared/src/stores/SharedStoreBase.ts b/packages/shared/src/stores/SharedStoreBase.ts index a576a0e..bd71cea 100644 --- a/packages/shared/src/stores/SharedStoreBase.ts +++ b/packages/shared/src/stores/SharedStoreBase.ts | |||
@@ -30,6 +30,7 @@ import { | |||
30 | import GlobalSettingsBase from './GlobalSettingsBase'; | 30 | import GlobalSettingsBase from './GlobalSettingsBase'; |
31 | import ProfileBase from './Profile'; | 31 | import ProfileBase from './Profile'; |
32 | import ServiceBase from './ServiceBase'; | 32 | import ServiceBase from './ServiceBase'; |
33 | import WritingDirection from './WritingDirection'; | ||
33 | 34 | ||
34 | export const FALLBACK_LOCALE = 'en'; | 35 | export const FALLBACK_LOCALE = 'en'; |
35 | 36 | ||
@@ -46,6 +47,7 @@ export function defineSharedStoreModel< | |||
46 | services: types.array(types.reference(service)), | 47 | services: types.array(types.reference(service)), |
47 | shouldUseDarkColors: false, | 48 | shouldUseDarkColors: false, |
48 | language: FALLBACK_LOCALE, | 49 | language: FALLBACK_LOCALE, |
50 | writingDirection: types.optional(WritingDirection, 'ltr'), | ||
49 | }); | 51 | }); |
50 | } | 52 | } |
51 | 53 | ||
diff --git a/packages/shared/src/stores/WritingDirection.ts b/packages/shared/src/stores/WritingDirection.ts new file mode 100644 index 0000000..561a86b --- /dev/null +++ b/packages/shared/src/stores/WritingDirection.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { Instance, types } from 'mobx-state-tree'; | ||
22 | |||
23 | const WritingDirection = /* @__PURE__ */ (() => | ||
24 | types.enumeration('WritingDirection', ['ltr', 'rtl']))(); | ||
25 | |||
26 | /* | ||
27 | eslint-disable-next-line @typescript-eslint/no-redeclare -- | ||
28 | Intentionally naming the type the same as the store definition. | ||
29 | */ | ||
30 | type WritingDirection = Instance<typeof WritingDirection>; | ||
31 | |||
32 | export default WritingDirection; | ||
@@ -1297,7 +1297,6 @@ __metadata: | |||
1297 | mobx-state-tree: ^5.1.3 | 1297 | mobx-state-tree: ^5.1.3 |
1298 | ms: ^2.1.3 | 1298 | ms: ^2.1.3 |
1299 | nanoid: ^3.3.2 | 1299 | nanoid: ^3.3.2 |
1300 | os-locale: ^6.0.2 | ||
1301 | os-name: ^5.0.1 | 1300 | os-name: ^5.0.1 |
1302 | slug: ^5.3.0 | 1301 | slug: ^5.3.0 |
1303 | source-map-support: ^0.5.21 | 1302 | source-map-support: ^0.5.21 |
@@ -5220,13 +5219,6 @@ __metadata: | |||
5220 | languageName: node | 5219 | languageName: node |
5221 | linkType: hard | 5220 | linkType: hard |
5222 | 5221 | ||
5223 | "invert-kv@npm:^3.0.0": | ||
5224 | version: 3.0.1 | ||
5225 | resolution: "invert-kv@npm:3.0.1" | ||
5226 | checksum: 782c44c97f8b693006f5ba0995301754bf68d2160ec98fc34d96b266e2c28cc0c91d86c341ca058fe993bc3dd91f104f776a40f04b6c75254a9a1a0d716ac814 | ||
5227 | languageName: node | ||
5228 | linkType: hard | ||
5229 | |||
5230 | "ip@npm:^1.1.5": | 5222 | "ip@npm:^1.1.5": |
5231 | version: 1.1.5 | 5223 | version: 1.1.5 |
5232 | resolution: "ip@npm:1.1.5" | 5224 | resolution: "ip@npm:1.1.5" |
@@ -6429,15 +6421,6 @@ __metadata: | |||
6429 | languageName: node | 6421 | languageName: node |
6430 | linkType: hard | 6422 | linkType: hard |
6431 | 6423 | ||
6432 | "lcid@npm:^3.1.1": | ||
6433 | version: 3.1.1 | ||
6434 | resolution: "lcid@npm:3.1.1" | ||
6435 | dependencies: | ||
6436 | invert-kv: ^3.0.0 | ||
6437 | checksum: 7ebab7a2696a3cc6c6c9f25d957ef81dd2a8a2f48b7e2a9185e4bbcfc36d70cb633acf5fa5c9508f3d30badf23a303b1b6afe0bba8f0bb7d353d0f5d59c9ec1b | ||
6438 | languageName: node | ||
6439 | linkType: hard | ||
6440 | |||
6441 | "leven@npm:^3.1.0": | 6424 | "leven@npm:^3.1.0": |
6442 | version: 3.1.0 | 6425 | version: 3.1.0 |
6443 | resolution: "leven@npm:3.1.0" | 6426 | resolution: "leven@npm:3.1.0" |
@@ -7164,15 +7147,6 @@ __metadata: | |||
7164 | languageName: node | 7147 | languageName: node |
7165 | linkType: hard | 7148 | linkType: hard |
7166 | 7149 | ||
7167 | "os-locale@npm:^6.0.2": | ||
7168 | version: 6.0.2 | ||
7169 | resolution: "os-locale@npm:6.0.2" | ||
7170 | dependencies: | ||
7171 | lcid: ^3.1.1 | ||
7172 | checksum: 812d73334c8773b971bf7fd257b84d2ce7b85d5d2184370f2875fe0e51451f530d6f7c272de1faa0b9ff02d0d10dafd665b6425ed85489271705ab5738691a43 | ||
7173 | languageName: node | ||
7174 | linkType: hard | ||
7175 | |||
7176 | "os-name@npm:^5.0.1": | 7150 | "os-name@npm:^5.0.1": |
7177 | version: 5.0.1 | 7151 | version: 5.0.1 |
7178 | resolution: "os-name@npm:5.0.1" | 7152 | resolution: "os-name@npm:5.0.1" |