Skip to content

Commit d5589ef

Browse files
authored
fix: locale polluted by cross request (#39)
* fix: i18n ctx cross request pollution * fix: add test * fix: test fail * fix: rename to `_i18nLocale`
1 parent 92ec916 commit d5589ef

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

spec/integration.spec.ts

+61
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,65 @@ describe('custom locale detection', () => {
140140
expect(res.body).toEqual(translated[locale])
141141
}
142142
})
143+
test('async parallel', async () => {
144+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
145+
146+
const loader = (path: string) => import(path).then((m) => m.default || m)
147+
const messages: Record<string, () => ReturnType<typeof loader>> = {
148+
en: () => loader('./fixtures/en.json'),
149+
ja: () => loader('./fixtures/ja.json'),
150+
}
151+
152+
// async locale detector
153+
const localeDetector = async (
154+
event: H3Event,
155+
i18n: CoreContext<string, DefineLocaleMessage>,
156+
) => {
157+
const locale = getQueryLocale(event).toString()
158+
await sleep(100)
159+
const loader = messages[locale]
160+
if (loader && !i18n.messages[locale]) {
161+
const message = await loader()
162+
i18n.messages[locale] = message
163+
}
164+
return locale
165+
}
166+
167+
const middleware = defineI18nMiddleware({
168+
locale: localeDetector,
169+
messages: {
170+
en: {
171+
hello: 'hello, {name}',
172+
},
173+
},
174+
})
175+
app = createApp({ ...middleware })
176+
request = supertest(toNodeListener(app))
177+
178+
app.use(
179+
'/',
180+
eventHandler(async (event) => {
181+
await sleep(100)
182+
const t = await useTranslation(event)
183+
await sleep(100)
184+
return { message: t('hello', { name: 'h3' }) }
185+
}),
186+
)
187+
188+
const translated: Record<string, { message: string }> = {
189+
en: {
190+
message: 'hello, h3',
191+
},
192+
ja: {
193+
message: 'こんにちは, h3',
194+
},
195+
}
196+
// request in parallel
197+
const resList = await Promise.all(
198+
['en', 'ja'].map((locale) =>
199+
request.get(`/?locale=${locale}`).then((res: { body: string }) => res.body)
200+
),
201+
)
202+
expect(resList).toEqual([translated['en'], translated['ja']])
203+
})
143204
})

src/index.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ describe('useTranslation', () => {
7373
const bindLocaleDetector = (locale as LocaleDetector).bind(null, eventMock)
7474
// @ts-ignore ignore type error because this is test
7575
context.locale = bindLocaleDetector
76+
// @ts-ignore ignore type error because this is test
77+
eventMock.context._i18nLocale = bindLocaleDetector
7678

7779
// test `useTranslation`
7880
const t = await useTranslation(eventMock)

src/index.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// deno-lint-ignore-file no-explicit-any ban-types
22

3-
import { createCoreContext, NOT_REOSLVED, translate as _translate } from '@intlify/core'
3+
import {
4+
createCoreContext,
5+
NOT_REOSLVED,
6+
// @ts-expect-error internal function
7+
parseTranslateArgs,
8+
translate as _translate,
9+
} from '@intlify/core'
410
import { getHeaderLocale } from '@intlify/utils/h3'
511

612
export * from '@intlify/utils/h3'
@@ -27,6 +33,7 @@ import type {
2733
declare module 'h3' {
2834
interface H3EventContext {
2935
i18n?: CoreContext
36+
_i18nLocale?: LocaleDetector
3037
}
3138
}
3239

@@ -129,7 +136,8 @@ export function defineI18nMiddleware<
129136

130137
return {
131138
onRequest(event: H3Event) {
132-
i18n.locale = getLocaleDetector(event, i18n as CoreContext)
139+
event.context._i18nLocale = getLocaleDetector(event, i18n as CoreContext)
140+
i18n.locale = event.context._i18nLocale
133141
event.context.i18n = i18n as CoreContext
134142
},
135143
onAfterResponse(event: H3Event) {
@@ -349,16 +357,25 @@ export async function useTranslation<
349357
)
350358
}
351359

352-
const localeDetector = event.context.i18n.locale as unknown as LocaleDetector
360+
const localeDetector = event.context._i18nLocale as unknown as LocaleDetector
361+
let locale: string
353362
if (localeDetector.constructor.name === 'AsyncFunction') {
354-
event.context.i18n.locale = await localeDetector(event)
363+
locale = await localeDetector(event)
364+
event.context.i18n.locale = locale
355365
}
356366

357367
function translate(key: string, ...args: unknown[]): string {
368+
const [_, options] = parseTranslateArgs(key, ...args)
369+
const [arg2] = args
358370
const result = Reflect.apply(_translate, null, [
359371
event.context.i18n!,
360372
key,
361-
...args,
373+
arg2,
374+
{
375+
// bind to request locale
376+
locale,
377+
...options,
378+
},
362379
])
363380
return NOT_REOSLVED === result ? key : result as string
364381
}

0 commit comments

Comments
 (0)