From b5e1be738da437f318b5d445bdc6fb354a134fad Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 13 Oct 2023 13:50:35 +0900 Subject: [PATCH] feat: add `getNavigatorLocale` and `getNavigatorLocales` --- README.md | 2 ++ src/node.ts | 78 ++++++++++++++++++++++++++++++++++++++++++++++++- src/web.test.ts | 42 ++++++++++++++++++++++++++ src/web.ts | 30 +++++++++++++++++++ tsconfig.json | 12 ++++---- 5 files changed, 158 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6b87578..425793f 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,8 @@ You can do `import { ... } from '@intlify/utils'` the above utilities - `getNavigatorLanguages` - `getNavigatorLanguage` +- `getNavigatorLocales` +- `getNavigatorLocale` You can do `import { ... } from '@intlify/utils'` the above utilities diff --git a/src/node.ts b/src/node.ts index 4981ad6..fffc939 100644 --- a/src/node.ts +++ b/src/node.ts @@ -271,6 +271,18 @@ export function getNavigatorLanguages(): readonly string[] { return navigatorLanguages = [...langs].filter(Boolean) } +/** + * get navigator locales + * + * @description + * You can get some {@link Intl.Locale} from system environment variables. + * + * @returns {Array} + */ +export function getNavigatorLocales(): readonly Intl.Locale[] { + return getNavigatorLanguages().map((lang) => new Intl.Locale(lang)) +} + /** * in-source testing for `getNavigatorLanguages` */ @@ -327,6 +339,37 @@ if (import.meta.vitest) { expect(mockEnv).toHaveBeenCalledTimes(2) }) }) + + describe('getNavigatorLocales', () => { + afterEach(() => { + vi.resetAllMocks() + navigatorLanguages = undefined + }) + + test('basic', () => { + vi.spyOn(process, 'env', 'get').mockReturnValue({ + LC_ALL: 'en-GB', + LC_MESSAGES: 'en-US', + LANG: 'ja-JP', + LANGUAGE: 'en', + }) + + const values = [ + 'en-GB', + 'en-US', + 'ja-JP', + 'en', + ] + expect(getNavigatorLocales().map((locale) => locale.toString())).toEqual([ + 'en-GB', + 'en-US', + 'ja-JP', + 'en', + ]) + // cache checking + expect(navigatorLanguages).toEqual(values) + }) + }) } let navigatorLanguage = '' @@ -345,7 +388,19 @@ export function getNavigatorLanguage(): string { } /** - * in-source testing for `getNavigatorLanguage` + * get navigator locale + * + * @description + * You can get the {@link Intl.Locale} from system environment variables. + * + * @returns {Intl.Locale} + */ +export function getNavigatorLocale(): Intl.Locale { + return new Intl.Locale(getNavigatorLanguage()) +} + +/** + * in-source testing for `getNavigatorLanguage` and `getNavigatorLocale` */ if (import.meta.vitest) { const { describe, test, expect, afterEach, vi } = import.meta.vitest @@ -391,4 +446,25 @@ if (import.meta.vitest) { expect(mockEnv).toHaveBeenCalledTimes(2) }) }) + + describe('getNavigatorLocale', () => { + afterEach(() => { + vi.resetAllMocks() + navigatorLanguages = undefined + navigatorLanguage = '' + }) + + test('basic', () => { + vi.spyOn(process, 'env', 'get').mockReturnValue({ + LC_ALL: 'en-GB', + LC_MESSAGES: 'en-US', + LANG: 'ja-JP', + LANGUAGE: 'en', + }) + + expect(getNavigatorLocale().toString()).toEqual('en-GB') + // cache checking + expect(navigatorLanguage).toEqual('en-GB') + }) + }) } diff --git a/src/web.test.ts b/src/web.test.ts index 263b95f..18ad99f 100644 --- a/src/web.test.ts +++ b/src/web.test.ts @@ -7,6 +7,8 @@ import { getHeaderLocales, getNavigatorLanguage, getNavigatorLanguages, + getNavigatorLocale, + getNavigatorLocales, setCookieLocale, } from './web.ts' import { DEFAULT_COOKIE_NAME, DEFAULT_LANG_TAG } from './constants.ts' @@ -257,3 +259,43 @@ describe('getNavigatorLanguage', () => { ) }) }) + +describe('getNavigatorLocales', () => { + test('basic', () => { + vi.stubGlobal('navigator', { + languages: ['en-US', 'en', 'ja'], + }) + + expect(getNavigatorLocales().map((locale) => locale.toString())).toEqual([ + 'en-US', + 'en', + 'ja', + ]) + }) + + test('error', () => { + vi.stubGlobal('navigator', undefined) + + expect(() => getNavigatorLocales()).toThrowError( + /not support `navigator`/, + ) + }) +}) + +describe('getNavigatorLanguage', () => { + test('basic', () => { + vi.stubGlobal('navigator', { + language: 'en-US', + }) + + expect(getNavigatorLocale().toString()).toEqual('en-US') + }) + + test('error', () => { + vi.stubGlobal('navigator', undefined) + + expect(() => getNavigatorLocale()).toThrowError( + /not support `navigator`/, + ) + }) +}) diff --git a/src/web.ts b/src/web.ts index bc1e655..e4a6bd0 100644 --- a/src/web.ts +++ b/src/web.ts @@ -282,3 +282,33 @@ export function getNavigatorLanguage(): string { } return navigator.language } + +/** + * get navigator locales + * + * @description + * This function is a wrapper that maps in {@link Intl.Locale} in `navigator.languages`. + * This function return values depends on the environments. if you use this function on the browser, you can get the languages, that are set in the browser, else if you use this function on the server side (Deno only), that value is the languages set in the server. + * + * @throws Throws the {@link Error} if the `navigator` is not exists. + * + * @returns {Array} + */ +export function getNavigatorLocales(): readonly Intl.Locale[] { + return getNavigatorLanguages().map((lang) => new Intl.Locale(lang)) +} + +/** + * get navigator locale + * + * @description + * This function is the {@link Intl.Locale} wrapper of `navigator.language`. + * The value depends on the environments. if you use this function on the browser, you can get the languages, that are set in the browser, else if you use this function on the server side (Deno only), that value is the language set in the server. + * + * @throws Throws the {@link Error} if the `navigator` is not exists. + * + * @returns {Intl.Locale} + */ +export function getNavigatorLocale(): Intl.Locale { + return new Intl.Locale(getNavigatorLanguage()) +} diff --git a/tsconfig.json b/tsconfig.json index 6d8ee1a..6306cdb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,11 +12,13 @@ /* Language and Environment */ "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [ - // "ESNext", - // "DOM", - // "DOM.Iterable" - // ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": [ + "ESNext", + "ES2020.Intl", + "ESNext.Intl", + "DOM", + "DOM.Iterable" + ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */