Skip to content

Commit

Permalink
feat: support getPathLanguage and getPathLocale (#12)
Browse files Browse the repository at this point in the history
* feat: support `getPathLanguage` and `getPathLocale`

* add URL instance supporting
  • Loading branch information
kazupon committed Sep 27, 2023
1 parent da0eec5 commit 1777a4d
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 6 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/h3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions src/http.test.ts
Original file line number Diff line number Diff line change
@@ -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,
)
})
})
42 changes: 40 additions & 2 deletions src/http.ts
Original file line number Diff line number Diff line change
@@ -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 ...

Expand Down Expand Up @@ -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))
}
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 1 addition & 1 deletion src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions src/shared.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { describe, expect, test } from 'vitest'
import {
createPathIndexLanguageParser,
isLocale,
normalizeLanguageName,
parseAcceptLanguage,
pathLanguageParser,
validateLanguageTag,
} from './shared.ts'

Expand Down Expand Up @@ -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('')
})
})
46 changes: 46 additions & 0 deletions src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 1777a4d

Please sign in to comment.