Skip to content

Commit

Permalink
fix: locale polluted by cross request (#39)
Browse files Browse the repository at this point in the history
* fix: i18n ctx cross request pollution

* fix: add test

* fix: test fail

* fix: rename to `_i18nLocale`
  • Loading branch information
kongmoumou authored Nov 19, 2024
1 parent 92ec916 commit d5589ef
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 5 deletions.
61 changes: 61 additions & 0 deletions spec/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,65 @@ describe('custom locale detection', () => {
expect(res.body).toEqual(translated[locale])
}
})
test('async parallel', async () => {
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

const loader = (path: string) => import(path).then((m) => m.default || m)
const messages: Record<string, () => ReturnType<typeof loader>> = {
en: () => loader('./fixtures/en.json'),
ja: () => loader('./fixtures/ja.json'),
}

// async locale detector
const localeDetector = async (
event: H3Event,
i18n: CoreContext<string, DefineLocaleMessage>,
) => {
const locale = getQueryLocale(event).toString()
await sleep(100)
const loader = messages[locale]
if (loader && !i18n.messages[locale]) {
const message = await loader()
i18n.messages[locale] = message
}
return locale
}

const middleware = defineI18nMiddleware({
locale: localeDetector,
messages: {
en: {
hello: 'hello, {name}',
},
},
})
app = createApp({ ...middleware })
request = supertest(toNodeListener(app))

app.use(
'/',
eventHandler(async (event) => {
await sleep(100)
const t = await useTranslation(event)
await sleep(100)
return { message: t('hello', { name: 'h3' }) }
}),
)

const translated: Record<string, { message: string }> = {
en: {
message: 'hello, h3',
},
ja: {
message: 'こんにちは, h3',
},
}
// request in parallel
const resList = await Promise.all(
['en', 'ja'].map((locale) =>
request.get(`/?locale=${locale}`).then((res: { body: string }) => res.body)
),
)
expect(resList).toEqual([translated['en'], translated['ja']])
})
})
2 changes: 2 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ describe('useTranslation', () => {
const bindLocaleDetector = (locale as LocaleDetector).bind(null, eventMock)
// @ts-ignore ignore type error because this is test
context.locale = bindLocaleDetector
// @ts-ignore ignore type error because this is test
eventMock.context._i18nLocale = bindLocaleDetector

// test `useTranslation`
const t = await useTranslation(eventMock)
Expand Down
27 changes: 22 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// deno-lint-ignore-file no-explicit-any ban-types

import { createCoreContext, NOT_REOSLVED, translate as _translate } from '@intlify/core'
import {
createCoreContext,
NOT_REOSLVED,
// @ts-expect-error internal function
parseTranslateArgs,
translate as _translate,
} from '@intlify/core'
import { getHeaderLocale } from '@intlify/utils/h3'

export * from '@intlify/utils/h3'
Expand All @@ -27,6 +33,7 @@ import type {
declare module 'h3' {
interface H3EventContext {
i18n?: CoreContext
_i18nLocale?: LocaleDetector
}
}

Expand Down Expand Up @@ -129,7 +136,8 @@ export function defineI18nMiddleware<

return {
onRequest(event: H3Event) {
i18n.locale = getLocaleDetector(event, i18n as CoreContext)
event.context._i18nLocale = getLocaleDetector(event, i18n as CoreContext)
i18n.locale = event.context._i18nLocale
event.context.i18n = i18n as CoreContext
},
onAfterResponse(event: H3Event) {
Expand Down Expand Up @@ -349,16 +357,25 @@ export async function useTranslation<
)
}

const localeDetector = event.context.i18n.locale as unknown as LocaleDetector
const localeDetector = event.context._i18nLocale as unknown as LocaleDetector
let locale: string
if (localeDetector.constructor.name === 'AsyncFunction') {
event.context.i18n.locale = await localeDetector(event)
locale = await localeDetector(event)
event.context.i18n.locale = locale
}

function translate(key: string, ...args: unknown[]): string {
const [_, options] = parseTranslateArgs(key, ...args)
const [arg2] = args
const result = Reflect.apply(_translate, null, [
event.context.i18n!,
key,
...args,
arg2,
{
// bind to request locale
locale,
...options,
},
])
return NOT_REOSLVED === result ? key : result as string
}
Expand Down

0 comments on commit d5589ef

Please sign in to comment.