Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support server-side i18n integration #2558

Merged
merged 50 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b186708
feat: support server-side i18n integration
kazupon Nov 16, 2023
96eb3a3
fix: locale detector loading on server-side
kazupon Nov 16, 2023
de37cd0
refactor
kazupon Nov 16, 2023
4c760a0
merge from main branch
kazupon Nov 16, 2023
2e3f2e0
fix: more deps
kazupon Nov 16, 2023
8a29b9a
fix: cannot bundle intlify/core
kazupon Nov 22, 2023
69866ca
fix: initial options loading
kazupon Nov 23, 2023
01b8c44
refactor: function name
kazupon Nov 23, 2023
33fea31
Merge branch 'main' into feat/intlify-h3-integration2
kazupon Nov 23, 2023
dd33773
refactor: remove context
kazupon Nov 23, 2023
809b837
fix: type errors
kazupon Nov 23, 2023
6f252cf
refactor: argument ordering
kazupon Nov 23, 2023
3fc1bd7
fix: pass user cache messages
kazupon Nov 23, 2023
3b5c833
fix: wrong judgement enabling
kazupon Nov 24, 2023
a0f5b64
fix: not work useRuntimeConfig
kazupon Nov 24, 2023
153ee61
Merge branch 'main' into feat/intlify-h3-integration3
kazupon Nov 25, 2023
0a7399c
feat: `useRuntimeConfig` support (#2572)
BobbieGoede Nov 25, 2023
181e1c4
fix: spec tests
kazupon Nov 25, 2023
0ec9b0a
chore: bump nitropack
kazupon Nov 25, 2023
75eb4a9
fix: salvage basic_usage.spec.ts
kazupon Nov 25, 2023
01bb141
fix: Avoid order broken of `vueI18nConfigPaths`
kazupon Nov 25, 2023
6c162d3
fix: type error
kazupon Nov 25, 2023
1ae6f0f
refactor: move to messages
kazupon Nov 25, 2023
1df540b
fix: lazy load implementation
kazupon Nov 28, 2023
a2a9992
fix: i18n resources always lazy loading for nitro side
kazupon Nov 28, 2023
d5cae4a
fix: reduce resource size for nitropack bundling
kazupon Nov 28, 2023
cb5f834
fix: drop import assetion
kazupon Nov 28, 2023
834b1ab
refactor
kazupon Nov 28, 2023
2f93b94
fix: h3 integration layer support (#2589)
BobbieGoede Dec 4, 2023
fbfab0b
Merge branch 'main' into feat/intlify-h3-integration
kazupon Dec 5, 2023
d02ab1c
chore: update deps
kazupon Dec 5, 2023
9a37015
chore: upgrade vitest
kazupon Dec 5, 2023
f8543cc
fix: cannot resolve type for intlify/h3 utilites
kazupon Dec 5, 2023
1ea7efb
fix: bump intlify/h3 and utilts
kazupon Dec 6, 2023
502b088
fix: tweak locale detector for playground
kazupon Dec 6, 2023
f1c1d55
refactor
kazupon Dec 6, 2023
cecac24
add `defineI18nLocaleDetector` composable
kazupon Dec 6, 2023
27ed64e
docs: add `useTranslation`
kazupon Dec 6, 2023
fd8c141
fix: remove console log
kazupon Dec 6, 2023
bf5fab0
docs: update server-side translations
kazupon Dec 6, 2023
5ef3b4c
Update docs/content/2.guide/16.server-side-translations.md
kazupon Dec 6, 2023
955e0fd
Update docs/content/2.guide/16.server-side-translations.md
kazupon Dec 6, 2023
14b9d5b
Update docs/content/2.guide/16.server-side-translations.md
kazupon Dec 6, 2023
990a52e
Update docs/content/2.guide/16.server-side-translations.md
kazupon Dec 6, 2023
1ae12b0
Update docs/content/4.API/1.composables.md
kazupon Dec 6, 2023
551ecea
Update docs/content/4.API/1.composables.md
kazupon Dec 6, 2023
d1454f9
Update docs/content/4.API/1.composables.md
kazupon Dec 6, 2023
f92b9a6
Update docs/content/4.API/1.composables.md
kazupon Dec 6, 2023
88f1a98
Update docs/content/4.API/1.composables.md
kazupon Dec 6, 2023
1520ad2
Update docs/content/2.guide/16.server-side-translations.md
kazupon Dec 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: initial options loading
  • Loading branch information
kazupon committed Nov 23, 2023
commit 69866ca436f1f6c2f105a5903a9a1235829fcbc0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"./package.json": "./package.json"
},
"imports": {
"#i18n": "./dist/runtime/composables.mjs"
"#i18n": "./dist/runtime/composables/index.mjs"
},
"main": "./dist/module.cjs",
"module": "./dist/module.mjs",
Expand Down
39 changes: 27 additions & 12 deletions src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ export type LoaderOptions = {
vueI18nConfigPaths: Required<VueI18nConfigPathInfo>[]
localeInfo: LocaleInfo[]
nuxtI18nOptions: NuxtI18nOptions
isServer: boolean
}

const debug = createDebug('@nuxtjs/i18n:gen')

const generateVueI18nConfiguration = (config: Required<VueI18nConfigPathInfo>): string => {
return genDynamicImport(genImportSpecifier(config.meta, 'config'), {
const generateVueI18nConfiguration = (config: Required<VueI18nConfigPathInfo>, isServer = false): string => {
return genDynamicImport(genImportSpecifier({ ...config.meta, isServer }, 'config'), {
comment: `webpackChunkName: "${config.meta.key}"`
})
}
Expand All @@ -47,15 +48,18 @@ function simplifyLocaleOptions(nuxt: Nuxt, locales: LocaleObject[]) {
})
}

export function generateLoaderOptions(nuxt: Nuxt, { nuxtI18nOptions, vueI18nConfigPaths, localeInfo }: LoaderOptions) {
export function generateLoaderOptions(
nuxt: Nuxt,
{ nuxtI18nOptions, vueI18nConfigPaths, localeInfo, isServer }: LoaderOptions
) {
debug('generateLoaderOptions: lazy', nuxtI18nOptions.lazy)

const importMapper = new Map<string, { key: string; load: string; cache: string }>()
const importStrings: string[] = []

function generateLocaleImports(locale: string, meta: NonNullable<LocaleInfo['meta']>[number]) {
function generateLocaleImports(locale: string, meta: NonNullable<LocaleInfo['meta']>[number], isServer = false) {
if (importMapper.has(meta.key)) return
const importSpecifier = genImportSpecifier(meta, 'locale', { locale })
const importSpecifier = genImportSpecifier({ ...meta, isServer }, 'locale', { locale })
const importer = { code: locale, key: meta.loadPath, load: '', cache: meta.file.cache ?? true }

if (nuxtI18nOptions.lazy) {
Expand All @@ -79,7 +83,7 @@ export function generateLoaderOptions(nuxt: Nuxt, { nuxtI18nOptions, vueI18nConf
* Prepare locale file imports
*/
for (const locale of localeInfo) {
locale?.meta?.forEach(meta => generateLocaleImports(locale.code, meta))
locale?.meta?.forEach(meta => generateLocaleImports(locale.code, meta, isServer))
}

/**
Expand All @@ -88,7 +92,7 @@ export function generateLoaderOptions(nuxt: Nuxt, { nuxtI18nOptions, vueI18nConf
const vueI18nConfigImports = vueI18nConfigPaths
.reverse()
.filter(config => config.absolute !== '')
.map(config => generateVueI18nConfiguration(config))
.map(config => generateVueI18nConfiguration(config, isServer))

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

Expand All @@ -111,25 +115,36 @@ export function generateLoaderOptions(nuxt: Nuxt, { nuxtI18nOptions, vueI18nConf
}

function genImportSpecifier(
{ loadPath, path, parsed, hash, type }: Pick<FileMeta, 'loadPath' | 'path' | 'parsed' | 'hash' | 'type'>,
{
loadPath,
path,
parsed,
hash,
type,
isServer
}: Pick<FileMeta, 'loadPath' | 'path' | 'parsed' | 'hash' | 'type'> & { isServer?: boolean },
resourceType: PrerenderTarget['type'] | undefined,
query: Record<string, string> = {}
) {
if (!EXECUTABLE_EXTENSIONS.includes(parsed.ext)) return loadPath
const getLoadPath = () => (!isServer ? loadPath : path)

if (!EXECUTABLE_EXTENSIONS.includes(parsed.ext)) {
return getLoadPath()
}

if (resourceType != null && type === 'unknown') {
throw new Error(`'unknown' type in '${path}'.`)
}

if (resourceType === 'locale') {
return withQuery(loadPath, type === 'dynamic' ? { hash, ...query } : {})
return !isServer ? withQuery(getLoadPath(), type === 'dynamic' ? { hash, ...query } : {}) : getLoadPath()
}

if (resourceType === 'config') {
return withQuery(loadPath, { hash, ...query, ...{ config: 1 } })
return !isServer ? withQuery(getLoadPath(), { hash, ...query, ...{ config: 1 } }) : getLoadPath()
}

return loadPath
return getLoadPath()
}

/* eslint-enable @typescript-eslint/no-explicit-any */
25 changes: 15 additions & 10 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from './utils'
import { distDir, runtimeDir } from './dirs'
import { applyLayerOptions, checkLayerOptions, resolveLayerVueI18nConfigInfo } from './layers'
import { generateTemplateNuxtI18nOptions } from './template'

import type { HookResult } from '@nuxt/schema'
import type { NuxtI18nOptions } from './types'
Expand Down Expand Up @@ -190,7 +191,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
addPlugin(resolve(runtimeDir, 'plugins/i18n'))

// for composables
nuxt.options.alias['#i18n'] = resolve(distDir, 'runtime/composables.mjs')
nuxt.options.alias['#i18n'] = resolve(distDir, 'runtime/composables/index.mjs')
nuxt.options.build.transpile.push('#i18n')

// TODO: We don't want to resolve the following as a template,
Expand All @@ -208,15 +209,13 @@ export default defineNuxtModule<NuxtI18nOptions>({
src: resolve(distDir, 'runtime/utils.mjs')
})

addTemplate({
filename: NUXT_I18N_TEMPLATE_OPTIONS_KEY,
src: resolve(distDir, 'runtime/templates/options.template.mjs'),
write: true,
options: {
const genTemplate = (isServer: boolean) => {
return generateTemplateNuxtI18nOptions({
...generateLoaderOptions(nuxt, {
vueI18nConfigPaths,
localeInfo,
nuxtI18nOptions: options
nuxtI18nOptions: options,
isServer
}),
NUXT_I18N_MODULE_ID,
localeCodes,
Expand All @@ -225,7 +224,13 @@ export default defineNuxtModule<NuxtI18nOptions>({
dev: nuxt.options.dev,
isSSG: nuxt.options._generate,
parallelPlugin: options.parallelPlugin
}
})
}

addTemplate({
filename: NUXT_I18N_TEMPLATE_OPTIONS_KEY,
write: true,
getContents: () => genTemplate(false)
})

/**
Expand Down Expand Up @@ -277,7 +282,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
* setup nitro
*/

await setupNitro(nuxt, options)
await setupNitro(nuxt, options, genTemplate(true))

/**
* auto imports
Expand Down Expand Up @@ -307,7 +312,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
].map(key => ({
name: key,
as: key,
from: resolve(runtimeDir, 'composables')
from: resolve(runtimeDir, 'composables/index')
}))
])

Expand Down
52 changes: 23 additions & 29 deletions src/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ import createDebug from 'debug'
import { assign } from '@intlify/shared'
import { resolveModuleExportNames } from 'mlly'
import { defu } from 'defu'
import { addServerPlugin, addTemplate, createResolver, resolvePath, useLogger } from '@nuxt/kit'
import { addServerPlugin, createResolver, resolvePath, useLogger } from '@nuxt/kit'
import { getFeatureFlags } from './bundler'
import { isExists } from './utils'
import { EXECUTABLE_EXTENSIONS, NUXT_I18N_MODULE_ID } from './constants'
import {
EXECUTABLE_EXTENSIONS,
NUXT_I18N_MODULE_ID,
NUXT_I18N_COMPOSABLE_DEFINE_LOCALE,
NUXT_I18N_COMPOSABLE_DEFINE_CONFIG
} from './constants'

import type { NuxtI18nOptions } from './types'
import type { Nuxt } from '@nuxt/schema'
import type { NuxtI18nOptions } from './types'

const debug = createDebug('@nuxtjs/i18n:nitro')

export async function setupNitro(nuxt: Nuxt, nuxtOptions: Required<NuxtI18nOptions>) {
export async function setupNitro(nuxt: Nuxt, nuxtOptions: Required<NuxtI18nOptions>, nuxtI18nOptionsCode: string) {
console.log('#internal/i18n/options.mjs', nuxtI18nOptionsCode)

const { resolve } = createResolver(import.meta.url)
const [enableServerIntegration, localeDetectionPath] = await resolveLocaleDetectorPath(nuxt, nuxtOptions)

Expand All @@ -24,45 +31,32 @@ export async function setupNitro(nuxt: Nuxt, nuxtOptions: Required<NuxtI18nOptio
})

nitroConfig.virtual = nitroConfig.virtual || {}
/**
* NOTE:
* WIP
* On the nitro side, we can share code by using virtual modules.
* We want to share nuxt i18n options and use the same settings in the nitro plugin.
*
*/
nitroConfig.virtual['#internal/i18n/options.mjs'] = () => nuxtI18nOptionsCode
nitroConfig.virtual['#internal/i18n/locale.detector.mjs'] = () => `
import localeDetector from ${JSON.stringify(localeDetectionPath)}
export { localeDetector }
`

// auto import `@intlify/h3` utilities for server-side
// auto import for server-side
if (nitroConfig.imports) {
// `@intlify/h3` utilities
const h3Exports = await resolveModuleExportNames('@intlify/h3', { url: import.meta.url })
const excludes = ['defineI18nMiddleware', 'detectLocaleFromAcceptLanguageHeader']
nitroConfig.imports.presets = nitroConfig.imports.presets || []
nitroConfig.imports.presets.push({
from: '@intlify/h3',
imports: h3Exports.filter(name => !excludes.includes(name))
})
// `defineI18nLocale` and `defineI18nConfig`
nitroConfig.imports.imports = nitroConfig.imports.imports || []
nitroConfig.imports.imports.push(
...[NUXT_I18N_COMPOSABLE_DEFINE_LOCALE, NUXT_I18N_COMPOSABLE_DEFINE_CONFIG].map(key => ({
name: key,
as: key,
from: resolve('runtime/composables/shared')
}))
)
}

/**
* NOTE:
* WIP
* This is a test code to see if the nitro plugin can import when using `getContents` instead of the template file at `addTemplate`.
*/
addTemplate({
filename: 'example-file.mjs',
getContents: data => {
console.log('exmpale-file.mjs', data.options)
return 'export const example = 42;\nexport const foo = "bar"'
},
options: {
foo: 'bar',
test: 1
}
})
}

if (nuxt.options.ssr) {
Expand Down
4 changes: 1 addition & 3 deletions src/options.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { NuxtI18nOptions, NuxtI18nInternalOptions, RootRedirectOptions } from './types'
import type { NuxtI18nOptions, NuxtI18nInternalOptions, RootRedirectOptions, VueI18nConfig } from './types'
import type { NuxtI18nOptionsDefault } from './constants'
import type { DeepRequired } from 'ts-essentials'
import type { I18nOptions } from 'vue-i18n'

/**
* stub type definition for @nuxtjs/i18n internally
Expand All @@ -20,7 +19,6 @@ export const loadMessages: () => Promise<any> = () => Promise.resolve({})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const localeMessages: Record<string, LocaleLoader[]> = {}

export type VueI18nConfig = () => Promise<{ default: I18nOptions | (() => I18nOptions | Promise<I18nOptions>) }>
export const vueI18nConfigs: VueI18nConfig[]

export const localeCodes: string[] = []
Expand Down
57 changes: 2 additions & 55 deletions src/runtime/composables.ts β†’ src/runtime/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import type { Ref } from 'vue'
import type { DetectBrowserLanguageOptions } from '#build/i18n.options.mjs'

export * from 'vue-i18n'
export * from './shared'
export type { LocaleObject } from 'vue-i18n-routing'
import type { Locale, LocaleMessages, DefineLocaleMessage, I18nOptions } from 'vue-i18n'
import type { Locale } from 'vue-i18n'

/**
* The `useRouteBaseName` composable returns a function that gets the route's base name.
Expand Down Expand Up @@ -248,57 +249,3 @@ export function defineI18nRoute(route: I18nRoute | false): void {
warnRuntimeUsage('defineI18nRoute')
}
}

type MaybePromise<T> = T | Promise<T>

/**
* The `defineI18nLocale` defines a composable function to dynamically load locale messages.
*
* @remarks
* This function is used to dynamically load a locale with lazy-load translations.
*
* You can use at JavaScript and TypeScript extension formats.
*
* @param locale - A target locale that is passed from nuxt i18n module.
*
* @returns Returns the locale messages object that will be resolved with Promise.
*/
export type LocaleLoader<Messages = LocaleMessages<DefineLocaleMessage>, Locales = Locale> = (
locale: Locales
) => MaybePromise<Messages>

/**
* Define locale loader for dynamic locale messages loading
*
* @param locale - The target locale
*
* @returns The defined locale
*/
export function defineI18nLocale<Messages = LocaleMessages<DefineLocaleMessage>, Locales = Locale>(
locale: LocaleLoader<Messages, Locales>
): LocaleLoader<Messages, Locales> {
return locale
}

/**
* The `defineI18nConfig` defines a composable function to vue-i18n configuration.
*
* @remarks
* This function is used to pass the `createI18n` options on nuxt i18n module.
*
* For more details about configuration, see the [Vue I18n documentation](https://vue-i18n.intlify.dev/api/general.html#createi18n).
*
* @returns Return vue-i18n options object that will be resolved by Promise.
*/
export type ConfigLoader<Config extends I18nOptions> = () => MaybePromise<Config>

/**
* Define configuration for vue-i18n runtime plugin
*
* @param config - The target configuration for vue-i18n
*
* @returns The defined configuration
*/
export function defineI18nConfig<Config extends I18nOptions>(config: ConfigLoader<Config>): ConfigLoader<Config> {
return config
}
Loading