Skip to content

Commit 089e837

Browse files
authored
perf: cache static locale messages server-side (#3579)
1 parent 98695df commit 089e837

37 files changed

+3273
-2311
lines changed

TODO.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# TODO
22

3-
- For Official release
4-
- [x] remove `i18n:extends-messages` hook logic
5-
- [x] remove `parsePage` option logic
6-
- [x] update `@intlify/**` and vue-i18-\* pkgs for latest version
7-
- Refactoring
8-
- [ ] module alias, langs option, etc should be resolved with `createResolver`
9-
- [ ] resolve i18n options of nuxt.config with `useRuntimeConfig`, not `i18n.options.mjs`
10-
- [ ] pass `vueI18n` option as config and pass it to vue-i18n in runtime context.
11-
- [ ] request nuxt segment parser from `@nuxt/kit` API and use it.
3+
### Performance
4+
5+
- [ ] caching
6+
- [ ] server route for fetching locale messages from the server
7+
8+
### Maintenance
9+
10+
- [ ] rework routing strategy tests - use unit tests instead of e2e tests

internals.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ declare module '#internal/i18n/options.mjs' {
2121
}
2222

2323
declare module '#internal/i18n/locale.detector.mjs' {
24-
import type { LocaleDetector } from '@nuxtjs/i18n/dist/runtime/composables/server'
25-
26-
export const localeDetector: LocaleDetector
24+
export const localeDetector: ((event: H3Event, config: LocaleConfig) => string) | undefined
2725
}
2826

2927
declare module '#internal/i18n-type-generation-options' {

knip.jsonc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"ignoreUnresolved": [
1515
// virtuals
1616
"#nuxt-i18n/*",
17-
"#internal/i18n*"
17+
"#internal/i18n*",
18+
"#internal/nuxt.config.mjs"
1819
],
1920
"ignoreDependencies": [
2021
// pre-commit

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"rollup": "4.37.0"
8585
},
8686
"dependencies": {
87+
"@intlify/core": "^11.1.3",
8788
"@intlify/h3": "^0.6.1",
8889
"@intlify/shared": "^11.1.3",
8990
"@intlify/unplugin-vue-i18n": "^6.0.8",
@@ -94,6 +95,7 @@
9495
"@vue/compiler-sfc": "^3.5.13",
9596
"debug": "^4.4.0",
9697
"defu": "^6.1.4",
98+
"devalue": "^5.1.1",
9799
"esbuild": "^0.25.3",
98100
"estree-walker": "^3.0.3",
99101
"h3": "^1.15.1",

pnpm-lock.yaml

Lines changed: 2815 additions & 2125 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

specs/basic-usage-tests.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getDom,
88
gotoPath,
99
renderPage,
10+
setServerRuntimeConfig,
1011
startServerWithRuntimeConfig
1112
} from './helper'
1213
import type { RouteLocation } from 'vue-router'
@@ -155,7 +156,7 @@ export function basicUsageTests() {
155156

156157
expect(await page.locator('#runtime-config').innerText()).toEqual('Hello from runtime config!')
157158

158-
await startServerWithRuntimeConfig({
159+
await setServerRuntimeConfig({
159160
public: { runtimeValue: 'The environment variable has changed!' }
160161
})
161162

@@ -387,7 +388,7 @@ export function basicUsageTests() {
387388
test('render seo tags with baseUrl', async () => {
388389
const configDomain = 'https://runtime-config-domain.com'
389390

390-
await startServerWithRuntimeConfig({
391+
await setServerRuntimeConfig({
391392
public: {
392393
i18n: {
393394
baseUrl: configDomain
@@ -530,13 +531,17 @@ export function basicUsageTests() {
530531
})
531532

532533
test('(#2000) Should be able to load large vue-i18n messages', async () => {
533-
await startServerWithRuntimeConfig({
534-
public: { longTextTest: true }
535-
})
534+
const restore = await startServerWithRuntimeConfig(
535+
{
536+
public: { longTextTest: true }
537+
},
538+
true
539+
)
536540

537541
const { page } = await renderPage('/nl/long-text')
538542

539543
expect(await page.locator('#long-text').innerText()).toEqual('hallo,'.repeat(8 * 500))
544+
await restore()
540545
})
541546

542547
test('(#2094) vue-i18n messages are loaded from config exported as variable', async () => {

specs/basic_usage.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url'
33
import { setTestContext, setup, useTestContext } from './utils'
44
import { basicUsageTests } from './basic-usage-tests'
55
import { languageSwitchingTests } from './language-switching-tests'
6-
import { startServerWithRuntimeConfig } from './helper'
6+
import { setServerRuntimeConfig } from './helper'
77

88
describe('basic usage', async () => {
99
await setup({
@@ -32,7 +32,7 @@ describe('basic usage', async () => {
3232
describe('language switching', async () => {
3333
beforeAll(async () => {
3434
setTestContext(ctx)
35-
await startServerWithRuntimeConfig(
35+
await setServerRuntimeConfig(
3636
{
3737
public: {
3838
i18n: {

specs/basic_usage_compat_4.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { beforeAll, describe } from 'vitest'
22
import { fileURLToPath } from 'node:url'
33
import { setTestContext, setup, useTestContext } from './utils'
44
import { basicUsageTests } from './basic-usage-tests'
5-
import { startServerWithRuntimeConfig } from './helper'
5+
import { setServerRuntimeConfig } from './helper'
66
import { languageSwitchingTests } from './language-switching-tests'
77

88
describe('basic usage - compatibilityVersion: 4', async () => {
@@ -32,7 +32,7 @@ describe('basic usage - compatibilityVersion: 4', async () => {
3232
describe('language switching', async () => {
3333
beforeAll(async () => {
3434
setTestContext(ctx)
35-
await startServerWithRuntimeConfig(
35+
await setServerRuntimeConfig(
3636
{
3737
public: {
3838
i18n: {

specs/browser_language_detection/no_prefix.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect } from 'vitest'
22
import { fileURLToPath } from 'node:url'
33
import { setup } from '../utils'
4-
import { gotoPath, renderPage, startServerWithRuntimeConfig } from '../helper'
4+
import { gotoPath, renderPage, setServerRuntimeConfig } from '../helper'
55

66
await setup({
77
rootDir: fileURLToPath(new URL(`../fixtures/basic`, import.meta.url)),
@@ -22,7 +22,7 @@ await setup({
2222
})
2323

2424
test('detection with cookie', async () => {
25-
await startServerWithRuntimeConfig({
25+
await setServerRuntimeConfig({
2626
public: {
2727
i18n: {
2828
detectBrowserLanguage: {
@@ -63,7 +63,7 @@ test('detection with cookie', async () => {
6363
})
6464

6565
test('detection with cookie - overwrite unknown locale', async () => {
66-
await startServerWithRuntimeConfig({
66+
await setServerRuntimeConfig({
6767
public: {
6868
i18n: {
6969
detectBrowserLanguage: {
@@ -99,7 +99,7 @@ test('detection with cookie - overwrite unknown locale', async () => {
9999

100100
// browser
101101
test('detection with browser', async () => {
102-
await startServerWithRuntimeConfig({
102+
await setServerRuntimeConfig({
103103
public: {
104104
i18n: {
105105
detectBrowserLanguage: {
@@ -136,7 +136,7 @@ test('detection with browser', async () => {
136136

137137
// disable
138138
test('disable', async () => {
139-
await startServerWithRuntimeConfig({
139+
await setServerRuntimeConfig({
140140
public: {
141141
i18n: {
142142
detectBrowserLanguage: false
@@ -168,7 +168,7 @@ test('disable', async () => {
168168
})
169169

170170
test('fallback', async () => {
171-
await startServerWithRuntimeConfig({
171+
await setServerRuntimeConfig({
172172
public: {
173173
i18n: {
174174
detectBrowserLanguage: {

specs/browser_language_detection/prefix_and_default.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect } from 'vitest'
22
import { fileURLToPath } from 'node:url'
33
import { setup, url } from '../utils'
4-
import { gotoPath, renderPage, startServerWithRuntimeConfig } from '../helper'
4+
import { gotoPath, renderPage, setServerRuntimeConfig } from '../helper'
55

66
await setup({
77
rootDir: fileURLToPath(new URL(`../fixtures/basic`, import.meta.url)),
@@ -18,7 +18,7 @@ await setup({
1818
})
1919

2020
test('redirectOn: all', async () => {
21-
await startServerWithRuntimeConfig({
21+
await setServerRuntimeConfig({
2222
public: {
2323
i18n: {
2424
detectBrowserLanguage: {
@@ -43,7 +43,7 @@ test('redirectOn: all', async () => {
4343
})
4444

4545
test('redirectOn: no prefix', async () => {
46-
await startServerWithRuntimeConfig({
46+
await setServerRuntimeConfig({
4747
public: {
4848
i18n: {
4949
detectBrowserLanguage: {
@@ -68,7 +68,7 @@ test('redirectOn: no prefix', async () => {
6868
})
6969

7070
test('alwaysRedirect: all', async () => {
71-
await startServerWithRuntimeConfig({
71+
await setServerRuntimeConfig({
7272
public: {
7373
i18n: {
7474
detectBrowserLanguage: {
@@ -95,7 +95,7 @@ test('alwaysRedirect: all', async () => {
9595
})
9696

9797
test('alwaysRedirect: no prefix', async () => {
98-
await startServerWithRuntimeConfig({
98+
await setServerRuntimeConfig({
9999
public: {
100100
i18n: {
101101
detectBrowserLanguage: {

specs/browser_language_detection/prefix_except_default.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect, describe, beforeEach } from 'vitest'
22
import { fileURLToPath } from 'node:url'
33
import { setup, url } from '../utils'
4-
import { renderPage, startServerWithRuntimeConfig } from '../helper'
4+
import { renderPage, setServerRuntimeConfig } from '../helper'
55

66
await setup({
77
rootDir: fileURLToPath(new URL(`../fixtures/basic`, import.meta.url)),
@@ -21,7 +21,7 @@ await setup({
2121

2222
describe('`detectBrowserLanguage` using strategy `prefix_except_default`', async () => {
2323
test('(#2262) redirect using browser cookie with `alwaysRedirect: true`', async () => {
24-
await startServerWithRuntimeConfig({
24+
await setServerRuntimeConfig({
2525
public: {
2626
i18n: {
2727
detectBrowserLanguage: {
@@ -56,7 +56,7 @@ describe('`detectBrowserLanguage` using strategy `prefix_except_default`', async
5656

5757
describe('(#2255) detect browser language and redirect on root', async () => {
5858
beforeEach(async () => {
59-
await startServerWithRuntimeConfig({
59+
await setServerRuntimeConfig({
6060
public: {
6161
i18n: {
6262
detectBrowserLanguage: {

specs/helper.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @ts-ignore
22
import createJITI from 'jiti'
33
import { JSDOM } from 'jsdom'
4-
import { getBrowser, TestContext, url, useTestContext } from './utils'
4+
import { getBrowser, startServer, TestContext, url, useTestContext } from './utils'
55
import { resolveAlias } from '@nuxt/kit'
66
import { onTestFinished } from 'vitest'
77

@@ -175,7 +175,7 @@ async function updateProcessRuntimeConfig(ctx: TestContext, config: unknown) {
175175
return await updated
176176
}
177177

178-
export async function startServerWithRuntimeConfig(env: Record<string, unknown>, skipRestore = false) {
178+
export async function setServerRuntimeConfig(env: Record<string, unknown>, skipRestore = false) {
179179
const ctx = useTestContext()
180180

181181
const stored = await updateProcessRuntimeConfig(ctx, env)
@@ -195,6 +195,61 @@ export async function startServerWithRuntimeConfig(env: Record<string, unknown>,
195195
return restoreFn
196196
}
197197

198+
import { snakeCase } from 'scule'
199+
200+
function flattenObject(obj: Record<string, unknown> = {}) {
201+
const flattened: Record<string, unknown> = {}
202+
203+
for (const key of Object.keys(obj)) {
204+
const entry = obj[key]
205+
206+
if (typeof entry !== 'object' || entry == null) {
207+
flattened[key] = obj[key]
208+
continue
209+
}
210+
211+
const flatObject = flattenObject(entry as Record<string, unknown>)
212+
for (const x of Object.keys(flatObject)) {
213+
flattened[key + '_' + x] = flatObject[x]
214+
}
215+
}
216+
217+
return flattened
218+
}
219+
220+
function convertObjectToConfig(obj: Record<string, unknown>) {
221+
const makeEnvKey = (str: string) => `NUXT_${snakeCase(str).toUpperCase()}`
222+
223+
const env: Record<string, unknown> = {}
224+
const flattened = flattenObject(obj)
225+
for (const key in flattened) {
226+
env[makeEnvKey(key)] = flattened[key]
227+
}
228+
229+
return env
230+
}
231+
export async function startServerWithRuntimeConfig(env: Record<string, unknown>, skipRestore = false) {
232+
const ctx = useTestContext()
233+
const stored = await updateProcessRuntimeConfig(ctx, env)
234+
235+
await startServer(convertObjectToConfig(env))
236+
237+
let restored = false
238+
const restoreFn = async () => {
239+
if (restored) return
240+
241+
restored = true
242+
// await await updateProcessRuntimeConfig(ctx, stored)
243+
await startServer({})
244+
}
245+
246+
if (!skipRestore) {
247+
onTestFinished(restoreFn)
248+
}
249+
250+
return restoreFn
251+
}
252+
198253
export async function localeLoaderHelpers() {
199254
const ctx = useTestContext()
200255
const jiti = createJITI(ctx.nuxt!.options.rootDir, { alias: ctx.nuxt!.options.alias })

specs/lazy_load/basic_lazy_load.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,19 @@ describe('basic lazy loading', async () => {
118118
const { findKey } = await localeLoaderHelpers()
119119

120120
await page.click('#lang-switcher-with-nuxt-link-en-GB')
121+
await waitForMs(100) // FIXME: may cause flaky test
121122
expect(logs.filter(log => log.text.includes(`${findKey('en-GB', 'js')} bypassing cache!`))).toHaveLength(1)
122123

123124
await page.click('#lang-switcher-with-nuxt-link-fr')
125+
await waitForMs(100) // FIXME: may cause flaky test
124126
expect(logs.filter(log => log.text.includes(`${findKey('fr', 'json5')} bypassing cache!`))).toHaveLength(1)
125127

126128
await page.click('#lang-switcher-with-nuxt-link-en-GB')
129+
await waitForMs(100) // FIXME: may cause flaky test
127130
expect(logs.filter(log => log.text.includes(`${findKey('en-GB', 'js')} bypassing cache!`))).toHaveLength(2)
128131

129132
await page.click('#lang-switcher-with-nuxt-link-fr')
133+
await waitForMs(100) // FIXME: may cause flaky test
130134
expect(logs.filter(log => log.text.includes(`${findKey('fr', 'json5')} bypassing cache!`))).toHaveLength(2)
131135
})
132136

specs/lazy_load/restructure.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,19 @@ describe('basic lazy loading (restructure)', async () => {
118118
const { findKey } = await localeLoaderHelpers()
119119

120120
await page.click('#lang-switcher-with-nuxt-link-en-GB')
121+
await waitForMs(100) // FIXME: may cause flaky test
121122
expect(logs.filter(log => log.text.includes(`${findKey('en-GB', 'js')} bypassing cache!`))).toHaveLength(1)
122123

123124
await page.click('#lang-switcher-with-nuxt-link-fr')
125+
await waitForMs(100) // FIXME: may cause flaky test
124126
expect(logs.filter(log => log.text.includes(`${findKey('fr', 'json5')} bypassing cache!`))).toHaveLength(1)
125127

126128
await page.click('#lang-switcher-with-nuxt-link-en-GB')
129+
await waitForMs(100) // FIXME: may cause flaky test
127130
expect(logs.filter(log => log.text.includes(`${findKey('en-GB', 'js')} bypassing cache!`))).toHaveLength(2)
128131

129132
await page.click('#lang-switcher-with-nuxt-link-fr')
133+
await waitForMs(100) // FIXME: may cause flaky test
130134
expect(logs.filter(log => log.text.includes(`${findKey('fr', 'json5')} bypassing cache!`))).toHaveLength(2)
131135
})
132136

0 commit comments

Comments
 (0)