aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/main/package.json1
-rw-r--r--packages/main/src/i18n/loadLocalization.ts93
-rw-r--r--packages/main/src/i18n/synchronizeLocalizationSettings.ts99
-rw-r--r--packages/main/src/initReactions.ts21
-rw-r--r--packages/main/src/stores/SharedStore.ts5
-rw-r--r--packages/renderer/src/components/ThemeProvider.tsx3
-rw-r--r--packages/renderer/src/i18n/loadRendererLoalization.ts27
-rw-r--r--packages/renderer/src/index.tsx9
-rw-r--r--packages/shared/src/index.ts2
-rw-r--r--packages/shared/src/stores/SharedStoreBase.ts2
-rw-r--r--packages/shared/src/stores/WritingDirection.ts32
-rw-r--r--yarn.lock26
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
21import { FALLBACK_LOCALE } from '@sophie/shared'; 21import { FALLBACK_LOCALE, SYSTEM_LOCALE } from '@sophie/shared';
22import i18next from 'i18next'; 22import i18next, { i18n } from 'i18next';
23import { autorun } from 'mobx'; 23import { reaction } from 'mobx';
24import { addDisposer } from 'mobx-state-tree'; 24import { addDisposer } from 'mobx-state-tree';
25 25
26import type MainStore from '../stores/MainStore'; 26import type MainStore from '../stores/MainStore';
27import { getLogger } from '../utils/log'; 27import { getLogger } from '../utils/log';
28 28
29import I18nStore from './I18nStore'; 29import I18nStore from './I18nStore';
30import LocatlizationRepository from './LocalizationRepository'; 30import type LocatlizationRepository from './LocalizationRepository';
31import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend'; 31import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend';
32import i18nLog from './i18nLog'; 32import i18nLog from './i18nLog';
33 33
34const log = getLogger('loadLocationzation'); 34const log = getLogger('loadLocationzation');
35 35
36const TEST_LOCALE = 'cimode';
37
38function 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
62function 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
36export default async function loadLocalization( 75export 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
21import { FALLBACK_LOCALE, SYSTEM_LOCALE } from '@sophie/shared';
22import { reaction } from 'mobx';
23import { addDisposer } from 'mobx-state-tree';
24
25import type MainStore from '../stores/MainStore';
26import { getLogger } from '../utils/log';
27
28const log = getLogger('synchronizeLocalizationSettings');
29
30export 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 */
44function 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
63export 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
21import { app, session } from 'electron'; 21import { app, session } from 'electron';
22import { osLocale } from 'os-locale';
23 22
24import LocalizationFiles from './i18n/impl/LocaltizationFiles'; 23import LocalizationFiles from './i18n/impl/LocaltizationFiles';
25import loadLocalization from './i18n/loadLocalization'; 24import loadLocalization from './i18n/loadLocalization';
26import { synchronizeLocalizationSettings } from './i18n/synchronizeLocalizationSettings';
27import ConfigFile from './infrastructure/config/impl/ConfigFile'; 25import ConfigFile from './infrastructure/config/impl/ConfigFile';
28import UserAgents from './infrastructure/electron/UserAgents'; 26import UserAgents from './infrastructure/electron/UserAgents';
29import ElectronViewFactory from './infrastructure/electron/impl/ElectronViewFactory'; 27import 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
21import { defineSharedStoreModel } from '@sophie/shared'; 21import { defineSharedStoreModel, WritingDirection } from '@sophie/shared';
22import { getSnapshot, Instance } from 'mobx-state-tree'; 22import { getSnapshot, Instance } from 'mobx-state-tree';
23 23
24import { getLogger } from '../utils/log'; 24import { 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';
30export default observer( 30export 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
21import { FALLBACK_LOCALE, SophieRenderer } from '@sophie/shared'; 21import { FALLBACK_LOCALE, SophieRenderer } from '@sophie/shared';
22import i18next from 'i18next'; 22import i18next from 'i18next';
23import { autorun } from 'mobx'; 23import { reaction } from 'mobx';
24import { addDisposer } from 'mobx-state-tree'; 24import { addDisposer } from 'mobx-state-tree';
25import { initReactI18next } from 'react-i18next'; 25import { 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
57loadRendererLocalization(store, ipc, isDevelopment); 57loadRendererLocalization(store, ipc, isDevelopment);
58 58
59const disposeSetHtmlLang = autorun(() => {
60 const {
61 shared: { language, writingDirection },
62 } = store;
63 document.documentElement.lang = language;
64 document.documentElement.dir = writingDirection;
65});
66addDisposer(store, disposeSetHtmlLang);
67
59const disposeSetTitle = autorun(() => { 68const 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
84export { 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 {
30import GlobalSettingsBase from './GlobalSettingsBase'; 30import GlobalSettingsBase from './GlobalSettingsBase';
31import ProfileBase from './Profile'; 31import ProfileBase from './Profile';
32import ServiceBase from './ServiceBase'; 32import ServiceBase from './ServiceBase';
33import WritingDirection from './WritingDirection';
33 34
34export const FALLBACK_LOCALE = 'en'; 35export 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
21import { Instance, types } from 'mobx-state-tree';
22
23const 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*/
30type WritingDirection = Instance<typeof WritingDirection>;
31
32export default WritingDirection;
diff --git a/yarn.lock b/yarn.lock
index 768f3c0..5a844c8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"