Skip to content

Commit

Permalink
Merge branch 'next' of github.com:nuxt-modules/i18n into next
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed Sep 5, 2023
2 parents 250dc18 + 2eda7db commit 30514f2
Show file tree
Hide file tree
Showing 16 changed files with 306 additions and 126 deletions.
50 changes: 43 additions & 7 deletions docs/content/2.guide/8.lazy-load-translations.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,48 @@ In the example above, only two files are defined for `files`, of course you can

By taking advantage of the characteristic that locale messages are overridden in sequence, it's possible to manage locale messages by defining them on a differential basis. By adding shared (common) locale messages as the first entry of `files`, followed by file entries of regional/dialectal locale messages, it's possible to manage resources while avoiding the duplication of locale messages.

::alert{type="info"}
Lazy loaded locale messages are cached based on their filename, `file` and `files` shared across locales will be used from cache once loaded.
::
## Caching
Lazy loaded locale messages are cached based on their filename, `file` and `files` shared across locales will be used from cache once loaded. By default caching is enabled for static files, and disabled for files that return messages via a function.

::alert{type="warn"}
Please note that caching for JS/TS format resources is disabled by default as these files can return messages dynamically.
Caching can be configured per file by setting `file` or entries of `files` to objects with the following type signature `{ path: string, cache?: boolean}`. The example below demonstrates several valid file configurations.

