Skip to content

Commit

Permalink
feat: add getAcceptLocales (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon authored Sep 22, 2023
1 parent e5babb6 commit 3803ffb
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 21 deletions.
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

0 comments on commit 3803ffb

Please sign in to comment.