Skip to content

Commit

Permalink
refactor: message loading and setting (nuxt-modules#2659)
Browse files Browse the repository at this point in the history
* refactor: messages loading and caching

* refactor: rename `localeMessages` to `localeLoaders`
  • Loading branch information
BobbieGoede authored Jan 7, 2024
1 parent ebfba10 commit a2a883f
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 94 deletions.
4 changes: 2 additions & 2 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function generateLoaderOptions(
.filter(config => config.absolute !== '')
.map(config => generateVueI18nConfiguration(config, isServer))

const localeMessages = localeInfo.map(locale => [locale.code, locale.meta?.map(meta => importMapper.get(meta.key))])
const localeLoaders = localeInfo.map(locale => [locale.code, locale.meta?.map(meta => importMapper.get(meta.key))])

const generatedNuxtI18nOptions = {
...nuxtI18nOptions,
Expand All @@ -109,7 +109,7 @@ export function generateLoaderOptions(

const generated = {
importStrings,
localeMessages,
localeLoaders,
nuxtI18nOptions: generatedNuxtI18nOptions,
vueI18nConfigs: vueI18nConfigImports
}
Expand Down
5 changes: 1 addition & 4 deletions src/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ type LocaleLoader = {
cache: boolean
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const loadMessages: () => Promise<any> = () => Promise.resolve({})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const localeMessages: Record<string, LocaleLoader[]> = {}
export const localeLoaders: Record<string, LocaleLoader[]> = {}

export const vueI18nConfigs: VueI18nConfig[]

Expand Down
68 changes: 32 additions & 36 deletions src/runtime/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import type { I18nOptions, Locale, FallbackLocale, LocaleMessages, DefineLocaleM
import type { NuxtApp } from '#app'
import type { DeepRequired } from 'ts-essentials'
import type { VueI18nConfig, NuxtI18nOptions } from '../types'
import type { CoreContext } from '@intlify/h3'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type LocaleLoader = { key: string; load: () => Promise<any>; cache: boolean }

const cacheMessages = new Map<string, LocaleMessages<DefineLocaleMessage>>()

export async function loadVueI18nOptions(
vueI18nConfigs: VueI18nConfig[],
nuxt: Pick<NuxtApp, 'runWithContext'>
Expand Down Expand Up @@ -43,45 +46,30 @@ export function makeFallbackLocaleCodes(fallback: FallbackLocale, locales: Local

export async function loadInitialMessages<Context extends NuxtApp = NuxtApp>(
messages: LocaleMessages<DefineLocaleMessage>,
localeLoaderMessages: Record<Locale, LocaleLoader[]>,
options: DeepRequired<NuxtI18nOptions<Context>> & {
localeLoaders: Record<Locale, LocaleLoader[]>,
options: Pick<DeepRequired<NuxtI18nOptions<Context>>, 'defaultLocale' | 'lazy'> & {
initialLocale: Locale
fallbackLocale: FallbackLocale
localeCodes: string[]
cacheMessages?: Map<string, LocaleMessages<DefineLocaleMessage>>
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<Record<string, any>> {
const { defaultLocale, initialLocale, localeCodes, fallbackLocale, lazy, cacheMessages } = options
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setter = (locale: Locale, message: Record<string, any>) => {
const base = messages[locale] || {}
deepCopy(message, base)
messages[locale] = base
}
const { defaultLocale, initialLocale, localeCodes, fallbackLocale, lazy } = options

// load fallback messages
if (lazy && fallbackLocale) {
const fallbackLocales = makeFallbackLocaleCodes(fallbackLocale, [defaultLocale, initialLocale])
await Promise.all(
fallbackLocales.map(locale => loadLocale({ locale, setter, localeMessages: localeLoaderMessages }, cacheMessages))
)
await Promise.all(fallbackLocales.map(locale => loadAndSetLocaleMessages(locale, localeLoaders, messages)))
}

// load initial messages
const locales = lazy ? [...new Set<Locale>().add(defaultLocale).add(initialLocale)] : localeCodes
await Promise.all(
locales.map((locale: Locale) => loadLocale({ locale, setter, localeMessages: localeLoaderMessages }, cacheMessages))
)
await Promise.all(locales.map((locale: Locale) => loadAndSetLocaleMessages(locale, localeLoaders, messages)))

return messages
}

async function loadMessage(
locale: Locale,
{ key, load }: LocaleLoader,
cacheMessages?: Map<string, LocaleMessages<DefineLocaleMessage>>
) {
async function loadMessage(locale: Locale, { key, load }: LocaleLoader) {
let message: LocaleMessages<DefineLocaleMessage> | null = null
try {
__DEBUG__ && console.log('loadMessage: (locale) -', locale)
Expand All @@ -98,29 +86,19 @@ async function loadMessage(
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
// eslint-disable-next-line no-console
// console.error(formatMessage('Failed locale loading: ' + e.message))
console.error('Failed locale loading: ' + e.message)
}
return message
}

export async function loadLocale(
{
locale,
localeMessages,
setter
}: {
locale: Locale
localeMessages: Record<Locale, LocaleLoader[]>
setter: (locale: Locale, message: LocaleMessages<DefineLocaleMessage>) => void
},
cacheMessages?: Map<string, LocaleMessages<DefineLocaleMessage>>
locale: Locale,
localeLoaders: Record<Locale, LocaleLoader[]>,
setter: (locale: Locale, message: LocaleMessages<DefineLocaleMessage>) => void
) {
const loaders = localeMessages[locale]
const loaders = localeLoaders[locale]

if (loaders == null) {
// console.warn(formatMessage('Could not find messages for locale code: ' + locale))
console.warn('Could not find messages for locale code: ' + locale)
return
}
Expand All @@ -135,7 +113,7 @@ export async function loadLocale(
} else {
__DEBUG__ && !loader.cache && console.log(loader.key + ' bypassing cache!')
__DEBUG__ && console.log(loader.key + ' is loading ...')
message = await loadMessage(locale, loader, cacheMessages)
message = await loadMessage(locale, loader)
}

if (message != null) {
Expand All @@ -145,3 +123,21 @@ export async function loadLocale(

setter(locale, targetMessage)
}

type LocaleLoaderMessages = CoreContext['messages'] | LocaleMessages<DefineLocaleMessage>
export async function loadAndSetLocaleMessages(
locale: Locale,
localeLoaders: Record<Locale, LocaleLoader[]>,
messages: LocaleLoaderMessages
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setter = (locale: Locale, message: Record<string, any>) => {
// @ts-expect-error should be able to use `locale` as index
const base = messages[locale] || {}
deepCopy(message, base)
// @ts-expect-error should be able to use `locale` as index
messages[locale] = base
}

await loadLocale(locale, localeLoaders, setter)
}
23 changes: 9 additions & 14 deletions src/runtime/plugins/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
nuxtI18nOptions as _nuxtI18nOptions,
nuxtI18nInternalOptions,
isSSG,
localeMessages,
localeLoaders,
parallelPlugin
} from '#build/i18n.options.mjs'
import { loadVueI18nOptions, loadInitialMessages } from '../messages'
Expand All @@ -50,7 +50,7 @@ import {
DefaultDetectBrowserLanguageFromResult
} from '../internal'

import type { Composer, Locale, I18nOptions, LocaleMessages, DefineLocaleMessage } from 'vue-i18n'
import type { Composer, Locale, I18nOptions } from 'vue-i18n'
import type { LocaleObject, ExtendProperyDescripters, VueI18nRoutingPluginOptions } from 'vue-i18n-routing'
import type { NuxtApp } from '#app'

Expand All @@ -60,9 +60,6 @@ type LocaleRoute = typeof localeRoute
type LocaleHead = typeof localeHead
type SwitchLocalePath = typeof switchLocalePath

// cache for locale messages
const cacheMessages = new Map<string, LocaleMessages<DefineLocaleMessage>>()

export default defineNuxtPlugin({
name: 'i18n:plugin',
parallel: parallelPlugin,
Expand Down Expand Up @@ -132,12 +129,12 @@ export default defineNuxtPlugin({
__DEBUG__ && console.log('first detect initial locale', initialLocale)

// load initial vue-i18n locale messages
vueI18nOptions.messages = await loadInitialMessages(vueI18nOptions.messages, localeMessages, {
...nuxtI18nOptions,
initialLocale,
fallbackLocale: vueI18nOptions.fallbackLocale,
vueI18nOptions.messages = await loadInitialMessages(vueI18nOptions.messages, localeLoaders, {
localeCodes,
cacheMessages
initialLocale,
lazy: nuxtI18nOptions.lazy,
defaultLocale: nuxtI18nOptions.defaultLocale,
fallbackLocale: vueI18nOptions.fallbackLocale
})

/**
Expand Down Expand Up @@ -214,11 +211,10 @@ export default defineNuxtPlugin({
})
composer.setLocale = async (locale: string) => {
const localeSetup = isInitialLocaleSetup(locale)
const [modified] = await loadAndSetLocale(locale, localeMessages, i18n, {
const [modified] = await loadAndSetLocale(locale, i18n, {
useCookie,
differentDomains,
initial: localeSetup,
cacheMessages,
skipSettingLocaleOnNavigate,
lazy
})
Expand Down Expand Up @@ -472,11 +468,10 @@ export default defineNuxtPlugin({
const localeSetup = isInitialLocaleSetup(locale)
__DEBUG__ && console.log('localeSetup', localeSetup)

const [modified] = await loadAndSetLocale(locale, localeMessages, i18n, {
const [modified] = await loadAndSetLocale(locale, i18n, {
useCookie,
differentDomains,
initial: localeSetup,
cacheMessages,
skipSettingLocaleOnNavigate,
lazy
})
Expand Down
31 changes: 12 additions & 19 deletions src/runtime/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { defineI18nMiddleware } from '@intlify/h3'
import { deepCopy } from '@intlify/shared'
import { nuxtI18nOptions, localeCodes, vueI18nConfigs, localeMessages } from '#internal/i18n/options.mjs'
import { nuxtI18nOptions, localeCodes, vueI18nConfigs, localeLoaders } from '#internal/i18n/options.mjs'
// @ts-ignore
import { localeDetector as _localeDetector } from '#internal/i18n/locale.detector.mjs'
import { loadVueI18nOptions, loadInitialMessages, makeFallbackLocaleCodes, loadLocale } from '../messages'
import { loadVueI18nOptions, loadInitialMessages, makeFallbackLocaleCodes, loadAndSetLocaleMessages } from '../messages'

import type { NitroAppPlugin } from 'nitropack'
import type { H3Event } from 'h3'
import type { NuxtApp } from 'nuxt/app'
import type { Locale, FallbackLocale, LocaleMessages, DefineLocaleMessage } from 'vue-i18n'
import type { Locale, FallbackLocale, DefineLocaleMessage } from 'vue-i18n'
import type { CoreContext } from '@intlify/h3'

const nuxtMock: { runWithContext: NuxtApp['runWithContext'] } = { runWithContext: async fn => await fn() }

export const nitroPlugin: NitroAppPlugin = async nitro => {
// cache for locale messages
const cacheMessages = new Map<string, LocaleMessages<DefineLocaleMessage>>()

// `defineI18nMiddleware` options (internally, options passed to`createCoreContext` in intlify / core) are compatible with vue-i18n options
const options = (await loadVueI18nOptions(vueI18nConfigs, nuxtMock)) as any // eslint-disable-line @typescript-eslint/no-explicit-any
options.messages = options.messages || {}
Expand All @@ -26,12 +22,12 @@ export const nitroPlugin: NitroAppPlugin = async nitro => {
const initialLocale = defaultLocale || options.locale || 'en-US'

// load initial locale messages for intlify/h3
options.messages = await loadInitialMessages(options.messages, localeMessages, {
...nuxtI18nOptions,
initialLocale,
fallbackLocale: options.fallbackLocale,
options.messages = await loadInitialMessages(options.messages, localeLoaders, {
localeCodes,
cacheMessages
initialLocale,
lazy: nuxtI18nOptions.lazy,
defaultLocale: nuxtI18nOptions.defaultLocale,
fallbackLocale: options.fallbackLocale
})

const localeDetector = async (
Expand All @@ -40,16 +36,13 @@ export const nitroPlugin: NitroAppPlugin = async nitro => {
): Promise<Locale> => {
const locale = _localeDetector(event, { defaultLocale: initialLocale, fallbackLocale: options.fallbackLocale })
if (lazy) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setter = (locale: Locale, message: Record<string, any>) => {
i18nContext.messages[locale] = i18nContext.messages[locale] || {}
deepCopy(message, i18nContext.messages[locale])
}
if (fallbackLocale) {
const fallbackLocales = makeFallbackLocaleCodes(fallbackLocale, [locale])
await Promise.all(fallbackLocales.map(locale => loadLocale({ locale, setter, localeMessages }, cacheMessages)))
await Promise.all(
fallbackLocales.map(locale => loadAndSetLocaleMessages(locale, localeLoaders, i18nContext.messages))
)
}
await loadLocale({ locale, setter, localeMessages }, cacheMessages)
await loadAndSetLocaleMessages(locale, localeLoaders, i18nContext.messages)
}
return locale
}
Expand Down
18 changes: 10 additions & 8 deletions src/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import {
import { joinURL, isEqual } from 'ufo'
import { isString, isFunction, isArray, isObject } from '@intlify/shared'
import { navigateTo, useNuxtApp, useRoute, useRuntimeConfig, useState } from '#imports'
import { nuxtI18nInternalOptions, nuxtI18nOptionsDefault, NUXT_I18N_MODULE_ID, isSSG } from '#build/i18n.options.mjs'
import {
nuxtI18nInternalOptions,
nuxtI18nOptionsDefault,
NUXT_I18N_MODULE_ID,
isSSG,
localeLoaders
} from '#build/i18n.options.mjs'
import {
detectBrowserLanguage,
getLocaleCookie,
Expand All @@ -42,12 +48,11 @@ import type {
I18nHeadOptions,
SeoAttributesOptions
} from 'vue-i18n-routing'
import type { I18n, I18nOptions, Locale, FallbackLocale, LocaleMessages, DefineLocaleMessage } from 'vue-i18n'
import type { I18n, I18nOptions, Locale, FallbackLocale } from 'vue-i18n'
import type { NuxtApp } from '#app'
import type { NuxtI18nOptions, DetectBrowserLanguageOptions, RootRedirectOptions } from '#build/i18n.options.mjs'
import type { DeepRequired } from 'ts-essentials'
import type { DetectLocaleContext } from './internal'
import type { LocaleLoader as LocaleInternalLoader } from './messages'
import type { HeadSafe } from '@unhead/vue'
import { useLocaleRoute, useRouteBaseName, useSwitchLocalePath } from '#i18n'

Expand Down Expand Up @@ -87,19 +92,16 @@ export async function finalizePendingLocaleChange(i18n: I18n) {

export async function loadAndSetLocale<Context extends NuxtApp = NuxtApp>(
newLocale: string,
localeMessages: Record<Locale, LocaleInternalLoader[]>,
i18n: I18n,
{
useCookie = nuxtI18nOptionsDefault.detectBrowserLanguage.useCookie,
skipSettingLocaleOnNavigate = nuxtI18nOptionsDefault.skipSettingLocaleOnNavigate,
differentDomains = nuxtI18nOptionsDefault.differentDomains,
initial = false,
cacheMessages = undefined,
lazy = false
}: Pick<DetectBrowserLanguageOptions, 'useCookie'> &
Pick<NuxtI18nOptions<Context>, 'lazy' | 'skipSettingLocaleOnNavigate' | 'differentDomains'> & {
initial?: boolean
cacheMessages?: Map<string, LocaleMessages<DefineLocaleMessage>>
} = {}
): Promise<[boolean, string]> {
const nuxtApp = useNuxtApp()
Expand Down Expand Up @@ -134,9 +136,9 @@ export async function loadAndSetLocale<Context extends NuxtApp = NuxtApp>(
const setter = (locale: Locale, message: Record<string, any>) => mergeLocaleMessage(i18n, locale, message)
if (i18nFallbackLocales) {
const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale])
await Promise.all(fallbackLocales.map(locale => loadLocale({ locale, setter, localeMessages }, cacheMessages)))
await Promise.all(fallbackLocales.map(locale => loadLocale(locale, localeLoaders, setter)))
}
await loadLocale({ locale: newLocale, setter, localeMessages }, cacheMessages)
await loadLocale(newLocale, localeLoaders, setter)
}

if (skipSettingLocaleOnNavigate) {
Expand Down
4 changes: 2 additions & 2 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ ${options.importStrings.length > 0 ? options.importStrings.join('\n') + '\n' : '
export const localeCodes = ${JSON.stringify(options.localeCodes, null, 2)}
export const localeMessages = {
${options.localeMessages
export const localeLoaders = {
${options.localeLoaders
.map(([key, val]) => {
return ` "${key}": [${val
.map(
Expand Down
Loading

0 comments on commit a2a883f

Please sign in to comment.