Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add getAcceptLocales #7

Merged
merged 1 commit into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ You can do `import { ... } from '@intlify/utils'` the above utilities

- `getAcceptLanguages`
- `getAcceptLanguage`
- `getAcceptLocales`
- `getAcceptLocale`
- `getCookieLocale`
- `setCookieLocale`
Expand Down
44 changes: 44 additions & 0 deletions src/h3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getAcceptLanguage,
getAcceptLanguages,
getAcceptLocale,
getAcceptLocales,
getCookieLocale,
setCookieLocale,
} from './h3.ts'
Expand Down Expand Up @@ -97,6 +98,49 @@ describe('getAcceptLanguage', () => {
})
})

describe('getAcceptLocales', () => {
test('basic', () => {
const mockEvent = {
node: {
req: {
method: 'GET',
headers: {
'accept-language': 'en-US,en;q=0.9,ja;q=0.8',
},
},
},
} as H3Event
expect(getAcceptLocales(mockEvent).map((locale) => locale.baseName))
.toEqual(['en-US', 'en', 'ja'])
})

test('any language', () => {
const mockEvent = {
node: {
req: {
method: 'GET',
headers: {
'accept-language': '*',
},
},
},
} as H3Event
expect(getAcceptLocales(mockEvent)).toEqual([])
})

test('empty', () => {
const mockEvent = {
node: {
req: {
method: 'GET',
headers: {},
},
},
} as H3Event
expect(getAcceptLocales(mockEvent)).toEqual([])
})
})

