diff --git a/packages/h3/README.md b/packages/h3/README.md index 168909c..e7f0621 100644 --- a/packages/h3/README.md +++ b/packages/h3/README.md @@ -149,11 +149,11 @@ export default defineNitroPlugin(nitroApp => { You can detect locale with your custom logic from current `H3Event`. -example for detecting locale from url query: +example for detecting locale from url query, and get locale with `getDetectorLocale` util: ```ts import { H3 } from 'h3' -import { intlify, getQueryLocale } from '@intlify/h3' +import { intlify, getQueryLocale, getDetectorLocale } from '@intlify/h3' import type { H3Event } from 'h3' @@ -172,6 +172,11 @@ const app = new H3({ }) ] }) + +app.get('/', async event => { + const locale = await getDetectorLocale(event) + console.log(`Current Locale: ${locale.language}`) +}) ``` You can make that function asynchronous. This is useful when loading resources along with locale detection. diff --git a/packages/h3/docs/functions/getDetectorLocale.md b/packages/h3/docs/functions/getDetectorLocale.md new file mode 100644 index 0000000..36a831d --- /dev/null +++ b/packages/h3/docs/functions/getDetectorLocale.md @@ -0,0 +1,41 @@ +[**@intlify/h3**](../index.md) + +*** + +[@intlify/h3](../index.md) / getDetectorLocale + +# Function: getDetectorLocale() + +```ts +function getDetectorLocale(event): Promise; +``` + +get a locale which is detected with locale detector. + +## Parameters + +| Parameter | Type | Description | +| ------ | ------ | ------ | +| `event` | `H3Event` | A H3 event | + +## Returns + +`Promise`\<`Locale`\> + +Return an `Intl.Locale` instance representing the detected locale + +## Description + +The locale obtainable via this function comes from the locale detector specified in the `locale` option of the [intlify](../variables/intlify.md) plugin. + +## Example + +```js +app.get( + '/', + async (event) => { + const locale = await getDetectorLocale(event) + return `Current Locale: ${locale.language}` + }, +) +``` diff --git a/packages/h3/docs/index.md b/packages/h3/docs/index.md index 8d10a1a..e875dde 100644 --- a/packages/h3/docs/index.md +++ b/packages/h3/docs/index.md @@ -18,6 +18,7 @@ Internationalization middleware & utilities for h3 | ------ | ------ | | [detectLocaleFromAcceptLanguageHeader](functions/detectLocaleFromAcceptLanguageHeader.md) | Locale detection with `Accept-Language` header | | [getCookieLocale](functions/getCookieLocale.md) | get locale from cookie | +| [getDetectorLocale](functions/getDetectorLocale.md) | get a locale which is detected with locale detector. | | [getHeaderLanguage](functions/getHeaderLanguage.md) | get language from header | | [getHeaderLanguages](functions/getHeaderLanguages.md) | get languages from header | | [getHeaderLocale](functions/getHeaderLocale.md) | get locale from header | diff --git a/packages/h3/src/index.test.ts b/packages/h3/src/index.test.ts index 481390d..db85d4f 100644 --- a/packages/h3/src/index.test.ts +++ b/packages/h3/src/index.test.ts @@ -5,6 +5,7 @@ import { SYMBOL_I18N, SYMBOL_I18N_LOCALE } from './symbols.ts' import { defineI18nMiddleware, detectLocaleFromAcceptLanguageHeader, + getDetectorLocale, useTranslation } from './index.ts' @@ -88,3 +89,27 @@ describe('useTranslation', () => { await expect(() => useTranslation(eventMock)).rejects.toThrowError() }) }) + +test('getDetectorLocale', async () => { + const context = createCoreContext({ + locale: detectLocaleFromAcceptLanguageHeader + }) + const eventMock = { + req: { + headers: { + get: _name => (_name === 'accept-language' ? 'ja;q=0.9,en;q=0.8' : '') + } + }, + context: { + [SYMBOL_I18N]: context as CoreContext + } + } as H3Event + 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 + eventMock.context[SYMBOL_I18N_LOCALE] = bindLocaleDetector + + const locale = await getDetectorLocale(eventMock) + expect(locale.language).toEqual('ja') +}) diff --git a/packages/h3/src/index.ts b/packages/h3/src/index.ts index a370411..3f099ea 100644 --- a/packages/h3/src/index.ts +++ b/packages/h3/src/index.ts @@ -261,6 +261,21 @@ export const detectLocaleFromAcceptLanguageHeader = (event: H3Event): Locale => */ export interface DefineLocaleMessage extends LocaleMessage {} +async function getLocaleAndEventContext(event: H3Event): Promise<[string, H3EventContext]> { + const context = getEventContext(event) + if (context[SYMBOL_I18N] == null) { + throw new Error( + 'plugin has not been initialized. Please check that the `intlify` plugin is installed correctly.' + ) + } + + const localeDetector = context[SYMBOL_I18N_LOCALE] as unknown as LocaleDetector + // Always await detector call - works for both sync and async detectors + // (awaiting a non-promise value returns it immediately) + const locale = await localeDetector(event) + return [locale, context] +} + /** * Use translation function in event handler * @@ -283,19 +298,8 @@ export async function useTranslation< Schema extends Record = {}, // eslint-disable-line @typescript-eslint/no-explicit-any -- NOTE(kazupon): generic type Event extends H3Event = H3Event >(event: Event): Promise> { - const context = getEventContext(event) - if (context[SYMBOL_I18N] == null) { - throw new Error( - 'middleware not initialized, please setup `onRequest` and `onResponse` options of `H3` with the middleware obtained with `defineI18nMiddleware`' - ) - } - - const localeDetector = context[SYMBOL_I18N_LOCALE] as unknown as LocaleDetector - // Always await detector call - works for both sync and async detectors - // (awaiting a non-promise value returns it immediately) - const locale = await localeDetector(event) - context[SYMBOL_I18N].locale = locale - + const [locale, context] = await getLocaleAndEventContext(event) + context[SYMBOL_I18N]!.locale = locale function translate(key: string, ...args: unknown[]): string { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call -- NOTE(kazupon): generic type const [_, options] = parseTranslateArgs(key, ...args) @@ -317,3 +321,27 @@ export async function useTranslation< return translate as TranslationFunction } + +/** + * get a locale which is detected with locale detector. + * + * @description The locale obtainable via this function comes from the locale detector specified in the `locale` option of the {@link intlify} plugin. + * + * @example + * ```js + * app.get( + * '/', + * async (event) => { + * const locale = await getDetectorLocale(event) + * return `Current Locale: ${locale.language}` + * }, + * ) + * ``` + * @param event - A H3 event + * + * @returns Return an {@linkcode Intl.Locale} instance representing the detected locale + */ +export async function getDetectorLocale(event: H3Event): Promise { + const result = await getLocaleAndEventContext(event) + return new Intl.Locale(result[0]) +} diff --git a/packages/hono/README.md b/packages/hono/README.md index b3d6f2a..f15e1c0 100644 --- a/packages/hono/README.md +++ b/packages/hono/README.md @@ -117,10 +117,11 @@ export default app You can detect locale with your custom logic from current `Context`. -example for detecting locale from url query: +example for detecting locale from url query, and get locale with `getDetectorLocale` util: ```ts -import { defineI18nMiddleware, getQueryLocale } from '@intlify/hono' +import { Hono } from 'hono' +import { defineI18nMiddleware, getQueryLocale, getDetectorLocale } from '@intlify/hono' import type { Context } from 'hono' const DEFAULT_LOCALE = 'en' @@ -134,12 +135,19 @@ const localeDetector = (ctx: Context): string => { } } -const middleware = defineI18nMiddleware({ +const i18nMiddleware = defineI18nMiddleware({ // set your custom locale detector locale: localeDetector // something options // ... }) + +const app = new Hono() +app.use('*', i18nMiddleware) +app.get('/', async ctx => { + const locale = await getDetectorLocale(ctx) + return ctx.text(`Current Locale: ${locale.language}`) +}) ``` You can make that function asynchronous. This is useful when loading resources along with locale detection. diff --git a/packages/hono/docs/functions/getDetectorLocale.md b/packages/hono/docs/functions/getDetectorLocale.md new file mode 100644 index 0000000..9e0d31f --- /dev/null +++ b/packages/hono/docs/functions/getDetectorLocale.md @@ -0,0 +1,41 @@ +[**@intlify/hono**](../index.md) + +*** + +[@intlify/hono](../index.md) / getDetectorLocale + +# Function: getDetectorLocale() + +```ts +function getDetectorLocale(ctx): Promise; +``` + +get a locale which is detected with locale detector. + +## Parameters + +| Parameter | Type | Description | +| ------ | ------ | ------ | +| `ctx` | `Context` | A Hono context | + +## Returns + +`Promise`\<`Intl.Locale`\> + +Return an `Intl.Locale` instance representing the detected locale + +## Description + +The locale obtainable via this function comes from the locale detector specified in the `locale` option of the [defineI18nMiddleware](defineI18nMiddleware.md). + +## Example + +```js +app.get( + '/', + async ctx => { + const locale = await getDetectorLocale(ctx) + return ctx.text(`Current Locale: ${locale.language}`) + }, +) +``` diff --git a/packages/hono/docs/functions/useTranslation.md b/packages/hono/docs/functions/useTranslation.md index d3a3c35..05ec8e4 100644 --- a/packages/hono/docs/functions/useTranslation.md +++ b/packages/hono/docs/functions/useTranslation.md @@ -25,7 +25,7 @@ function useTranslation(ctx): Promise>>>; ``` -use translation function in event handler +use translation function in handler ## Type Parameters diff --git a/packages/hono/docs/index.md b/packages/hono/docs/index.md index dbe47ec..9ef2ebd 100644 --- a/packages/hono/docs/index.md +++ b/packages/hono/docs/index.md @@ -13,6 +13,7 @@ Internationalization middleware & utilities for Hono | [defineI18nMiddleware](functions/defineI18nMiddleware.md) | define i18n middleware for Hono | | [detectLocaleFromAcceptLanguageHeader](functions/detectLocaleFromAcceptLanguageHeader.md) | locale detection with `Accept-Language` header | | [getCookieLocale](functions/getCookieLocale.md) | get locale from cookie | +| [getDetectorLocale](functions/getDetectorLocale.md) | get a locale which is detected with locale detector. | | [getHeaderLanguage](functions/getHeaderLanguage.md) | get language from header | | [getHeaderLanguages](functions/getHeaderLanguages.md) | get languages from header | | [getHeaderLocale](functions/getHeaderLocale.md) | get locale from header | @@ -25,7 +26,7 @@ Internationalization middleware & utilities for Hono | [tryHeaderLocales](functions/tryHeaderLocales.md) | try to get locales from header | | [tryPathLocale](functions/tryPathLocale.md) | try to get the locale from the path | | [tryQueryLocale](functions/tryQueryLocale.md) | try to get the locale from the query | -| [useTranslation](functions/useTranslation.md) | use translation function in event handler | +| [useTranslation](functions/useTranslation.md) | use translation function in handler | ## Interfaces diff --git a/packages/hono/src/index.test.ts b/packages/hono/src/index.test.ts index bb72c59..7921100 100644 --- a/packages/hono/src/index.test.ts +++ b/packages/hono/src/index.test.ts @@ -7,6 +7,7 @@ import type { Context } from 'hono' import { defineI18nMiddleware, detectLocaleFromAcceptLanguageHeader, + getDetectorLocale, useTranslation } from './index.ts' @@ -93,3 +94,33 @@ describe('useTranslation', () => { await expect(() => useTranslation(mockContext)).rejects.toThrowError() }) }) + +test('getDetectorLocale', async () => { + /** + * setup `defineI18nMiddleware` emulates + */ + const context = createCoreContext({ + locale: detectLocaleFromAcceptLanguageHeader + }) + const mockContext = { + req: { + raw: { + headers: { + get: _name => (_name === 'accept-language' ? 'ja;q=0.9,en;q=0.8' : '') + } + } + }, + get: (key: string) => { + if (key === 'i18n') { + return context + } else if (key === 'i18nLocaleDetector') { + const locale = context.locale as unknown + return (locale as LocaleDetector).bind(null, mockContext) + } + } + } as Context + + // test `getDetectorLocale` + const locale = await getDetectorLocale(mockContext) + expect(locale.language).toEqual('ja') +}) diff --git a/packages/hono/src/index.ts b/packages/hono/src/index.ts index a7adf65..5fe0c40 100644 --- a/packages/hono/src/index.ts +++ b/packages/hono/src/index.ts @@ -193,8 +193,30 @@ export function defineI18nMiddleware< export const detectLocaleFromAcceptLanguageHeader = (ctx: Context): Locale => getHeaderLocale(ctx.req.raw).toString() +async function getLocaleAndIntlifyContext(ctx: Context): Promise<[string, CoreContext]> { + const intlify = ctx.get('i18n') + if (intlify == null) { + throw new Error( + 'middleware not initialized, please setup `app.use` with the middleware obtained with `defineI18nMiddleware`' + ) + } + + const localeDetector = ctx.get('i18nLocaleDetector') + if (localeDetector == null) { + throw new Error( + 'locale detector not found in context, please make sure that the i18n middleware is correctly set up' + ) + } + + // Always await detector call - works for both sync and async detectors + // (awaiting a non-promise value returns it immediately) + const locale = await localeDetector(ctx) + + return [locale, intlify] +} + /** - * use translation function in event handler + * use translation function in handler * * @param ctx - A Hono context * @returns Return a translation function, which can be translated with i18n resource messages @@ -229,24 +251,8 @@ export async function useTranslation< Schema extends Record = {}, // eslint-disable-line @typescript-eslint/no-explicit-any -- NOTE(kazupon): generic type HonoContext extends Context = Context >(ctx: HonoContext): Promise> { - const i18n = ctx.get('i18n') - if (i18n == null) { - throw new Error( - 'middleware not initialized, please setup `app.use` with the middleware obtained with `defineI18nMiddleware`' - ) - } - - const localeDetector = ctx.get('i18nLocaleDetector') - if (localeDetector == null) { - throw new Error( - 'locale detector not found in context, please make sure that the i18n middleware is correctly set up' - ) - } - // Always await detector call - works for both sync and async detectors - // (awaiting a non-promise value returns it immediately) - const locale = await localeDetector(ctx) - i18n.locale = locale - + const [locale, intlify] = await getLocaleAndIntlifyContext(ctx) + intlify.locale = locale function translate(key: string, ...args: unknown[]): string { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call -- NOTE(kazupon): generic type const [_, options] = parseTranslateArgs(key, ...args) @@ -254,7 +260,7 @@ export async function useTranslation< // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- NOTE(kazupon): generic type const result = Reflect.apply(_translate, null, [ - i18n, + intlify, key, arg2, { @@ -268,3 +274,28 @@ export async function useTranslation< return translate as TranslationFunction } + +/** + * get a locale which is detected with locale detector. + * + * @description The locale obtainable via this function comes from the locale detector specified in the `locale` option of the {@link defineI18nMiddleware}. + * + * @example + * ```js + * app.get( + * '/', + * async ctx => { + * const locale = await getDetectorLocale(ctx) + * return ctx.text(`Current Locale: ${locale.language}`) + * }, + * ) + * ``` + * + * @param ctx - A Hono context + * + * @returns Return an {@linkcode Intl.Locale} instance representing the detected locale + */ +export async function getDetectorLocale(ctx: Context): Promise { + const [locale] = await getLocaleAndIntlifyContext(ctx) + return new Intl.Locale(locale) +}