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

fix: options passed with installModule are overwritten #2882

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/content/docs/2.guide/12.extend-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Translations added this way will be loaded after those added in your project, an
Example:
::code-group

```ts[my-module-example/module.ts]
```ts [my-module-example/module.ts]
import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/2.guide/14.layers.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Layers pages
description: How Nuxt i18n handles layers.
title: Layers
description: Using layers to extends projects with Nuxt i18n.
---

Nuxt i18n module supports layers and will automatically combine i18n configuration of all extended layers. [Read more about layers here](https://nuxt.com/docs/getting-started/layers)
Expand Down
66 changes: 66 additions & 0 deletions docs/content/docs/2.guide/16.install-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Installing from a module
description: How to install Nuxt i18n using `installModule` inside of a module.
---

If you're a **module author** and want your module to install Nuxt i18n, you can do so using `installModule` but you will have to resolve paths used for `vueI18n`, `langDir` and those configured in `locales`.

::callout
We strongly recommend using [layers](/docs/guide/layers) for complete module installations over using `installModule`, layers are merged by priority which allows projects to overwrite options as desired and will not cause conflicts if more than one layer provides options for the Nuxt i18n module.

:br :br

If you would only like your module to provide translations, consider using the hook described in [extend-messages](/docs/guide/extend-messages) instead.
::

Note that when using `installModule`, the options passed will essentially have a higher priority than any layer (including the project layer), options are merged when possible and applicable but will otherwise override configurations.

Example:
::code-group

```ts [my-module-example/module.ts]
import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)

// paths needs to be resolved so absolute paths are used
await installModule('@nuxtjs/i18n', {
vueI18n: resolve('./i18n.config.ts'),
langDir: resolve('./lang'),
locales: [
{
code: 'en',
file: resolve('./lang/en.json'),
},
{
code: 'fr',
file: resolve('./lang/fr.json'),
},
]
})
}
})
```

```json [lang/en.json]
{
"my-module-example": {
"hello": "Hello from external module"
}
}
```

```json [lang/fr.json]
{
"my-module-example": {
"hello": "Bonjour depuis le module externe"
}
}
```

::

Now the project has access to new messages and can use them through `$t('my-module-example.hello')`.

7 changes: 7 additions & 0 deletions specs/basic_usage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,4 +617,11 @@ describe('basic usage', async () => {
expect(dom.querySelector('head #switch-locale-path').content).toEqual('/fr/composables')
expect(dom.querySelector('head #route-base-name').content).toEqual('nested-test-route')
})

test('(#2874) options `locales` and `vueI18n` passed using `installModule` are not overridden', async () => {
const { page } = await renderPage('/')

expect(await getText(page, '#install-module-locale')).toEqual('Installer module locale works!')
expect(await getText(page, '#install-module-vue-i18n')).toEqual('Installer module vue-i18n works!')
})
})
9 changes: 9 additions & 0 deletions specs/fixtures/basic_usage/installer-module/i18n.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { I18nOptions } from 'vue-i18n'

export default {
messages: {
en: {
installerModuleVueI18nMessage: 'Installer module vue-i18n works!'
}
}
} as I18nOptions
20 changes: 20 additions & 0 deletions specs/fixtures/basic_usage/installer-module/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createResolver, defineNuxtModule, installModule } from '@nuxt/kit'

