-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support runtime aginostic
getAcceptLanguages
(#4)
* feat: add `parseAcceptLanguage` * feat: support framework/runtime aginostic `getAcceptLanguages`
- Loading branch information
Showing
13 changed files
with
233 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { describe, expect, test } from 'vitest' | ||
import { getAcceptLanguages } from './h3.ts' | ||
|
||
import type { H3Event } from 'h3' | ||
|
||
describe('getAcceptLanguages', () => { | ||
test('basic', () => { | ||
const eventMock = { | ||
node: { | ||
req: { | ||
method: 'GET', | ||
headers: { | ||
'accept-language': 'en-US,en;q=0.9,ja;q=0.8', | ||
}, | ||
}, | ||
}, | ||
} as H3Event | ||
expect(getAcceptLanguages(eventMock)).toEqual(['en-US', 'en', 'ja']) | ||
}) | ||
|
||
test('any language', () => { | ||
const eventMock = { | ||
node: { | ||
req: { | ||
method: 'GET', | ||
headers: { | ||
'accept-language': '*', | ||
}, | ||
}, | ||
}, | ||
} as H3Event | ||
expect(getAcceptLanguages(eventMock)).toEqual([]) | ||
}) | ||
|
||
test('empty', () => { | ||
const eventMock = { | ||
node: { | ||
req: { | ||
method: 'GET', | ||
headers: {}, | ||
}, | ||
}, | ||
} as H3Event | ||
expect(getAcceptLanguages(eventMock)).toEqual([]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { getAcceptLanguagesFromGetter } from './http.ts' | ||
import { getHeaders } from 'h3' | ||
|
||
import type { H3Event } from 'h3' | ||
|
||
/** | ||
* get accpet languages | ||
* | ||
* @description parse `accept-language` header string | ||
* | ||
* @param {H3Event} event The {@link H3Event | H3} event | ||
* | ||
* @returns {Array<string>} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. | ||
*/ | ||
export function getAcceptLanguages(event: H3Event): string[] { | ||
const getter = () => { | ||
const headers = getHeaders(event) | ||
return headers['accept-language'] | ||
} | ||
return getAcceptLanguagesFromGetter(getter) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { parseAcceptLanguage } from './shared.ts' | ||
|
||
export function getAcceptLanguagesFromGetter( | ||
getter: () => string | null | undefined, | ||
): string[] { | ||
const acceptLanguage = getter() | ||
return acceptLanguage ? parseAcceptLanguage(acceptLanguage) : [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1 @@ | ||
const objectToString = Object.prototype.toString | ||
const toTypeString = (value: unknown): string => objectToString.call(value) | ||
|
||
/** | ||
* check whether the value is a {@link Intl.Locale} instance | ||
* | ||
* @param {unknown} val The locale value | ||
* | ||
* @returns {boolean} Returns `true` if the value is a {@link Intl.Locale} instance, else `false`. | ||
*/ | ||
export function isLocale(val: unknown): val is Intl.Locale { | ||
return toTypeString(val) === '[object Intl.Locale]' | ||
} | ||
|
||
/** | ||
* parse `accept-language` header string | ||
* | ||
* @param {string} value The accept-language header string | ||
* | ||
* @returns {Array<string>} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. | ||
*/ | ||
export function parseAcceptLanguage(value: string): string[] { | ||
return value.split(',').map((tag) => tag.split(';')[0]).filter((tag) => | ||
!(tag === '*' || tag === '') | ||
) | ||
} | ||
|
||
/** | ||
* validate the language tag whether is a well-formed {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 language tag}. | ||
* | ||
* @param {string} lang a language tag | ||
* | ||
* @returns {boolean} Returns `true` if the language tag is valid, else `false`. | ||
*/ | ||
export function validateLanguageTag(lang: string): boolean { | ||
try { | ||
// TODO: if we have a better way to validate the language tag, we should use it. | ||
new Intl.Locale(lang) | ||
return true | ||
} catch { | ||
return false | ||
} | ||
} | ||
export * from './shared.ts' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { describe, expect, test } from 'vitest' | ||
import { getAcceptLanguages } from './node.ts' | ||
import { IncomingMessage } from 'node:http' | ||
|
||
describe('getAcceptLanguages', () => { | ||
test('basic', () => { | ||
const mockRequest = { | ||
headers: { | ||
'accept-language': 'en-US,en;q=0.9,ja;q=0.8', | ||
}, | ||
} as IncomingMessage | ||
expect(getAcceptLanguages(mockRequest)).toEqual(['en-US', 'en', 'ja']) | ||
}) | ||
|
||
test('any language', () => { | ||
const mockRequest = { | ||
headers: { | ||
'accept-language': '*', | ||
}, | ||
} as IncomingMessage | ||
expect(getAcceptLanguages(mockRequest)).toEqual([]) | ||
}) | ||
|
||
test('empty', () => { | ||
const mockRequest = { | ||
headers: {}, | ||
} as IncomingMessage | ||
expect(getAcceptLanguages(mockRequest)).toEqual([]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { IncomingMessage } from 'node:http' | ||
import { getAcceptLanguagesFromGetter } from './http.ts' | ||
|
||
/** | ||
* get accpet languages | ||
* | ||
* @description parse `accept-language` header string | ||
* | ||
* @param {IncomingMessage} event The {@link IncomingMessage | request} | ||
* | ||
* @returns {Array<string>} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. | ||
*/ | ||
export function getAcceptLanguages(req: IncomingMessage) { | ||
const getter = () => req.headers['accept-language'] | ||
return getAcceptLanguagesFromGetter(getter) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
const objectToString = Object.prototype.toString | ||
const toTypeString = (value: unknown): string => objectToString.call(value) | ||
|
||
/** | ||
* check whether the value is a {@link Intl.Locale} instance | ||
* | ||
* @param {unknown} val The locale value | ||
* | ||
* @returns {boolean} Returns `true` if the value is a {@link Intl.Locale} instance, else `false`. | ||
*/ | ||
export function isLocale(val: unknown): val is Intl.Locale { | ||
return toTypeString(val) === '[object Intl.Locale]' | ||
} | ||
|
||
/** | ||
* parse `accept-language` header string | ||
* | ||
* @param {string} value The accept-language header string | ||
* | ||
* @returns {Array<string>} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. | ||
*/ | ||
export function parseAcceptLanguage(value: string): string[] { | ||
return value.split(',').map((tag) => tag.split(';')[0]).filter((tag) => | ||
!(tag === '*' || tag === '') | ||
) | ||
} | ||
|
||
/** | ||
* validate the language tag whether is a well-formed {@link https://datatracker.ietf.org/doc/html/rfc4646#section-2.1 | BCP 47 language tag}. | ||
* | ||
* @param {string} lang a language tag | ||
* | ||
* @returns {boolean} Returns `true` if the language tag is valid, else `false`. | ||
*/ | ||
export function validateLanguageTag(lang: string): boolean { | ||
try { | ||
// TODO: if we have a better way to validate the language tag, we should use it. | ||
new Intl.Locale(lang) | ||
return true | ||
} catch { | ||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { describe, expect, test } from 'vitest' | ||
import { getAcceptLanguages } from './web.ts' | ||
|
||
describe('getAcceptLanguages', () => { | ||
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(getAcceptLanguages(mockRequest)).toEqual(['en-US', 'en', 'ja']) | ||
}) | ||
|
||
test('any language', () => { | ||
const mockRequest = new Request('https://example.com') | ||
mockRequest.headers.set('accept-language', '*') | ||
expect(getAcceptLanguages(mockRequest)).toEqual([]) | ||
}) | ||
|
||
test('empty', () => { | ||
const mockRequest = new Request('https://example.com') | ||
expect(getAcceptLanguages(mockRequest)).toEqual([]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { getAcceptLanguagesFromGetter } from './http.ts' | ||
|
||
/** | ||
* get accpet languages | ||
* | ||
* @description parse `accept-language` header string | ||
* | ||
* @param {Request} event The {@link Request | request} | ||
* | ||
* @returns {Array<string>} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. | ||
*/ | ||
export function getAcceptLanguages(req: Request) { | ||
const getter = () => req.headers.get('accept-language') | ||
return getAcceptLanguagesFromGetter(getter) | ||
} |