aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/main/esbuild.config.js12
-rw-r--r--packages/main/package.json1
-rw-r--r--packages/main/src/i18n/loadLocalization.ts11
-rw-r--r--packages/main/src/i18n/synchronizeLocalizationSettings.ts99
-rw-r--r--packages/main/src/initReactions.ts41
-rw-r--r--packages/main/src/stores/SharedStore.ts3
-rw-r--r--packages/main/types/importMeta.d.ts1
-rw-r--r--packages/renderer/src/i18n/loadRendererLoalization.ts5
-rw-r--r--packages/shared/src/index.ts4
-rw-r--r--packages/shared/src/stores/GlobalSettingsBase.ts3
-rw-r--r--packages/shared/src/stores/SharedStoreBase.ts4
-rw-r--r--yarn.lock26
12 files changed, 183 insertions, 27 deletions
diff --git a/packages/main/esbuild.config.js b/packages/main/esbuild.config.js
index 996ec5a..ae8565d 100644
--- a/packages/main/esbuild.config.js
+++ b/packages/main/esbuild.config.js
@@ -1,9 +1,15 @@
1import { readdir } from 'node:fs/promises';
2import path from 'node:path';
3
1import getRepoInfo from 'git-repo-info'; 4import getRepoInfo from 'git-repo-info';
2 5
3import { node } from '../../config/buildConstants.js'; 6import { node } from '../../config/buildConstants.js';
4import fileUrlToDirname from '../../config/fileUrlToDirname.js'; 7import fileUrlToDirname from '../../config/fileUrlToDirname.js';
5import getEsbuildConfig from '../../config/getEsbuildConfig.js'; 8import getEsbuildConfig from '../../config/getEsbuildConfig.js';
6 9
10/** @type {string} */
11const thisDir = fileUrlToDirname(import.meta.url);
12
7const externalPackages = ['electron']; 13const externalPackages = ['electron'];
8 14
9if (process.env.MODE !== 'development') { 15if (process.env.MODE !== 'development') {
@@ -12,9 +18,12 @@ if (process.env.MODE !== 'development') {
12 18
13const gitInfo = getRepoInfo(); 19const gitInfo = getRepoInfo();
14 20
21/** @type {string[]} */
22const locales = await readdir(path.join(thisDir, '../../locales'));
23
15export default getEsbuildConfig( 24export default getEsbuildConfig(
16 { 25 {
17 absWorkingDir: fileUrlToDirname(import.meta.url), 26 absWorkingDir: thisDir,
18 entryPoints: ['src/index.ts'], 27 entryPoints: ['src/index.ts'],
19 outfile: 'dist/index.cjs', 28 outfile: 'dist/index.cjs',
20 format: 'cjs', 29 format: 'cjs',
@@ -27,5 +36,6 @@ export default getEsbuildConfig(
27 GIT_SHA: gitInfo.abbreviatedSha, 36 GIT_SHA: gitInfo.abbreviatedSha,
28 GIT_BRANCH: gitInfo.branch, 37 GIT_BRANCH: gitInfo.branch,
29 BUILD_DATE: Date.now(), 38 BUILD_DATE: Date.now(),
39 SUPPORTED_LOCALES: locales,
30 }, 40 },
31); 41);
diff --git a/packages/main/package.json b/packages/main/package.json
index 491e83a..c84816d 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -23,6 +23,7 @@
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",
26 "os-name": "^5.0.1", 27 "os-name": "^5.0.1",
27 "slug": "^5.3.0" 28 "slug": "^5.3.0"
28 }, 29 },
diff --git a/packages/main/src/i18n/loadLocalization.ts b/packages/main/src/i18n/loadLocalization.ts
index ec3cf84..0413373 100644
--- a/packages/main/src/i18n/loadLocalization.ts
+++ b/packages/main/src/i18n/loadLocalization.ts
@@ -18,33 +18,31 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { fallbackLng } from '@sophie/shared'; 21import { FALLBACK_LOCALE } from '@sophie/shared';
22import i18next from 'i18next'; 22import i18next from 'i18next';
23import { autorun } from 'mobx'; 23import { autorun } from 'mobx';
24import { addDisposer } from 'mobx-state-tree'; 24import { addDisposer } from 'mobx-state-tree';
25 25
26import type Resources from '../infrastructure/resources/Resources';
27import type MainStore from '../stores/MainStore'; 26import type MainStore from '../stores/MainStore';
28import { getLogger } from '../utils/log'; 27import { getLogger } from '../utils/log';
29 28
30import I18nStore from './I18nStore'; 29import I18nStore from './I18nStore';
30import LocatlizationRepository from './LocalizationRepository';
31import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend'; 31import RepositoryBasedI18nBackend from './RepositoryBasedI18nBackend';
32import i18nLog from './i18nLog'; 32import i18nLog from './i18nLog';
33import LocalizationFiles from './impl/LocaltizationFiles';
34 33
35const log = getLogger('loadLocationzation'); 34const log = getLogger('loadLocationzation');
36 35
37export default async function loadLocalization( 36export default async function loadLocalization(
38 store: MainStore, 37 store: MainStore,
39 resources: Resources, 38 repository: LocatlizationRepository,
40 devMode: boolean, 39 devMode: boolean,
41): Promise<void> { 40): Promise<void> {
42 const repository = new LocalizationFiles(resources);
43 const backend = new RepositoryBasedI18nBackend(repository, devMode); 41 const backend = new RepositoryBasedI18nBackend(repository, devMode);
44 const i18n = i18next 42 const i18n = i18next
45 .createInstance({ 43 .createInstance({
46 lng: store.shared.language, 44 lng: store.shared.language,
47 fallbackLng, 45 fallbackLng: [FALLBACK_LOCALE],
48 debug: devMode, 46 debug: devMode,
49 saveMissing: devMode, 47 saveMissing: devMode,
50 }) 48 })
@@ -58,6 +56,7 @@ export default async function loadLocalization(
58 shared: { language }, 56 shared: { language },
59 } = store; 57 } = store;
60 if (i18n.language !== language) { 58 if (i18n.language !== language) {
59 log.debug('Setting language', language);
61 i18n.changeLanguage(language).catch((error) => { 60 i18n.changeLanguage(language).catch((error) => {
62 log.error('Failed to change language', error); 61 log.error('Failed to change language', error);
63 }); 62 });
diff --git a/packages/main/src/i18n/synchronizeLocalizationSettings.ts b/packages/main/src/i18n/synchronizeLocalizationSettings.ts
new file mode 100644
index 0000000..971a593
--- /dev/null
+++ b/packages/main/src/i18n/synchronizeLocalizationSettings.ts
@@ -0,0 +1,99 @@
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 cdff551..b383c8f 100644
--- a/packages/main/src/initReactions.ts
+++ b/packages/main/src/initReactions.ts
@@ -19,8 +19,11 @@
19 */ 19 */
20 20
21import { app, session } from 'electron'; 21import { app, session } from 'electron';
22import { osLocale } from 'os-locale';
22 23
24import LocalizationFiles from './i18n/impl/LocaltizationFiles';
23import loadLocalization from './i18n/loadLocalization'; 25import loadLocalization from './i18n/loadLocalization';
26import { synchronizeLocalizationSettings } from './i18n/synchronizeLocalizationSettings';
24import ConfigFile from './infrastructure/config/impl/ConfigFile'; 27import ConfigFile from './infrastructure/config/impl/ConfigFile';
25import UserAgents from './infrastructure/electron/UserAgents'; 28import UserAgents from './infrastructure/electron/UserAgents';
26import ElectronViewFactory from './infrastructure/electron/impl/ElectronViewFactory'; 29import ElectronViewFactory from './infrastructure/electron/impl/ElectronViewFactory';
@@ -44,28 +47,36 @@ export default async function initReactions(
44 store.shared, 47 store.shared,
45 configRepository, 48 configRepository,
46 ); 49 );
50 const resources = getDistResources(devMode);
51 const localizationLoaded = (async () => {
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.
47 await app.whenReady(); 62 await app.whenReady();
48 const disposeNativeThemeController = synchronizeNativeTheme(store.shared); 63 const disposeNativeThemeController = synchronizeNativeTheme(store.shared);
49 const resources = getDistResources(devMode);
50 hardenSession(resources, devMode, session.defaultSession); 64 hardenSession(resources, devMode, session.defaultSession);
51 if (devMode) {
52 await installDevToolsExtensions();
53 }
54 const userAgents = new UserAgents(app.userAgentFallback); 65 const userAgents = new UserAgents(app.userAgentFallback);
55 app.userAgentFallback = userAgents.fallbackUserAgent(devMode); 66 app.userAgentFallback = userAgents.fallbackUserAgent(devMode);
56 const localizeInterface = async () => { 67 const devToolsLoaded = devMode
57 await loadLocalization(store, resources, devMode); 68 ? installDevToolsExtensions()
58 setApplicationMenu(store, devMode, isMac); 69 : Promise.resolve();
59 };
60 const localization = localizeInterface();
61 const viewFactory = new ElectronViewFactory(userAgents, resources, devMode); 70 const viewFactory = new ElectronViewFactory(userAgents, resources, devMode);
62 const [mainWindow] = await Promise.all([ 71 const mainWindow = (async () => {
63 viewFactory.createMainWindow(store), 72 await localizationLoaded;
64 viewFactory.loadServiceInject(), 73 setApplicationMenu(store, devMode, isMac);
65 ]); 74 await devToolsLoaded;
66 store.setMainWindow(mainWindow); 75 return viewFactory.createMainWindow(store);
76 })();
77 await viewFactory.loadServiceInject();
67 loadServices(store, viewFactory); 78 loadServices(store, viewFactory);
68 await localization; 79 store.setMainWindow(await mainWindow);
69 return () => { 80 return () => {
70 disposeNativeThemeController(); 81 disposeNativeThemeController();
71 disposeConfigController(); 82 disposeConfigController();
diff --git a/packages/main/src/stores/SharedStore.ts b/packages/main/src/stores/SharedStore.ts
index 67d58d6..960b65e 100644
--- a/packages/main/src/stores/SharedStore.ts
+++ b/packages/main/src/stores/SharedStore.ts
@@ -64,6 +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 {
68 self.language = langauge;
69 },
67 setShouldUseDarkColors(shouldUseDarkColors: boolean): void { 70 setShouldUseDarkColors(shouldUseDarkColors: boolean): void {
68 self.shouldUseDarkColors = shouldUseDarkColors; 71 self.shouldUseDarkColors = shouldUseDarkColors;
69 }, 72 },
diff --git a/packages/main/types/importMeta.d.ts b/packages/main/types/importMeta.d.ts
index 7426961..9818ca1 100644
--- a/packages/main/types/importMeta.d.ts
+++ b/packages/main/types/importMeta.d.ts
@@ -7,5 +7,6 @@ interface ImportMeta {
7 GIT_SHA: string; 7 GIT_SHA: string;
8 GIT_BRANCH: string; 8 GIT_BRANCH: string;
9 BUILD_DATE: number; 9 BUILD_DATE: number;
10 SUPPORTED_LOCALES: string[];
10 }; 11 };
11} 12}
diff --git a/packages/renderer/src/i18n/loadRendererLoalization.ts b/packages/renderer/src/i18n/loadRendererLoalization.ts
index 19d1e2d..b078aeb 100644
--- a/packages/renderer/src/i18n/loadRendererLoalization.ts
+++ b/packages/renderer/src/i18n/loadRendererLoalization.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 { fallbackLng, SophieRenderer } from '@sophie/shared'; 21import { FALLBACK_LOCALE, SophieRenderer } from '@sophie/shared';
22import i18next from 'i18next'; 22import i18next from 'i18next';
23import { autorun } from 'mobx'; 23import { autorun } from 'mobx';
24import { addDisposer } from 'mobx-state-tree'; 24import { addDisposer } from 'mobx-state-tree';
@@ -40,7 +40,7 @@ export default function loadRendererLocalization(
40 const i18n = i18next 40 const i18n = i18next
41 .createInstance({ 41 .createInstance({
42 lng: store.shared.language, 42 lng: store.shared.language,
43 fallbackLng, 43 fallbackLng: [FALLBACK_LOCALE],
44 interpolation: { 44 interpolation: {
45 escapeValue: false, // Not needed for react 45 escapeValue: false, // Not needed for react
46 }, 46 },
@@ -73,6 +73,7 @@ export default function loadRendererLocalization(
73 shared: { language }, 73 shared: { language },
74 } = store; 74 } = store;
75 if (i18n.language !== language) { 75 if (i18n.language !== language) {
76 log.debug('Setting language', language);
76 i18n.changeLanguage(language).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 });
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 51f9f06..c4de885 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -18,8 +18,6 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21export const fallbackLng = ['en'];
22
23export type { default as SophieRenderer } from './contextBridge/SophieRenderer'; 21export type { default as SophieRenderer } from './contextBridge/SophieRenderer';
24 22
25export { MainToRendererIpcMessage, RendererToMainIpcMessage } from './ipc'; 23export { MainToRendererIpcMessage, RendererToMainIpcMessage } from './ipc';
@@ -44,6 +42,7 @@ export type {
44export { 42export {
45 default as GlobalSettingsBase, 43 default as GlobalSettingsBase,
46 defineGlobalSettingsModel, 44 defineGlobalSettingsModel,
45 SYSTEM_LOCALE,
47} from './stores/GlobalSettingsBase'; 46} from './stores/GlobalSettingsBase';
48 47
49export { default as Profile } from './stores/Profile'; 48export { default as Profile } from './stores/Profile';
@@ -79,4 +78,5 @@ export type {
79export { 78export {
80 default as SharedStoreBase, 79 default as SharedStoreBase,
81 defineSharedStoreModel, 80 defineSharedStoreModel,
81 FALLBACK_LOCALE,
82} from './stores/SharedStoreBase'; 82} from './stores/SharedStoreBase';
diff --git a/packages/shared/src/stores/GlobalSettingsBase.ts b/packages/shared/src/stores/GlobalSettingsBase.ts
index 1bd0628..c74c822 100644
--- a/packages/shared/src/stores/GlobalSettingsBase.ts
+++ b/packages/shared/src/stores/GlobalSettingsBase.ts
@@ -30,10 +30,13 @@ import { ThemeSource } from '../schemas/ThemeSource';
30 30
31import ServiceBase from './ServiceBase'; 31import ServiceBase from './ServiceBase';
32 32
33export const SYSTEM_LOCALE = 'system';
34
33export function defineGlobalSettingsModel<TS extends IAnyModelType>( 35export function defineGlobalSettingsModel<TS extends IAnyModelType>(
34 service: TS, 36 service: TS,
35) { 37) {
36 return types.model('GlobalSettings', { 38 return types.model('GlobalSettings', {
39 language: SYSTEM_LOCALE,
37 themeSource: types.optional( 40 themeSource: types.optional(
38 types.enumeration(ThemeSource.options), 41 types.enumeration(ThemeSource.options),
39 'system', 42 'system',
diff --git a/packages/shared/src/stores/SharedStoreBase.ts b/packages/shared/src/stores/SharedStoreBase.ts
index 86bd0fc..a576a0e 100644
--- a/packages/shared/src/stores/SharedStoreBase.ts
+++ b/packages/shared/src/stores/SharedStoreBase.ts
@@ -31,6 +31,8 @@ import GlobalSettingsBase from './GlobalSettingsBase';
31import ProfileBase from './Profile'; 31import ProfileBase from './Profile';
32import ServiceBase from './ServiceBase'; 32import ServiceBase from './ServiceBase';
33 33
34export const FALLBACK_LOCALE = 'en';
35
34export function defineSharedStoreModel< 36export function defineSharedStoreModel<
35 TG extends IAnyModelType, 37 TG extends IAnyModelType,
36 TP extends IAnyModelType, 38 TP extends IAnyModelType,
@@ -43,7 +45,7 @@ export function defineSharedStoreModel<
43 servicesById: types.map(service), 45 servicesById: types.map(service),
44 services: types.array(types.reference(service)), 46 services: types.array(types.reference(service)),
45 shouldUseDarkColors: false, 47 shouldUseDarkColors: false,
46 language: 'en', 48 language: FALLBACK_LOCALE,
47 }); 49 });
48} 50}
49 51
diff --git a/yarn.lock b/yarn.lock
index 5a844c8..768f3c0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1297,6 +1297,7 @@ __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
1300 os-name: ^5.0.1 1301 os-name: ^5.0.1
1301 slug: ^5.3.0 1302 slug: ^5.3.0
1302 source-map-support: ^0.5.21 1303 source-map-support: ^0.5.21
@@ -5219,6 +5220,13 @@ __metadata:
5219 languageName: node 5220 languageName: node
5220 linkType: hard 5221 linkType: hard
5221 5222
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
5222"ip@npm:^1.1.5": 5230"ip@npm:^1.1.5":
5223 version: 1.1.5 5231 version: 1.1.5
5224 resolution: "ip@npm:1.1.5" 5232 resolution: "ip@npm:1.1.5"
@@ -6421,6 +6429,15 @@ __metadata:
6421 languageName: node 6429 languageName: node
6422 linkType: hard 6430 linkType: hard
6423 6431
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
6424"leven@npm:^3.1.0": 6441"leven@npm:^3.1.0":
6425 version: 3.1.0 6442 version: 3.1.0
6426 resolution: "leven@npm:3.1.0" 6443 resolution: "leven@npm:3.1.0"
@@ -7147,6 +7164,15 @@ __metadata:
7147 languageName: node 7164 languageName: node
7148 linkType: hard 7165 linkType: hard
7149 7166
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
7150"os-name@npm:^5.0.1": 7176"os-name@npm:^5.0.1":
7151 version: 5.0.1 7177 version: 5.0.1
7152 resolution: "os-name@npm:5.0.1" 7178 resolution: "os-name@npm:5.0.1"