diff --git a/.github/labels.yml b/.github/labels.yml index 270bd72..0fdc1ca 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -70,3 +70,18 @@ - name: 'status: need more repro codes or info' description: Lacks enough info to make progress color: 'F9C90A' +- name: '๐Ÿงน p1-chore' + description: 'Priority 1: no change in change code behavior' + color: '#FDDFD7' +- name: '๐Ÿฐ p2-nice-to-have' + description: "Priority 2: nothing is broken but it's worth addressing" + color: '#0e8a16' +- name: '๐Ÿ”จ p3-minor-bug' + description: 'Priority 3: a bug in an edge case that only affects very specific usage' + color: '#fbca04' +- name: 'โ— p4-important' + description: 'Priority 4: bugs that violate documented behavior, or significantly impact perf' + color: '#d93f0b' +- name: '๐Ÿ”ฅ p5-urgent' + description: 'Priority 5: build-breaking bugs that affect most users and should be fixed ASAP' + color: '#ee0701' diff --git a/README.md b/README.md index 4496beb..8952aaf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,156 @@ # @intlify/h3 -Internationalization utilitis for h3 +[![npm version][npm-version-src]][npm-version-href] +[![npm downloads][npm-downloads-src]][npm-downloads-href] +[![CI][ci-src]][ci-href] -**NOTICE: This is a work in progress ๐Ÿ‘ท** +Internationalization middleware & utilitis for h3 + +## ๐ŸŒŸ Features + +โœ…๏ธ  **Translation:** Simple API like +[vue-i18n](https://vue-i18n.intlify.dev/) + +โœ…  **Custom locale detector:** You can implement your own locale detector +on server-side + +โœ…๏ธ๏ธ  **Useful utilities:** support internationalization composables +utilities via [@intlify/utils](https://github.com/intlify/utils) + +## ๐Ÿ’ฟ Installation + +```sh +# Using npm +npm install @intlify/h3 + +# Using yarn +yarn add @intlify/h3 + +# Using pnpm +pnpm add @intlify/h3 + +# Using bun +bun add @intlify/h3 +``` + +## ๐Ÿš€ Usage + +```ts +import { createServer } from 'node:http' +import { createApp, createRouter, eventHandler, toNodeListener } from 'h3' +import { + defineI18nMiddleware, + detectLocaleFromAcceptLanguageHeader, + useTranslation, +} from '@intlify/h3' + +// define middleware with vue-i18n like options +const middleware = defineI18nMiddleware({ + // detect locale with `accept-language` header + locale: detectLocaleFromAcceptLanguageHeader, + // resource messages + messages: { + en: { + hello: 'Hello {name}!', + }, + ja: { + hello: 'ใ“ใ‚“ใซใกใฏใ€{name}๏ผ', + }, + }, + // something options + // ... +}) + +// install middleware with `createApp` option +const app = createApp({ ...middleware }) + +const router = createRouter() +router.get( + '/', + eventHandler((event) => { + // use `useTranslation` in event handler + const t = useTranslation(event) + return t('hello', { name: 'h3' }) + }), +) + +app.use(router) +createServer(toNodeListener(app)).listen(3000) +``` + +## ๐Ÿ› ๏ธ Custom locale detection + +You can detect locale with your custom logic from current `H3Event`. + +example for detecting locale from url query header: + +```ts +import { defineI18nMiddleware, getQueryLocale } from '@intlify/h3' +import type { H3Event } from 'h3' + +const DEFAULT_LOCALE = 'en' + +// define custom locale detector +const localeDetector = (event: H3Event): string => { + try { + return getQueryLocale(event).toString() + } catch () { + return DEFAULT_LOCALE + } +} + +const middleware = defineI18nMiddleware({ + // set your custom locale detector + locale: localeDetector, + // something options + // ... +}) +``` + +## ๐Ÿ› ๏ธ Utilites & Helpers + +`@intlify/h3` has a concept of composable utilities & helpers. + +### Utilities + +`@intlify/h3` composable utilities accept event (from +`eventHandler((event) => {})`) as their first argument. (Exclud +`useTranslation`) return the +[`Intl.Locale`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale) + +### Translations + +- `useTranslation(event)`: use translation function + +### Headers + +- `getHeaderLocale(event)`: get locale from `accept-language` header +- `getHeaderLocales(event)`: get some locales from `accept-language` header + +### Cookies + +- `getCookieLocale()`: get locale from cookie +- `setCookieLocale()`: set locale to cookie + +### Misc + +- `getPathLocale(event)`: get locale from path +- `getQueryLocale(event)`: get locale from query + +## Helpers + +- `detectLocaleFromAcceptLanguageHeader`: detect locale from `accept-language` + header + +## ยฉ๏ธ License + +[MIT](http://opensource.org/licenses/MIT) + + + +[npm-version-src]: https://img.shields.io/npm/v/@intlify/h3?style=flat&colorA=18181B&colorB=FFAD33 +[npm-version-href]: https://npmjs.com/package/@intlify/h3 +[npm-downloads-src]: https://img.shields.io/npm/dm/@intlify/h3?style=flat&colorA=18181B&colorB=FFAD33 +[npm-downloads-href]: https://npmjs.com/package/@intlify/h3 +[ci-src]: https://github.com/intlify/utils/actions/workflows/ci.yml/badge.svg +[ci-href]: https://github.com/intlify/utils/actions/workflows/ci.yml diff --git a/bun.lockb b/bun.lockb index 38639ff..42172f6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 6d92a14..a314711 100644 --- a/package.json +++ b/package.json @@ -63,11 +63,15 @@ "@vitest/coverage-v8": "^0.34.4", "bumpp": "^9.2.0", "gh-changelogen": "^0.2.8", - "h3": "^1.8.1", + "h3": "^1.8.2", "lint-staged": "^14.0.0", "supertest": "^6.3.3", "typescript": "^5.2.2", "unbuild": "^2.0.0", - "vitest": "^0.34.4" + "vitest": "^1.0.0-beta.1" + }, + "dependencies": { + "@intlify/core": "npm:@intlify/core-edge@9.5.0-3caed81", + "@intlify/utils": "^0.8.0" } } diff --git a/spec/integration.spec.ts b/spec/integration.spec.ts new file mode 100644 index 0000000..ad2e321 --- /dev/null +++ b/spec/integration.spec.ts @@ -0,0 +1,88 @@ +import { afterEach, expect, test, vi } from 'vitest' +import { createApp, eventHandler, toNodeListener } from 'h3' +import { getQueryLocale } from '@intlify/utils/h3' +import supertest from 'supertest' + +import { + defineI18nMiddleware, + detectLocaleFromAcceptLanguageHeader, + useTranslation, +} from '../src/index.ts' + +import type { App, H3Event } from 'h3' +import type { SuperTest, Test } from 'supertest' + +let app: App +let request: SuperTest + +afterEach(() => { + vi.resetAllMocks() +}) + +test('translation', async () => { + const middleware = defineI18nMiddleware({ + locale: detectLocaleFromAcceptLanguageHeader, + messages: { + en: { + hello: 'hello, {name}', + }, + ja: { + hello: 'ใ“ใ‚“ใซใกใฏ, {name}', + }, + }, + }) + app = createApp({ ...middleware }) + request = supertest(toNodeListener(app)) + + app.use( + '/', + eventHandler((event) => { + const t = useTranslation(event) + return { message: t('hello', { name: 'h3' }) } + }), + ) + + const res = await request.get('/').set( + 'accept-language', + 'en;q=0.9,ja;q=0.8', + ) + expect(res.body).toEqual({ message: 'hello, h3' }) +}) + +test('custom locale detection', async () => { + const defaultLocale = 'en' + + // define custom locale detector + const localeDetector = (event: H3Event): string => { + try { + return getQueryLocale(event).toString() + } catch (_e) { + return defaultLocale + } + } + + const middleware = defineI18nMiddleware({ + locale: localeDetector, + messages: { + en: { + hello: 'hello, {name}', + }, + ja: { + hello: 'ใ“ใ‚“ใซใกใฏ, {name}', + }, + }, + }) + app = createApp({ ...middleware }) + request = supertest(toNodeListener(app)) + + app.use( + '/', + eventHandler((event) => { + const t = useTranslation(event) + return { message: t('hello', { name: 'h3' }) } + }), + ) + + const res = await request.get('/?locale=ja') + expect(res.body).toEqual({ message: 'ใ“ใ‚“ใซใกใฏ, h3' }) +}) diff --git a/src/index.ts b/src/index.ts index 78d3f10..f44ddbb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,73 +1,192 @@ -import { getCookie, getHeaders, setCookie } from 'h3' -import { isLocale, parseAcceptLanguage, validateLanguageTag } from './utils.ts' +import { + createCoreContext, + NOT_REOSLVED, + translate as _translate, +} from '@intlify/core' +import { getHeaderLocale } from '@intlify/utils/h3' -import type { H3Event } from 'h3' +export * from '@intlify/utils/h3' + +import type { AppOptions, H3Event } from 'h3' +import type { + CoreContext, + CoreOptions, + Locale, + LocaleDetector, +} from '@intlify/core' + +declare module 'h3' { + interface H3EventContext { + i18n?: CoreContext + } +} /** - * get accpet languages + * i18n middleware for h3 * - * @description parse `accept-language` header string - * - * @param {H3Event} event The {@link H3Event | H3} event - * - * @returns {Array} The array of language tags, if `*` (any language) or empty string is detected, return an empty array. + * @description + * The middleware for h3 [`createApp`]({@link https://www.jsdocs.io/package/h3#createApp}) */ -export function getAcceptLanguages(event: H3Event): string[] { - const headers = getHeaders(event) - const acceptLanguage = headers['accept-language'] - return acceptLanguage ? parseAcceptLanguage(acceptLanguage) : [] +export interface I18nMiddleware { + /** + * `onRequest` option of `createApp` + */ + onRequest: AppOptions['onRequest'] + /** + * `onAfterResponse` option of `createApp` + */ + onAfterResponse: AppOptions['onAfterResponse'] } /** - * get locale + * define i18n middleware for h3 + * + * @description + * Define the middleware to be specified for h3 [`createApp`]({@link https://www.jsdocs.io/package/h3#createApp}) * - * @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 {CoreOptions} options - An i18n options like vue-i18n [`createI18n`]({@link https://vue-i18n.intlify.dev/guide/#javascript}), which are passed to `createCoreContext` of `@intlify/core`, see about details [`CoreOptions` of `@intlify/core`](https://github.com/intlify/vue-i18n-next/blob/6a9947dd3e0fe90de7be9c87ea876b8779998de5/packages/core-base/src/context.ts#L196-L216) * - * @throws {RangeError} Throws a {@link RangeError} if `lang` option or `accpet-languages` are not a well-formed BCP 47 language tag. + * @returns {I18nMiddleware} A defined i18n middleware, which is included `onRequest` and `onAfterResponse` options of `createApp` * - * @returns {Intl.Locale} The locale that resolved from `accept-language` header string, first language tag is used. if `*` (any language) or empty string is detected, return `en-US`. + * @example + * + * ```js + * import { defineI18nMiddleware } from '@intlify/h3' + * + * const middleware = defineI18nMiddleware({ + * messages: { + * en: { + * hello: 'Hello {name}!', + * }, + * ja: { + * hello: 'ใ“ใ‚“ใซใกใฏใ€{name}๏ผ', + * }, + * }, + * // your locale detection logic here + * locale: (event) => { + * // ... + * }, + * }) + * + * const app = createApp({ ...middleware }) + * ``` */ -export function getLocale(event: H3Event, lang = 'en-US'): Intl.Locale { - const language = getAcceptLanguages(event)[0] || lang - return new Intl.Locale(language) +export function defineI18nMiddleware( + options: CoreOptions = {}, +): { + onRequest: AppOptions['onRequest'] + onAfterResponse: AppOptions['onAfterResponse'] +} { + const i18n = createCoreContext(options) + const orgLocale = i18n.locale + + let staticLocaleDetector: LocaleDetector | null = null + if (typeof orgLocale === 'string') { + console.warn( + `defineI18nMiddleware 'locale' option is static ${orgLocale} locale! you should specify dynamic locale detector function.`, + ) + staticLocaleDetector = () => orgLocale + } + + const getLocaleDetector = (event: H3Event): LocaleDetector => { + // deno-fmt-ignore + return typeof orgLocale === 'function' + ? orgLocale.bind(null, event) + : staticLocaleDetector != null + ? staticLocaleDetector.bind(null, event) + : detectLocaleFromAcceptLanguageHeader.bind(null, event) + } + + return { + onRequest(event: H3Event) { + i18n.locale = getLocaleDetector(event) + event.context.i18n = i18n as CoreContext + }, + onAfterResponse(event: H3Event) { + i18n.locale = orgLocale + delete event.context.i18n + }, + } } /** - * get locale from cookie + * locale detection with `Accept-Language` header + * + * @param {H3Event} event - A h3 event + * + * @returns {Locale} A locale string, which will be detected of **first** from `Accept-Language` header * - * @param {H3Event} event The {@link H3Event | H3} event - * @param {string} options.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} options.name The cookie name, default is `i18n_locale` + * @example + * ```js + * import { createApp } from 'h3' + * import { defineI18nMiddleware, detectLocaleWithAcceeptLanguageHeader } from '@intlify/h3' * - * @throws {RangeError} Throws a {@link RangeError} if `lang` option or cookie name value are not a well-formed BCP 47 language tag. + * const middleware = defineI18nMiddleware({ + * messages: { + * en: { + * hello: 'Hello {name}!', + * }, + * ja: { + * hello: 'ใ“ใ‚“ใซใกใฏใ€{name}๏ผ', + * }, + * }, + * locale: detectLocaleWithAcceeptLanguageHeader + * }) * - * @returns The locale that resolved from cookie + * const app = createApp({ ...middleware }) + * ``` */ -export function getCookieLocale( +export const detectLocaleFromAcceptLanguageHeader = ( event: H3Event, - { lang = 'en-US', name = 'i18n_locale' } = {}, -): Intl.Locale { - return new Intl.Locale(getCookie(event, name) || lang) -} +): Locale => getHeaderLocale(event).toString() + +// TODO: should support key completion +type TranslationFunction = ( + key: string, + ...args: unknown[] +) => string /** - * @param {H3Event} event The {@link H3Event | H3} event - * @param {string | Intl.Locale} locale The locale value - * @param options.name The cookie name, default is `i18n_locale` + * use translation function in event handler + * + * @description + * This function must be initialized with defineI18nMiddleware. See about the {@link defineI18nMiddleware} * - * @throws {SyntaxError} Throws a {@link SyntaxError} if `locale` is invalid. + * @param {H3Event} event - A h3 event + * + * @returns {TranslationFunction} Return a translation function, which can be translated with i18n resource messages + * + * @example + * ```js + * import { createRouter } from 'h3' + * + * const router = createRouter() + * router.get( + * '/', + * eventHandler((event) => { + * const t = useTranslation(event) + * return t('hello', { name: 'h3' }) + * }), + * ) + * ``` */ -export function setCookieLocale( - event: H3Event, - locale: string | Intl.Locale, - { name = 'i18n_locale' } = {}, -): void { - if ( - !(isLocale(locale) || - typeof locale === 'string' && validateLanguageTag(locale)) - ) { - throw new SyntaxError(`locale is invalid: ${locale.toString()}`) +// TODO: should support key completion +export function useTranslation(event: H3Event): TranslationFunction { + if (event.context.i18n == null) { + throw new Error( + 'middleware not initialized, please setup `onRequest` and `onAfterResponse` options of `createApp` with the middleware obtained with `defineI18nRequestMiddleware`', + ) } - setCookie(event, name, locale.toString()) + + // TODO: should design rest arguments + function translate(key: string, ...args: unknown[]): string { + const result = Reflect.apply(_translate, null, [ + event.context.i18n!, + key, + ...args, + ]) + return NOT_REOSLVED === result ? key : result as string + } + + return translate } diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index cd85fc4..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * parse `accept-language` header string - * - * @param {string} value The accept-language header string - * - * @returns {Array} 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 === '') - ) -} - -const objectToString = Object.prototype.toString -const toTypeString = (value: unknown): string => objectToString.call(value) - -/** - * check whether the value is a `Intl.Locale` instance - * - * @param {unknown} val The locale value - * - * @returns {boolean} Returns `true` if the value is a `Intl.Locale` instance, else `false`. - */ -export function isLocale(val: unknown): val is Intl.Locale { - return toTypeString(val) === '[object Intl.Locale]' -} - -/** - * 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 - } -} diff --git a/test/index.test.ts b/test/index.test.ts index 47e719f..d0fabcd 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,268 +1,97 @@ -import { beforeEach, describe, expect, test } from 'vitest' -import { createApp, eventHandler, toNodeListener } from 'h3' -import supertest from 'supertest' +import { describe, expect, test } from 'vitest' +import { createCoreContext } from '@intlify/core' + import { - getAcceptLanguages, - getCookieLocale, - getLocale, - setCookieLocale, + defineI18nMiddleware, + detectLocaleFromAcceptLanguageHeader, + useTranslation, } from '../src/index.ts' -import type { App, H3Event } from 'h3' -import type { SuperTest, Test } from 'supertest' - -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([]) - }) +import type { H3Event } from 'h3' +import type { CoreContext, LocaleDetector } from '@intlify/core' - test('empty', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: {}, +test('detectLocaleFromAcceptLanguageHeader', () => { + 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([]) - }) + }, + } as H3Event + expect(detectLocaleFromAcceptLanguageHeader(eventMock)).toBe('en-US') }) -describe('getLocale', () => { - test('basic', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: { - 'accept-language': 'en-US,en;q=0.9,ja;q=0.8', - }, - }, +test('defineI18nMiddleware', () => { + const middleware = defineI18nMiddleware({ + locale: detectLocaleFromAcceptLanguageHeader, + messages: { + en: { + hello: 'hello, {name}', }, - } as H3Event - const locale = getLocale(eventMock) - - expect(locale.baseName).toEqual('en-US') - expect(locale.language).toEqual('en') - expect(locale.region).toEqual('US') - }) - - test('accept-language is any language', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: { - 'accept-language': '*', - }, - }, + ja: { + hello: 'ใ“ใ‚“ใซใกใฏ, {name}', }, - } as H3Event - const locale = getLocale(eventMock) - - expect(locale.baseName).toEqual('en-US') - }) - - test('specify default language', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: { - 'accept-language': '*', - }, - }, - }, - } as H3Event - const locale = getLocale(eventMock, 'ja-JP') - - expect(locale.baseName).toEqual('ja-JP') - }) - - test('RangeError', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: { - 'accept-language': 's', - }, - }, - }, - } as H3Event - - expect(() => getLocale(eventMock, 'ja-JP')).toThrowError(RangeError) + }, }) + expect(middleware.onRequest).toBeDefined() + expect(middleware.onAfterResponse).toBeDefined() }) -describe('getCookieLocale', () => { +describe('useTranslation', () => { test('basic', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: { - cookie: 'i18n_locale=ja-US', - }, + /** + * setup `defineI18nMiddleware` emulates + */ + const context = createCoreContext({ + locale: detectLocaleFromAcceptLanguageHeader, + messages: { + en: { + hello: 'hello, {name}', }, - }, - } as H3Event - const locale = getCookieLocale(eventMock) - - expect(locale.baseName).toEqual('ja-US') - expect(locale.language).toEqual('ja') - expect(locale.region).toEqual('US') - }) - - test('cookie is empty', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: {}, - }, - }, - } as H3Event - const locale = getCookieLocale(eventMock) - - expect(locale.baseName).toEqual('en-US') - }) - - test('specify default language', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: {}, + ja: { + hello: 'ใ“ใ‚“ใซใกใฏ, {name}', }, }, - } as H3Event - const locale = getCookieLocale(eventMock, { lang: 'ja-JP' }) - - expect(locale.baseName).toEqual('ja-JP') - }) - - test('specify cookie name', () => { + }) const eventMock = { node: { req: { method: 'GET', headers: { - cookie: 'intlify_locale=fr-FR', + 'accept-language': 'ja,en', }, }, }, + context: { + i18n: context as CoreContext, + }, } as H3Event - const locale = getCookieLocale(eventMock, { name: 'intlify_locale' }) + const locale = context.locale as unknown + const bindLocaleDetector = (locale as LocaleDetector).bind(null, eventMock) + // @ts-ignore ignore type error because this is test + context.locale = bindLocaleDetector - expect(locale.baseName).toEqual('fr-FR') + // test `useTranslation` + const t = useTranslation(eventMock) + expect(t('hello', { name: 'h3' })).toEqual('ใ“ใ‚“ใซใกใฏ, h3') }) - test('RangeError', () => { + test('not initilize context', () => { const eventMock = { node: { req: { method: 'GET', headers: { - cookie: 'intlify_locale=f', + 'accept-language': 'ja,en', }, }, }, + context: {}, } as H3Event - expect(() => getCookieLocale(eventMock, { name: 'intlify_locale' })) - .toThrowError(RangeError) - }) -}) - -describe('setCookieLocale', () => { - let app: App - let request: SuperTest - - beforeEach(() => { - app = createApp({ debug: false }) - request = supertest(toNodeListener(app)) - }) - - test('specify Locale instance', async () => { - app.use( - '/', - eventHandler((event) => { - const locale = new Intl.Locale('ja-JP') - setCookieLocale(event, locale) - return '200' - }), - ) - const result = await request.get('/') - expect(result.headers['set-cookie']).toEqual([ - 'i18n_locale=ja-JP; Path=/', - ]) - }) - - test('specify language tag', async () => { - app.use( - '/', - eventHandler((event) => { - setCookieLocale(event, 'ja-JP') - return '200' - }), - ) - const result = await request.get('/') - expect(result.headers['set-cookie']).toEqual([ - 'i18n_locale=ja-JP; Path=/', - ]) - }) - - test('specify cookie name', async () => { - app.use( - '/', - eventHandler((event) => { - setCookieLocale(event, 'ja-JP', { name: 'intlify_locale' }) - return '200' - }), - ) - const result = await request.get('/') - expect(result.headers['set-cookie']).toEqual([ - 'intlify_locale=ja-JP; Path=/', - ]) - }) - - test('Syntax Error', () => { - const eventMock = { - node: { - req: { - method: 'GET', - headers: {}, - }, - }, - } as H3Event - - expect(() => setCookieLocale(eventMock, 'j')) - .toThrowError(/locale is invalid: j/) + expect(() => useTranslation(eventMock)).toThrowError() }) }) diff --git a/test/utils.test.ts b/test/utils.test.ts deleted file mode 100644 index 0ba3202..0000000 --- a/test/utils.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { - isLocale, - parseAcceptLanguage, - validateLanguageTag, -} from '../src/utils.ts' - -describe('parseAcceptLanguage', () => { - test('basic: ja,en-US;q=0.7,en;q=0.3', () => { - expect(parseAcceptLanguage('ja,en-US;q=0.7,en;q=0.3')).toEqual([ - 'ja', - 'en-US', - 'en', - ]) - }) - - test('q-factor nothing: ja,en-US', () => { - expect(parseAcceptLanguage('ja,en-US')).toEqual([ - 'ja', - 'en-US', - ]) - }) - - test('single: ja', () => { - expect(parseAcceptLanguage('ja')).toEqual([ - 'ja', - ]) - }) - - test('any language: *', () => { - expect(parseAcceptLanguage('*')).toEqual([]) - }) - - test('empty: ""', () => { - expect(parseAcceptLanguage('')).toEqual([]) - }) -}) - -describe('isLocale', () => { - test('Locale instance', () => { - expect(isLocale(new Intl.Locale('en-US'))).toBe(true) - }) - - test('not Locale instance', () => { - expect(isLocale('en-US')).toBe(false) - }) -}) - -describe('validateLanguageTag', () => { - test('valid', () => { - expect(validateLanguageTag('en-US')).toBe(true) - }) - - test('invalid', () => { - expect(validateLanguageTag('j')).toBe(false) - }) -})