export default defineNuxtModule({
async setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)

installModule('@nuxtjs/i18n', {
langDir: resolve('./locales'),
vueI18n: resolve('./i18n.config.ts'),
locales: [
{
code: 'en',
iso: 'en',
files: [resolve('./locales/en.json')],
name: 'English'
}
]
})
}
})
3 changes: 3 additions & 0 deletions specs/fixtures/basic_usage/installer-module/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"installerModuleLocaleMessage": "Installer module locale works!"
}
2 changes: 1 addition & 1 deletion specs/fixtures/basic_usage/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// https://nuxt.com/docs/guide/directory-structure/nuxt.config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: ['./layer-module', '@nuxtjs/i18n'],
modules: ['./layer-module', './installer-module', '@nuxtjs/i18n'],
runtimeConfig: {
public: {
runtimeValue: 'Hello from runtime config!',
Expand Down
4 changes: 4 additions & 0 deletions specs/fixtures/basic_usage/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,9 @@ useHead({
<code id="global-scope-properties">{{ localeProperties }}</code>
<LocalScope />
</section>
<section>
<div id="install-module-locale">{{ $t('installerModuleLocaleMessage') }}</div>
<div id="install-module-vue-i18n">{{ $t('installerModuleVueI18nMessage') }}</div>
</section>
</div>
</template>
62 changes: 58 additions & 4 deletions src/layers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import createDebug from 'debug'
import { getLayerI18n, getProjectPath, mergeConfigLocales, resolveVueI18nConfigInfo, formatMessage } from './utils'
import {
getLayerI18n,
getProjectPath,
mergeConfigLocales,
resolveVueI18nConfigInfo,
formatMessage,
getLocaleFiles
} from './utils'

import { useLogger } from '@nuxt/kit'
import { isAbsolute, resolve } from 'pathe'
import { isAbsolute, parse, resolve } from 'pathe'
import { isString } from '@intlify/shared'
import { NUXT_I18N_MODULE_ID } from './constants'

import type { LocaleConfig } from './utils'
import type { Nuxt, NuxtConfigLayer } from '@nuxt/schema'
import type { NuxtI18nOptions, VueI18nConfigPathInfo } from './types'
import type { LocaleObject, NuxtI18nOptions, VueI18nConfigPathInfo } from './types'

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

Expand Down Expand Up @@ -60,6 +67,11 @@ export const checkLayerOptions = (options: NuxtI18nOptions, nuxt: Nuxt) => {
}
}

/**
* Merges `locales` configured by each layer and resolves the locale `files` to absolute paths.
*
* This overwrites `options.locales`
*/
export const applyLayerOptions = (options: NuxtI18nOptions, nuxt: Nuxt) => {
const project = nuxt.options._layers[0]
const layers = nuxt.options._layers
Expand Down Expand Up @@ -107,6 +119,43 @@ export const mergeLayerLocales = (options: NuxtI18nOptions, nuxt: Nuxt) => {
}
})

const localeObjects = options.locales.filter((x): x is LocaleObject => x !== 'string')
const absoluteConfigMap = new Map<string, LocaleConfig>()
// locale `files` use absolute paths installed using `installModule`
const absoluteLocaleObjects = localeObjects.filter(localeObject => {
const files = getLocaleFiles(localeObject)
if (files.length === 0) return false
return files.every(
file => isAbsolute(file.path) && configs.find(config => config.langDir === parse(file.path).dir) == null
)
})

// filter layer locales
for (const absoluteLocaleObject of absoluteLocaleObjects) {
const files = getLocaleFiles(absoluteLocaleObject)
if (files.length === 0) continue
const langDir = parse(files[0].path).dir

if (absoluteConfigMap.has(langDir)) {
const entry = absoluteConfigMap.get(langDir)

absoluteConfigMap.set(langDir, {
langDir,
projectLangDir,
locales: [...(entry!.locales! as LocaleObject[]), absoluteLocaleObject]
})
continue
}

absoluteConfigMap.set(langDir, {
langDir,
projectLangDir,
locales: [absoluteLocaleObject]
})
}

configs.unshift(...Array.from(absoluteConfigMap.values()))

return mergeConfigLocales(configs)
}

Expand All @@ -126,7 +175,7 @@ export const getLayerLangPaths = (nuxt: Nuxt) => {
}) as string[]
}

export async function resolveLayerVueI18nConfigInfo(nuxt: Nuxt, buildDir: string) {
export async function resolveLayerVueI18nConfigInfo(options: NuxtI18nOptions, nuxt: Nuxt, buildDir: string) {
const logger = useLogger(NUXT_I18N_MODULE_ID)
const layers = [...nuxt.options._layers]
const project = layers.shift() as NuxtConfigLayer
Expand Down Expand Up @@ -158,5 +207,10 @@ export async function resolveLayerVueI18nConfigInfo(nuxt: Nuxt, buildDir: string
})
)

// use `vueI18n` passed by `installModule`
if (options.vueI18n && isAbsolute(options.vueI18n)) {
resolved.unshift(await resolveVueI18nConfigInfo({ vueI18n: options.vueI18n }, buildDir, parse(options.vueI18n).dir))
}

return resolved.filter((x): x is Required<VueI18nConfigPathInfo> => x != null)
}
2 changes: 1 addition & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export default defineNuxtModule<NuxtI18nOptions>({
* resolve vue-i18n config path
*/

const vueI18nConfigPaths = await resolveLayerVueI18nConfigInfo(nuxt, nuxt.options.buildDir)
const vueI18nConfigPaths = await resolveLayerVueI18nConfigInfo(options, nuxt, nuxt.options.buildDir)
debug('VueI18nConfigPaths', vueI18nConfigPaths)

/**
Expand Down