Skip to content
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
9 changes: 7 additions & 2 deletions packages/h3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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.
Expand Down
41 changes: 41 additions & 0 deletions packages/h3/docs/functions/getDetectorLocale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[**@intlify/h3**](../index.md)

***

[@intlify/h3](../index.md) / getDetectorLocale

# Function: getDetectorLocale()

```ts
function getDetectorLocale(event): Promise<Locale>;
```

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}`
},
)
```
1 change: 1 addition & 0 deletions packages/h3/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
25 changes: 25 additions & 0 deletions packages/h3/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SYMBOL_I18N, SYMBOL_I18N_LOCALE } from './symbols.ts'
import {
defineI18nMiddleware,
detectLocaleFromAcceptLanguageHeader,
getDetectorLocale,
useTranslation
} from './index.ts'

Expand Down Expand Up @@ -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')
})
54 changes: 41 additions & 13 deletions packages/h3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,21 @@ export const detectLocaleFromAcceptLanguageHeader = (event: H3Event): Locale =>
*/
export interface DefineLocaleMessage extends LocaleMessage<string> {}

async function getLocaleAndEventContext(event: H3Event): Promise<[string, H3EventContext]> {
const context = getEventContext<H3EventContext>(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
*
Expand All @@ -283,19 +298,8 @@ export async function useTranslation<
Schema extends Record<string, any> = {}, // eslint-disable-line @typescript-eslint/no-explicit-any -- NOTE(kazupon): generic type
Event extends H3Event = H3Event
>(event: Event): Promise<TranslationFunction<Schema, DefineLocaleMessage>> {
const context = getEventContext<H3EventContext>(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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Effective refactoring using the shared helper.

The refactoring centralizes locale detection logic and maintains the existing translation flow while improving code organization.

🤖 Prompt for AI Agents
In packages/h3/src/index.ts around line 302, replace the existing inline
locale-detection and context extraction with a single call to the shared helper
getLocaleAndEventContext and destructure its resolved tuple into [locale,
context]; ensure all subsequent uses reference these variables (remove the old
duplicated detection code paths), preserve the existing translation flow, and
add minimal defensive handling if the helper returns undefined/null so we don't
break downstream code.

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)
Expand All @@ -317,3 +321,27 @@ export async function useTranslation<

return translate as TranslationFunction<Schema, DefineLocaleMessage>
}

/**
* 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<Intl.Locale> {
const result = await getLocaleAndEventContext(event)
return new Intl.Locale(result[0])
}
14 changes: 11 additions & 3 deletions packages/hono/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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.
Expand Down
41 changes: 41 additions & 0 deletions packages/hono/docs/functions/getDetectorLocale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[**@intlify/hono**](../index.md)

***

[@intlify/hono](../index.md) / getDetectorLocale

# Function: getDetectorLocale()

```ts
function getDetectorLocale(ctx): Promise<Intl.Locale>;
```

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}`)
},
)
```
2 changes: 1 addition & 1 deletion packages/hono/docs/functions/useTranslation.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function useTranslation<Schema, HonoContext>(ctx): Promise<TranslationFunction<S
}>>>>;
```

use translation function in event handler
use translation function in handler

## Type Parameters

Expand Down
3 changes: 2 additions & 1 deletion packages/hono/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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

Expand Down
31 changes: 31 additions & 0 deletions packages/hono/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Context } from 'hono'
import {
defineI18nMiddleware,
detectLocaleFromAcceptLanguageHeader,
getDetectorLocale,
useTranslation
} from './index.ts'

Expand Down Expand Up @@ -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')
})
Loading
Loading