Support for enabling/disabling caching on a per file basis is currently in development.
::
```js {}[nuxt.config.ts]
export default defineNuxtConfig({
// ...

i18n: {
locales: [
// ...

/**
* Example definition with `files` for Spanish speaking countries
*/
{
code: 'es-ES',
name: 'Español (Spain)',
// file with cache disabled
file: { path: 'es.js', cache: false }
},
{
code: 'es-AR',
name: 'Español (Argentina)',
// files with cache disabled
files: [{ path: 'es.js', cache: false }, { path: 'es-AR.js', cache: false} ]
},
{
code: 'es-UY',
name: 'Español (Uruguay)',
// strings and object configurations can be mixed
files: [{ path: 'es.js', cache: false }, 'es-UY.json']
},

// ...
],
lazy: true,
langDir: 'lang',
defaultLocale: 'en',
},

// ...
})
```
2 changes: 1 addition & 1 deletion specs/fixtures/lazy/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default defineNuxtConfig({
{
code: 'fr',
iso: 'fr-FR',
file: 'lazy-locale-fr.json5',
file: { path: 'lazy-locale-fr.json5', cache: false },
name: 'Français'
}
]
Expand Down
32 changes: 31 additions & 1 deletion specs/lazy_load/basic_lazy_load.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { getText, getData, waitForMs } from '../helper'
describe('basic lazy loading', async () => {
await setup({
rootDir: fileURLToPath(new URL(`../fixtures/lazy`, import.meta.url)),
browser: true
browser: true,
nuxtConfig: {
i18n: {
debug: true
}
}
})

test('dynamic locale files are not cached', async () => {
Expand Down Expand Up @@ -121,4 +126,29 @@ describe('basic lazy loading', async () => {
expect(await getText(page, '#profile-js')).toEqual('Profile1')
expect(await getText(page, '#profile-ts')).toEqual('Profile2')
})

test('files with cache disabled bypass caching', async () => {
const home = url('/')
const page = await createPage()
await page.goto(home)
const messages: string[] = []
page.on('console', msg => {
const content = msg.text()
if (content.includes('lazy-locale-')) {
messages.push(content)
}
})

await page.click('#lang-switcher-with-nuxt-link-en-GB')
expect([...messages].filter(msg => msg.includes('lazy-locale-en-GB.js bypassing cache!'))).toHaveLength(1)

await page.click('#lang-switcher-with-nuxt-link-fr')
expect([...messages].filter(msg => msg.includes('lazy-locale-fr.json5 bypassing cache!'))).toHaveLength(1)

await page.click('#lang-switcher-with-nuxt-link-en-GB')
expect([...messages].filter(msg => msg.includes('lazy-locale-en-GB.js bypassing cache!'))).toHaveLength(2)

await page.click('#lang-switcher-with-nuxt-link-fr')
expect([...messages].filter(msg => msg.includes('lazy-locale-fr.json5 bypassing cache!'))).toHaveLength(2)
})
})
44 changes: 30 additions & 14 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { EXECUTABLE_EXTENSIONS, NULL_HASH, NUXT_I18N_MODULE_ID } from './constan
import { genImport, genSafeVariableName, genDynamicImport } from 'knitwork'
import { parse as parsePath, normalize } from 'pathe'
import { withQuery } from 'ufo'
import { toCode } from './utils'
import { getLocalePaths, toCode } from './utils'

import type { NuxtI18nOptions, NuxtI18nInternalOptions, LocaleInfo, VueI18nConfigPathInfo, LocaleType } from './types'
import type { NuxtI18nOptionsDefault } from './constants'
import type { LocaleObject } from 'vue-i18n-routing'

export type LoaderOptions = {
localeCodes?: string[]
Expand Down Expand Up @@ -42,12 +43,11 @@ export function generateLoaderOptions(
const generatedImports = new Map<string, string>()
const importMapper = new Map<string, string>()

const convertToPairs = ({ file, files, path, paths, hash, hashes, type, types }: LocaleInfo) => {
const _files = file ? [file] : files || []
const convertToPairs = ({ files, path, paths, hash, hashes, type, types }: LocaleInfo) => {
const _paths = path ? [path] : paths || []
const _hashes = hash ? [hash] : hashes || []
const _types = type ? [type] : types || []
return _files.map((f, i) => ({ file: f, path: _paths[i], hash: _hashes[i], type: _types[i] }))
return (files ?? []).map((f, i) => ({ file: f, path: _paths[i], hash: _hashes[i], type: _types[i] }))
}

const makeImportKey = (root: string, dir: string, base: string) =>
Expand Down Expand Up @@ -87,6 +87,19 @@ export function generateLoaderOptions(
return gen
}

function simplifyLocaleOptions(locales: LocaleObject[]) {
return locales.map(locale => {
if (
locale?.files?.length === 0 &&
Object.keys(locale).filter(k => !['iso', 'code', 'hashes', 'types', 'file', 'files'].includes(k)).length === 0
) {
return locale.code
}

return { ...locale, files: getLocalePaths(locale) }
})
}

let genCode = ''
const localeInfo = options.localeInfo || []
const syncLocaleFiles = new Set<LocaleInfo>()
Expand All @@ -106,7 +119,7 @@ export function generateLoaderOptions(
*/
for (const localeInfo of syncLocaleFiles) {
convertToPairs(localeInfo).forEach(({ path, type, file, hash }) => {
genCode = generateSyncImports(genCode, path, type, localeInfo.code, hash, file)
genCode = generateSyncImports(genCode, path, type, localeInfo.code, hash, file.path)
})
}

Expand Down Expand Up @@ -219,7 +232,9 @@ export function generateLoaderOptions(
}
}
} else {
genCodes += ` ${rootKey}.${key} = ${toCode(key === 'locales' ? stripPathFromLocales(value) : value)}\n`
genCodes += ` ${rootKey}.${key} = ${toCode(
key === 'locales' ? simplifyLocaleOptions(stripPathFromLocales(value)) : value
)}\n`
}
}
genCodes += ` return nuxtI18nOptions\n`
Expand All @@ -236,20 +251,21 @@ export function generateLoaderOptions(
}).join(`,`)}})\n`
} else if (rootKey === 'localeInfo') {
let codes = `export const localeMessages = {\n`
for (const { code, file, files} of syncLocaleFiles) {
const syncPaths = file ? [file] : files|| []
codes += ` ${toCode(code)}: [${syncPaths.map(filepath => {
const { root, dir, base } = parsePath(filepath)
for (const { code, files} of syncLocaleFiles) {
codes += ` ${toCode(code)}: [${(files ?? []).map(file => {
const { root, dir, base } = parsePath(file.path)
const key = makeImportKey(root, dir, base)
return `{ key: ${toCode(generatedImports.get(key))}, load: () => Promise.resolve(${importMapper.get(key)}) }`
return `{ key: ${toCode(generatedImports.get(key))}, load: () => Promise.resolve(${importMapper.get(key)}), cache: ${toCode(file.cache)} }`
})}],\n`
}
for (const localeInfo of asyncLocaleFiles) {
codes += ` ${toCode(localeInfo.code)}: [${convertToPairs(localeInfo).map(({ file, path, hash, type }) => {
const { root, dir, base, ext } = parsePath(file)
const { root, dir, base, ext } = parsePath(file.path)
const key = makeImportKey(root, dir, base)
const loadPath = resolveLocaleRelativePath(localesRelativeBase, file)
return `{ key: ${toCode(loadPath)}, load: ${genDynamicImport(genImportSpecifier(loadPath, ext, path, type, { hash, query: { locale: localeInfo.code } }), { comment: `webpackChunkName: "lang_${normalizeWithUnderScore(key)}"` })} }`
const loadPath = resolveLocaleRelativePath(localesRelativeBase, file.path)
return `{ key: ${toCode(loadPath)}, load: ${genDynamicImport(
genImportSpecifier(loadPath, ext, path, type, { hash, query: { locale: localeInfo.code } }),
{ comment: `webpackChunkName: "lang_${normalizeWithUnderScore(key)}"` })}, cache: ${toCode(file.cache)} }`
})}],\n`
}
codes += `}\n`
Expand Down
14 changes: 9 additions & 5 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import {
getPackageManagerType,
mergeI18nModules,
resolveVueI18nConfigInfo,
applyOptionOverrides
applyOptionOverrides,
getLocaleFiles
} from './utils'
import { distDir, runtimeDir, pkgModulesDir } from './dirs'
import { applyLayerOptions, checkLayerOptions, resolveLayerVueI18nConfigInfo } from './layers'

import type { NuxtI18nOptions } from './types'
import { LocaleObject } from 'vue-i18n-routing'

export * from './types'

Expand Down Expand Up @@ -228,7 +230,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
nuxtI18nOptions: options,
nuxtI18nOptionsDefault: DEFAULT_OPTIONS,
nuxtI18nInternalOptions: {
__normalizedLocales: normalizedLocales
__normalizedLocales: normalizedLocales as LocaleObject[]
}
},
{
Expand Down Expand Up @@ -267,7 +269,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
*/
nuxt.hook('build:manifest', manifest => {
if (options.lazy) {
const langFiles = localeInfo.flatMap(locale => (locale.file != null ? locale.file : [...(locale?.files ?? [])]))
const langFiles = localeInfo.flatMap(locale => getLocaleFiles(locale)).map(x => x.path)
const langPaths = [...new Set(langFiles)]

for (const key in manifest) {
Expand Down Expand Up @@ -353,11 +355,13 @@ type ModulePublicRuntimeConfig<Context = unknown> = Pick<NuxtI18nOptions<Context

declare module '@nuxt/schema' {
interface NuxtConfig {
i18n?: NuxtI18nOptions
i18n?: NuxtI18nOptions<unknown>
}

interface NuxtHooks {
'i18n:registerModule': (registerModule: (config: Pick<NuxtI18nOptions, 'langDir' | 'locales'>) => void) => void
'i18n:registerModule': (
registerModule: (config: Pick<NuxtI18nOptions<unknown>, 'langDir' | 'locales'>) => void
) => void
}

interface PublicRuntimeConfig {
Expand Down
1 change: 1 addition & 0 deletions src/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type LocaleLoader = {
key: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
load: () => Promise<any>
cache: boolean
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
5 changes: 3 additions & 2 deletions src/runtime/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function deepCopy(src: Record<string, any>, des: Record<string, any>, predicate?
}
}

type LocaleLoader = { key: string; load: () => Promise<any> }
type LocaleLoader = { key: string; load: () => Promise<any>; cache: boolean }
const loadedMessages = new Map<string, LocaleMessages<DefineLocaleMessage>>()

async function loadMessage(context: NuxtApp, { key, load }: LocaleLoader, locale: Locale) {
Expand Down Expand Up @@ -167,10 +167,11 @@ export async function loadLocale(
for (const loader of loaders) {
let message: LocaleMessages<DefineLocaleMessage> | undefined | null = null

if (loadedMessages.has(loader.key)) {
if (loadedMessages.has(loader.key) && loader.cache) {
__DEBUG__ && console.log(loader.key + ' is already loaded')
message = loadedMessages.get(loader.key)
} else {
__DEBUG__ && !loader.cache && console.log(loader.key + ' bypassing cache!')
__DEBUG__ && console.log(loader.key + ' is loading ...')
message = await loadMessage(context, loader, locale)
}
Expand Down
12 changes: 7 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Strategies, LocaleObject, I18nRoutingOptions } from 'vue-i18n-routing'
import type { Strategies, I18nRoutingOptions, LocaleObject } from 'vue-i18n-routing'
import type { Locale, I18nOptions } from 'vue-i18n'
import type { PluginOptions } from '@intlify/unplugin-vue-i18n'

Expand All @@ -17,6 +17,8 @@ export interface DetectBrowserLanguageOptions {

export type LocaleType = 'static' | 'dynamic' | 'unknown'

export type LocaleFile = { path: string; cache?: boolean }

export type LocaleInfo = {
/**
* NOTE:
Expand All @@ -32,7 +34,7 @@ export type LocaleInfo = {
paths?: string[]
hashes?: string[]
types?: LocaleType[]
} & LocaleObject
} & Omit<LocaleObject, 'file' | 'files'> & { files: LocaleFile[] }

export type VueI18nConfigPathInfo = {
relative?: string
Expand Down Expand Up @@ -88,8 +90,8 @@ export type NuxtI18nOptions<Context = unknown> = {
/**
* @internal
*/
overrides?: Omit<NuxtI18nOptions, 'overrides'>
i18nModules?: { langDir?: string | null; locales?: I18nRoutingOptions<Context>['locales'] }[]
overrides?: Omit<NuxtI18nOptions<Context>, 'overrides'>
i18nModules?: { langDir?: string | null; locales?: NuxtI18nOptions<Context>['locales'] }[]
rootRedirect?: string | null | RootRedirectOptions
routesNameSeparator?: string
skipSettingLocaleOnNavigate?: boolean
Expand All @@ -104,8 +106,8 @@ export type NuxtI18nOptions<Context = unknown> = {
| 'strategy'
| 'defaultDirection'
| 'defaultLocale'
| 'defaultLocaleRouteNameSuffix'
| 'locales'
| 'defaultLocaleRouteNameSuffix'
| 'routesNameSeparator'
| 'trailingSlash'
>
Expand Down
Loading

0 comments on commit 30514f2

Please sign in to comment.