describe('getAcceptLocale', () => {
test('basic', () => {
const mockEvent = {
Expand Down
44 changes: 37 additions & 7 deletions src/h3.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getAcceptLanguagesWithGetter,
getLocaleWithGetter,
mapToLocaleFromLanguageTag,
validateLocale,
} from './http.ts'
import { getCookie, getHeaders, setCookie } from 'h3'
Expand Down Expand Up @@ -70,28 +71,57 @@ export function getAcceptLanguage(event: H3Event): string {
}

/**
* get {@link Intl.Locale | locale} from `accept-language` header
* get locales from `accept-language` header
*
* @description wrap with {@link Intl.Locale | locale}
* @description wrap language tags with {@link Intl.Locale | locale}
*
* @example
* example for h3:
*
* ```ts
* import { createApp, eventHandler } from 'h3'
* import { getLocale } from '@intlify/utils/h3'
* import { getAcceptLocales } from '@intlify/utils/h3'
*
* app.use(eventHandler(event) => {
* const locale = getLocale(event)
* console.log(locale) // output `Intl.Locale` instance
* const locales = getAcceptLocales(event)
* // ...
* return `accepted locales: ${locales.map(locale => locale.toString()).join(', ')}`
* })
* ```
*
* @param {H3Event} event The {@link H3Event | H3} event
*
* @returns {Array<Intl.Locale>} The locales that wrapped from `accept-language` header, if `*` (any language) or empty string is detected, return an empty array.
*/
export function getAcceptLocales(
event: H3Event,
): Intl.Locale[] {
return mapToLocaleFromLanguageTag(getAcceptLanguages, event)
}

/**
* get locale from `accept-language` header
*
* @description wrap language tag with {@link Intl.Locale | locale}
*
* @example
* example for h3:
*
* ```ts
* import { createApp, eventHandler } from 'h3'
* import { getAcceptLocale } from '@intlify/utils/h3'
*
* app.use(eventHandler(event) => {
* const locale = getAcceptLocale(event)
* // ...
* return `accepted locale: ${locale.toString()}`
* })
* ```
*
* @param {H3Event} event The {@link H3Event | H3} event
* @param {string} lang The default language tag, default is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
* @param {string} lang The default language tag, Optional. default value is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
*
* @throws {RangeError} Throws a {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
* @throws {RangeError} Throws the {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
*
* @returns {Intl.Locale} The first locale that resolved from `accept-language` header string, first language tag is used. if `*` (any language) or empty string is detected, return `en-US`.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ export function validateLocale(locale: string | Intl.Locale): void {
}
}

export function mapToLocaleFromLanguageTag(
// deno-lint-ignore no-explicit-any
getter: (...args: any[]) => string[],
arg: unknown,
): Intl.Locale[] {
return (Reflect.apply(getter, null, [arg]) as string[]).map((lang) =>
getLocaleWithGetter(() => lang)
)
}

export function getExistCookies(
name: string,
getter: () => unknown,
Expand Down
29 changes: 29 additions & 0 deletions src/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getAcceptLanguage,
getAcceptLanguages,
getAcceptLocale,
getAcceptLocales,
getCookieLocale,
setCookieLocale,
} from './node.ts'
Expand Down Expand Up @@ -64,6 +65,34 @@ describe('getAcceptLanguage', () => {
})
})

describe('getAcceptLocales', () => {
test('basic', () => {
const mockRequest = {
headers: {
'accept-language': 'en-US,en;q=0.9,ja;q=0.8',
},
} as IncomingMessage
expect(getAcceptLocales(mockRequest).map((locale) => locale.baseName))
.toEqual(['en-US', 'en', 'ja'])
})

test('any language', () => {
const mockRequest = {
headers: {
'accept-language': '*',
},
} as IncomingMessage
expect(getAcceptLocales(mockRequest)).toEqual([])
})

test('empty', () => {
const mockRequest = {
headers: {},
} as IncomingMessage
expect(getAcceptLocales(mockRequest)).toEqual([])
})
})

describe('getAcceptLocale', () => {
test('basic', () => {
const mockRequest = {
Expand Down
46 changes: 39 additions & 7 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getAcceptLanguagesWithGetter,
getExistCookies,
getLocaleWithGetter,
mapToLocaleFromLanguageTag,
validateLocale,
} from './http.ts'
import { DEFAULT_COOKIE_NAME, DEFAULT_LANG_TAG } from './constants.ts'
Expand Down Expand Up @@ -68,28 +69,59 @@ export function getAcceptLanguage(request: IncomingMessage): string {
}

/**
* get {@link Intl.Locale | locale} from `accept-language` header
* get locales from `accept-language` header
*
* @description wrap with {@link Intl.Locale | locale}
* @description wrap language tags with {@link Intl.Locale | locale}
*
* @example
* example for Node.js request:
*
* ```ts
* import { createServer } from 'node:http'
* import { getLocale } from '@intlify/utils/node'
* import { getAcceptLocales } from '@intlify/utils/node'
*
* const server = createServer((req, res) => {
* const locale = getLocale(req)
* console.log(locale) // output `Intl.Locale` instance
* const locales = getAcceptLocales(req)
* // ...
* res.writeHead(200)
* res.end(`accpected locales: ${locales.map(locale => locale.toString()).join(', ')}`)
* })
* ```
*
* @param {IncomingMessage} request The {@link IncomingMessage | request}
*
* @returns {Array<Intl.Locale>} The locales that wrapped from `accept-language` header, if `*` (any language) or empty string is detected, return an empty array.
*/
export function getAcceptLocales(
request: IncomingMessage,
): Intl.Locale[] {
return mapToLocaleFromLanguageTag(getAcceptLanguages, request)
}

/**
* get locale from `accept-language` header
*
* @description wrap language tag with {@link Intl.Locale | locale}
*
* @example
* example for Node.js request:
*
* ```ts
* import { createServer } from 'node:http'
* import { getAcceptLocale } from '@intlify/utils/node'
*
* const server = createServer((req, res) => {
* const locale = getAcceptLocale(req)
* // ...
* res.writeHead(200)
* res.end(`accpected locale: ${locale.toString()}`)
* })
* ```
*
* @param {IncomingMessage} request The {@link IncomingMessage | request}
* @param {string} lang The default language tag, default is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
* @param {string} lang The default language tag, Optional. default value is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
*
* @throws {RangeError} Throws a {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
* @throws {RangeError} Throws the {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
*
* @returns {Intl.Locale} The first locale that resolved from `accept-language` header string, first language tag is used. if `*` (any language) or empty string is detected, return `en-US`.
*/
Expand Down
21 changes: 21 additions & 0 deletions src/web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getAcceptLanguage,
getAcceptLanguages,
getAcceptLocale,
getAcceptLocales,
getCookieLocale,
setCookieLocale,
} from './web.ts'
Expand All @@ -27,6 +28,26 @@ describe('getAcceptLanguages', () => {
})
})

describe('getAcceptLocales', () => {
test('basic', () => {
const mockRequest = new Request('https://example.com')
mockRequest.headers.set('accept-language', 'en-US,en;q=0.9,ja;q=0.8')
expect(getAcceptLocales(mockRequest).map((locale) => locale.baseName))
.toEqual(['en-US', 'en', 'ja'])
})

test('any language', () => {
const mockRequest = new Request('https://example.com')
mockRequest.headers.set('accept-language', '*')
expect(getAcceptLocales(mockRequest)).toEqual([])
})

test('empty', () => {
const mockRequest = new Request('https://example.com')
expect(getAcceptLocales(mockRequest)).toEqual([])
})
})

describe('getAcceptLanguage', () => {
test('basic', () => {
const mockRequest = new Request('https://example.com')
Expand Down
45 changes: 38 additions & 7 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getAcceptLanguagesWithGetter,
getExistCookies,
getLocaleWithGetter,
mapToLocaleFromLanguageTag,
validateLocale,
} from './http.ts'
import { DEFAULT_COOKIE_NAME, DEFAULT_LANG_TAG } from './constants.ts'
Expand Down Expand Up @@ -67,28 +68,58 @@ export function getAcceptLanguage(request: Request): string {
}

/**
* get {@link Intl.Locale | locale} from `accept-language` header
* get locales from `accept-language` header
*
* @description wrap with {@link Intl.Locale | locale}
* @description wrap language tags with {@link Intl.Locale | locale}
*
* @example
* example for Web API request on Bun:
*
* import { getLocale } from '@intlify/utils/web'
* import { getAcceptLocales } from '@intlify/utils/web'
*
* Bun.serve({
* port: 8080,
* fetch(req) {
* const locale = getLocale(req)
* console.log(locale) // output `Intl.Locale` instance
* const locales = getAcceptLocales(req)
* // ...
* return new Response(`accpected locales: ${locales.map(locale => locale.toString()).join(', ')}`)
* },
* })
* ```
*
* @param {Request} request The {@link Request | request}
*
* @returns {Array<Intl.Locale>} The locales that wrapped from `accept-language` header, if `*` (any language) or empty string is detected, return an empty array.
*/
export function getAcceptLocales(
request: Request,
): Intl.Locale[] {
return mapToLocaleFromLanguageTag(getAcceptLanguages, request)
}

/**
* get locale from `accept-language` header
*
* @description wrap language tag with {@link Intl.Locale | locale}
*
* @example
* example for Web API request on Bun:
*
* import { getAcceptLocale } from '@intlify/utils/web'
*
* Bun.serve({
* port: 8080,
* fetch(req) {
* const locale = getAcceptLocale(req)
* // ...
* return new Response(`accpected locale: ${locale.toString()}`)
* },
* })
*
* @param {Request} request The {@link Request | request}
* @param {string} lang The default language tag, default is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
* @param {string} lang The default language tag, Optional. default value is `en-US`. You must specify the language tag with the {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 syntax}.
*
* @throws {RangeError} Throws a {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
* @throws {RangeError} Throws the {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag.
*
* @returns {Intl.Locale} The first locale that resolved from `accept-language` header string, first language tag is used. if `*` (any language) or empty string is detected, return `en-US`.
*/
Expand Down