diff --git a/README.md b/README.md index 9c53fb9..3b1ba87 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,8 @@ You can do `import { ... } from '@intlify/utils'` the above utilities - `getAcceptLocale` - `getCookieLocale` - `setCookieLocale` +- `getPathLanguage` +- `getPathLocale` The about utilies functions accpet Web APIs such as [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and diff --git a/src/h3.ts b/src/h3.ts index 128ac60..4413e43 100644 --- a/src/h3.ts +++ b/src/h3.ts @@ -155,7 +155,7 @@ export function getAcceptLocale( * * @throws {RangeError} Throws a {@link RangeError} if `lang` option or cookie name value are not a well-formed BCP 47 language tag. * - * @returns The locale that resolved from cookie + * @returns {Intl.Locale} The locale that resolved from cookie */ export function getCookieLocale( event: H3Event, diff --git a/src/http.test.ts b/src/http.test.ts new file mode 100644 index 0000000..7c1e428 --- /dev/null +++ b/src/http.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from 'vitest' +import { getPathLanguage, getPathLocale } from './http.ts' + +describe('getPathLanguage', () => { + test('basic', () => { + expect(getPathLanguage('/en/foo')).toBe('en') + }) + + test('URL instance', () => { + const url = new URL('https://example.com/en/foo') + expect(getPathLanguage(url)).toBe('en') + }) + + test('parser option', () => { + const nullLangParser = { + parse: () => 'null', + } + expect(getPathLanguage('/en/foo', nullLangParser)).toBe('null') + }) +}) + +describe('getPathLocale', () => { + test('basic', () => { + expect(getPathLocale('/en-US/foo').toString()).toBe('en-US') + }) + + test('URL instance', () => { + const url = new URL('https://example.com/ja-JP/foo') + expect(getPathLocale(url).toString()).toBe('ja-JP') + }) + + test('RangeError', () => { + const nullLangParser = { + parse: () => 'null', + } + expect(() => getPathLocale('/en/foo', nullLangParser)).toThrowError( + RangeError, + ) + }) +}) diff --git a/src/http.ts b/src/http.ts index 8101982..e73561d 100644 --- a/src/http.ts +++ b/src/http.ts @@ -1,6 +1,11 @@ -import { parseAcceptLanguage } from './shared.ts' -import { isLocale, validateLanguageTag } from './shared.ts' +import { + isLocale, + parseAcceptLanguage, + pathLanguageParser, + validateLanguageTag, +} from './shared.ts' +import type { PathLanguageParser } from './shared.ts' // import type { CookieSerializeOptions } from 'cookie-es' // NOTE: This is a copy of the type definition from `cookie-es` package, we want to avoid building error for this type definition ... @@ -146,3 +151,36 @@ export function getExistCookies( ) return setCookies as string[] } + +/** + * get the language from the path + * + * @param {string | URL} path the target path + * @param {PathLanguageParser} parser the path language parser, optional + * + * @returns {string} the language that is parsed by the path language parser + */ +export function getPathLanguage( + path: string | URL, + parser?: PathLanguageParser, +): string { + const _parser = parser || pathLanguageParser + return _parser.parse(path) +} + +/** + * get the locale from the path + * + * @param {string | URL} path the target path + * @param {PathLanguageParser} parser the path language parser, optional + * + * @throws {RangeError} Throws the {@link RangeError} if the language in the path, that is not a well-formed BCP 47 language tag. + * + * @returns {Intl.Locale} The locale that resolved from path + */ +export function getPathLocale( + path: string | URL, + parser?: PathLanguageParser, +): Intl.Locale { + return new Intl.Locale(getPathLanguage(path, parser)) +} diff --git a/src/index.ts b/src/index.ts index bd768da..26e7f73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,10 @@ -export * from './shared.ts' +export { + createPathIndexLanguageParser, + isLocale, + normalizeLanguageName, + parseAcceptLanguage, + registerPathLanguageParser, + validateLanguageTag, +} from './shared.ts' +export { getPathLanguage, getPathLocale } from './http.ts' export * from './web.ts' diff --git a/src/node.ts b/src/node.ts index 298f5ad..cab3c59 100644 --- a/src/node.ts +++ b/src/node.ts @@ -156,7 +156,7 @@ export function getAcceptLocale( * * @throws {RangeError} Throws a {@link RangeError} if `lang` option or cookie name value are not a well-formed BCP 47 language tag. * - * @returns The locale that resolved from cookie + * @returns {Intl.Locale} The locale that resolved from cookie */ export function getCookieLocale( request: IncomingMessage, diff --git a/src/shared.test.ts b/src/shared.test.ts index f2a7348..00cb349 100644 --- a/src/shared.test.ts +++ b/src/shared.test.ts @@ -1,8 +1,10 @@ import { describe, expect, test } from 'vitest' import { + createPathIndexLanguageParser, isLocale, normalizeLanguageName, parseAcceptLanguage, + pathLanguageParser, validateLanguageTag, } from './shared.ts' @@ -74,3 +76,18 @@ describe('normalizeLanguageName', () => { expect(normalizeLanguageName('')).toBe('') }) }) + +describe('PathIndexLanguageParser', () => { + test('default index: 0', () => { + expect(pathLanguageParser.parse('/en/hello')).toEqual('en') + }) + + test('index 1', () => { + const parser = createPathIndexLanguageParser(1) + expect(parser.parse('/hello/ja/bar')).toEqual('ja') + }) + + test('empty', () => { + expect(pathLanguageParser.parse('/')).toEqual('') + }) +}) diff --git a/src/shared.ts b/src/shared.ts index ff41a2e..75f70a7 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -63,3 +63,49 @@ export function normalizeLanguageName(langName: string): string { const [lang] = langName.split('.') return lang.replace(/_/g, '-') } + +/** + * path language parser + */ +export interface PathLanguageParser { + /** + * parse the path that is include language + * + * @param {string | URL} path the target path + * + * @returns {string} the language, if it cannot parse the path is not found, you need to return empty string (`''`) + */ + parse(path: string | URL): string +} + +export function createPathIndexLanguageParser( + index = 0, +): PathLanguageParser { + return { + parse(path: string | URL): string { + const rawPath = typeof path === 'string' ? path : path.pathname + const normalizedPath = rawPath.split('?')[0] + const parts = normalizedPath.split('/') + if (parts[0] === '') { + parts.shift() + } + return parts.length > index ? parts[index] || '' : '' + }, + } +} + +export let pathLanguageParser: PathLanguageParser = + /* #__PURE__*/ createPathIndexLanguageParser() + +/** + * register the path language parser + * + * @description register a parser to be used in the `getPathLanugage` utility function + * + * @param {PathLanguageParser} parser the path language parser + */ +export function registerPathLanguageParser( + parser: PathLanguageParser, +): void { + pathLanguageParser = parser +} diff --git a/src/web.ts b/src/web.ts index 172ad85..eb6b672 100644 --- a/src/web.ts +++ b/src/web.ts @@ -154,7 +154,7 @@ export function getAcceptLocale( * * @throws {RangeError} Throws a {@link RangeError} if `lang` option or cookie name value are not a well-formed BCP 47 language tag. * - * @returns The locale that resolved from cookie + * @returns {Intl.Locale} The locale that resolved from cookie */ export function getCookieLocale( request: Request,