diff --git a/.vscode/settings.json b/.vscode/settings.json
index c6341b442..f88c60502 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,4 +10,4 @@
"source.fixAll.eslint": true
},
"typescript.tsdk": "node_modules/typescript/lib"
-}
+}
\ No newline at end of file
diff --git a/docs/content/2.guide/15.layers.md b/docs/content/2.guide/15.layers.md
new file mode 100644
index 000000000..098509dbe
--- /dev/null
+++ b/docs/content/2.guide/15.layers.md
@@ -0,0 +1,87 @@
+# Layers
+
+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)
+
+---
+
+### Merging strategy
+As described in the [Nuxt layer authoring guide](https://nuxt.com/docs/guide/going-further/layers#multi-layer-support-for-nuxt-modules)
+> * Earlier items in the `_layers` array have higher priority and override later ones
+> * The user's project is the first item in the `_layers` array
+
+Mixing locale configuration such as lazy loading objects and strings may not work as expected, Nuxt i18n will attempt to merge layers as best it can. Consistency of i18n configuration between layers will be most effective.
+
+
+### Pages & Routing
+
+Pages in the `pages` directory from extended layers will automatically be merged and have i18n support as if they were part of your project.
+
+Page routes defined in `i18n.pages` in each layer configuration will be merged as well.
+
+### Locales
+::alert{type="warning"}
+A project extending layers containing lazy-loaded translations is still required to have `lazy` and `langDir` options configured.
+::
+
+Extending your project with a layer that contains locales can be done as follows:
+
+::code-group
+ ::code-block{label="Project config" active}
+ ```ts {} [nuxt.config.ts]
+ export default defineNuxtConfig({
+ extends: ['my-layer'],
+ modules: ['@nuxtjs/i18n'],
+ i18n: {
+ lazy: true,
+ langDir: './lang',
+ locales: [{ code: 'en', file: 'en.json' }],
+ },
+ })
+ ```
+ ::
+ ::code-block{label="Layer config"}
+ ```ts
+ export default defineNuxtConfig({
+ modules: ['@nuxtjs/i18n'],
+ i18n: {
+ lazy: true,
+ langDir: './lang',
+ locales: [
+ { code: 'en', file: 'en.json' },
+ { code: 'nl', file: 'nl.json' },
+ ],
+ },
+ })
+ ```
+ ::
+::
+
+This example would result in the project supporting two locales (`en`, `nl`) and would add the additional messages added for the `en` locale.
+
+::code-group
+ ::code-block{label="project/lang/en.json" active}
+ ```ts {} [project/lang/en.json]
+ {
+ "title": "foo"
+ }
+ ```
+ ::
+ ::code-block{label="project/my-layer/lang/en.json"}
+ ```ts {} [project/my-layer/lang/en.json]
+ {
+ "title": "layer title",
+ "description": "bar"
+ }
+ ```
+ ::
+ ::code-block{label="result en.json"}
+ ```ts {} [result]
+ {
+ // earlier layers take priority
+ "title": "foo",
+ "description": "bar"
+ }
+ ```
+ ::
+::
+
diff --git a/docs/content/2.guide/15.migrating.md b/docs/content/2.guide/16.migrating.md
similarity index 100%
rename from docs/content/2.guide/15.migrating.md
rename to docs/content/2.guide/16.migrating.md
diff --git a/playground/layers/i18n-layer/locales/en.json b/playground/layers/i18n-layer/locales/en.json
new file mode 100644
index 000000000..1ed90b058
--- /dev/null
+++ b/playground/layers/i18n-layer/locales/en.json
@@ -0,0 +1,3 @@
+{
+ "layerText": "This is a merged locale key"
+}
\ No newline at end of file
diff --git a/playground/layers/i18n-layer/locales/fr.json b/playground/layers/i18n-layer/locales/fr.json
new file mode 100644
index 000000000..32c101acd
--- /dev/null
+++ b/playground/layers/i18n-layer/locales/fr.json
@@ -0,0 +1,3 @@
+{
+ "layerText": "This is a merged locale key in French"
+}
\ No newline at end of file
diff --git a/playground/layers/i18n-layer/locales/nl.json b/playground/layers/i18n-layer/locales/nl.json
new file mode 100644
index 000000000..22977bca6
--- /dev/null
+++ b/playground/layers/i18n-layer/locales/nl.json
@@ -0,0 +1,3 @@
+{
+ "layerText": "This is a merged locale key in Dutch"
+}
\ No newline at end of file
diff --git a/playground/layers/i18n-layer/nuxt.config.ts b/playground/layers/i18n-layer/nuxt.config.ts
new file mode 100644
index 000000000..4c17e8f13
--- /dev/null
+++ b/playground/layers/i18n-layer/nuxt.config.ts
@@ -0,0 +1,62 @@
+// import type { NuxtApp } from 'nuxt/dist/app/index'
+
+// https://nuxt.com/docs/guide/directory-structure/nuxt.config
+export default defineNuxtConfig({
+ modules: ['@nuxtjs/i18n'],
+ i18n: {
+ langDir: 'locales',
+ lazy: true,
+ baseUrl: 'http://localhost:3000',
+ customRoutes: 'config',
+ pages: {
+ history: {
+ en: '/history',
+ fr: '/history-fr',
+ nl: '/geschiedenis'
+ }
+ },
+ locales: [
+ {
+ code: 'en',
+ iso: 'en-US',
+ file: 'en.json',
+ // domain: 'localhost',
+ name: 'English'
+ },
+ {
+ code: 'fr',
+ iso: 'fr-FR',
+ file: 'fr.json',
+ // domain: 'localhost',
+ name: 'Francais'
+ },
+ {
+ code: 'nl',
+ iso: 'nl-NL',
+ file: 'nl.json',
+ // domain: 'localhost',
+ name: 'Nederlands'
+ }
+ // {
+ // code: 'en-GB',
+ // iso: 'en-GB',
+ // files: ['en.json', 'en-GB.json'],
+ // name: 'English (UK)'
+ // },
+ // {
+ // code: 'ja',
+ // iso: 'ja-JP',
+ // file: 'ja.json',
+ // domain: 'mydomain.com',
+ // name: 'Japanses'
+ // },
+ // {
+ // code: 'fr',
+ // iso: 'fr-FR',
+ // file: 'fr.json',
+ // domain: 'mydomain.fr',
+ // name: 'Français'
+ // }
+ ]
+ }
+})
diff --git a/playground/layers/i18n-layer/pages/about.vue b/playground/layers/i18n-layer/pages/about.vue
new file mode 100644
index 000000000..3b16ea9bd
--- /dev/null
+++ b/playground/layers/i18n-layer/pages/about.vue
@@ -0,0 +1,9 @@
+
+
+
+ This about page is empty!
+
diff --git a/playground/layers/i18n-layer/pages/history.vue b/playground/layers/i18n-layer/pages/history.vue
new file mode 100644
index 000000000..7b8b46cb0
--- /dev/null
+++ b/playground/layers/i18n-layer/pages/history.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index c46c7fd82..987a7a0ba 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -3,8 +3,8 @@ import type { NuxtApp } from 'nuxt/dist/app/index'
// https://nuxt.com/docs/guide/directory-structure/nuxt.config
export default defineNuxtConfig({
+ extends: ['layers/i18n-layer'],
modules: [Module1, '@nuxtjs/i18n', '@nuxt/devtools'],
-
vite: {
build: {
minify: false
@@ -71,6 +71,9 @@ export default defineNuxtConfig({
dynamicRouteParams: true,
// customRoutes: 'config',
pages: {
+ history: {
+ ja: '/history-ja'
+ },
about: {
ja: '/about-ja'
}
diff --git a/playground/pages/index.vue b/playground/pages/index.vue
index a93042e07..3200e2062 100644
--- a/playground/pages/index.vue
+++ b/playground/pages/index.vue
@@ -18,6 +18,8 @@ console.log('route base name', getRouteBaseName())
console.log('useBrowserLocale', useBrowserLocale())
console.log('localeProperties', localeProperties)
console.log('foo', t('foo'))
+console.log('message if local layer merged:', t('layerText'))
+console.log('message if github layer merged:', t('layer-test-key'))
function getLocaleName(code: string) {
const locale = (locales.value as LocaleObject[]).find(i => i.code === code)
@@ -54,7 +56,8 @@ definePageMeta({
Current Language: {{ getLocaleName(locale) }}
Current Strategy: {{ strategy }}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fa0ed05dc..04b272f30 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -129,7 +129,7 @@ importers:
'@nuxtjs/i18n': link:..
nuxt: ^3.1.0
devDependencies:
- '@nuxt/devtools': /@nuxt/devtools-edge/0.2.5-27991385.f13be91_nuxt@3.1.2
+ '@nuxt/devtools': /@nuxt/devtools-edge/0.2.5-27994283.f736291_nuxt@3.1.2
'@nuxtjs/i18n': link:..
nuxt: 3.1.2
@@ -141,6 +141,14 @@ importers:
'@nuxtjs/i18n': link:../../..
nuxt: 3.1.2
+ specs/fixtures/basic_layer:
+ specifiers:
+ '@nuxtjs/i18n': link:../../..
+ nuxt: ^3.1.0
+ devDependencies:
+ '@nuxtjs/i18n': link:../../..
+ nuxt: 3.1.2
+
specs/fixtures/basic_usage:
specifiers:
'@nuxtjs/i18n': link:../../..
@@ -2167,14 +2175,14 @@ packages:
resolution: {integrity: sha512-YBI/6o2EBz02tdEJRBK8xkt3zvOFOWlLBf7WKYGBsSYSRtjjgrqPe2skp6VLLmKx5WbHHDNcW+6oACaurxGzeA==}
dev: true
- /@nuxt/devtools-edge/0.2.5-27991385.f13be91_nuxt@3.1.2:
- resolution: {integrity: sha512-+BJYCiKq8EK0oF4PoRzpudQQd6UkNyKnIcvLXKmU2Wf4HntAQrfkeoR3DwAGnQu8acwk/UX9K0YwoatvbH7irw==}
+ /@nuxt/devtools-edge/0.2.5-27994283.f736291_nuxt@3.1.2:
+ resolution: {integrity: sha512-MxHAwtKKvUpUR7pUURRbNiT1QqGOSkD6HIyU+Rs8/tbNyD+Fb15rvpfh/HdR2aQmG/3wDFqBiXIrIckacFcVpg==}
hasBin: true
peerDependencies:
nuxt: ^3.3.1
vite: '*'
dependencies:
- '@nuxt/devtools-kit': /@nuxt/devtools-kit-edge/0.2.5-27991385.f13be91_nuxt@3.1.2
+ '@nuxt/devtools-kit': /@nuxt/devtools-kit-edge/0.2.5-27994283.f736291_nuxt@3.1.2
'@nuxt/kit': 3.3.1
birpc: 0.2.10
consola: 2.15.3
@@ -2200,8 +2208,8 @@ packages:
- supports-color
dev: true
- /@nuxt/devtools-kit-edge/0.2.5-27991385.f13be91_nuxt@3.1.2:
- resolution: {integrity: sha512-vkvi226v1um9ZpNGWjNJ55PP3le6TPZbsdIZvpTQaT57ua0RbNqoUPxKj0MhObtPJAud6F1SVo6s4JVMNjVemg==}
+ /@nuxt/devtools-kit-edge/0.2.5-27994283.f736291_nuxt@3.1.2:
+ resolution: {integrity: sha512-9mRimi9Qcx7hQ4Zff2lYJVWRzRLNhv+Xl/5YeH8KYbVxWcy3Qud2hC3GTmEQrXBIGlMenaDXAUU5FRx3dVtlCQ==}
peerDependencies:
nuxt: ^3.3.1
vite: '*'
@@ -2386,7 +2394,7 @@ packages:
c12: 1.1.2
create-require: 1.1.1
defu: 6.1.2
- hookable: 5.4.2
+ hookable: 5.5.1
jiti: 1.18.2
pathe: 1.1.0
pkg-types: 1.0.2
@@ -2500,7 +2508,7 @@ packages:
h3: 1.5.0
knitwork: 1.0.0
magic-string: 0.27.0
- mlly: 1.2.0
+ mlly: 1.1.1
ohash: 1.0.0
pathe: 1.1.0
perfect-debounce: 0.1.3
@@ -2612,7 +2620,7 @@ packages:
/@nuxtjs/color-mode/3.2.0:
resolution: {integrity: sha512-isDR01yfadopiHQ/VEVUpyNSPrk5PCjUHS4t1qYRZwuRGefU4s9Iaxf6H9nmr1QFzoMgTm+3T0r/54jLwtpZbA==}
dependencies:
- '@nuxt/kit': 3.2.3
+ '@nuxt/kit': 3.3.1
lodash.template: 4.5.0
pathe: 1.1.0
transitivePeerDependencies:
@@ -3183,7 +3191,7 @@ packages:
bindings: 1.5.0
estree-walker: 2.0.2
glob: 7.2.3
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
micromatch: 4.0.5
node-gyp-build: 4.5.0
resolve-from: 5.0.0
@@ -3330,7 +3338,7 @@ packages:
'@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.21.3
'@babel/template': 7.20.7
'@babel/traverse': 7.20.13
- '@babel/types': 7.20.7
+ '@babel/types': 7.21.3
'@vue/babel-helper-vue-transform-on': 1.0.2
camelcase: 6.3.0
html-tags: 3.2.0
@@ -3629,7 +3637,7 @@ packages:
engines: {node: '>= 6'}
dependencies:
glob: 7.2.3
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
lazystream: 1.0.1
lodash.defaults: 4.2.0
lodash.difference: 4.5.0
@@ -3940,7 +3948,7 @@ packages:
glob: 8.1.0
infer-owner: 1.0.4
lru-cache: 7.18.3
- minipass: 3.3.4
+ minipass: 3.3.6
minipass-collect: 1.0.2
minipass-flush: 1.0.5
minipass-pipeline: 1.2.4
@@ -3963,7 +3971,7 @@ packages:
fs-minipass: 3.0.1
glob: 8.1.0
lru-cache: 7.18.3
- minipass: 4.2.0
+ minipass: 4.2.5
minipass-collect: 1.0.2
minipass-flush: 1.0.5
minipass-pipeline: 1.2.4
@@ -4098,7 +4106,7 @@ packages:
resolution: {integrity: sha512-p1dJO1Z995odIxdypzAykHIaUu+XnEvwYPSTyKJsbpL82o99sxN1G24tbecoMxTsV4PI+ZId82GJXRL2hhOeJA==}
hasBin: true
dependencies:
- c12: 1.1.2
+ c12: 1.2.0
consola: 2.15.3
convert-gitmoji: 0.1.3
execa: 6.1.0
@@ -4808,7 +4816,7 @@ packages:
resolution: {integrity: sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==}
engines: {node: '>=6.9.0'}
dependencies:
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
memory-fs: 0.5.0
tapable: 1.1.3
@@ -4816,7 +4824,7 @@ packages:
resolution: {integrity: sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==}
engines: {node: '>=10.13.0'}
dependencies:
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
tapable: 2.2.1
dev: true
@@ -5492,7 +5500,7 @@ packages:
resolution: {integrity: sha512-MhaJDcFRTuLidHrIttu0RDGyyXs/IYHVmlcxfLAEFIWjc1vdLAkdwT7Ace2u7DbitWC0toKMl5eJZRYNVreIMw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
- minipass: 4.2.0
+ minipass: 4.2.5
dev: true
/fs.realpath/1.0.0:
@@ -5746,10 +5754,10 @@ packages:
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+ dev: true
/graceful-fs/4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- dev: true
/grapheme-splitter/1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@@ -6595,7 +6603,7 @@ packages:
dependencies:
universalify: 2.0.0
optionalDependencies:
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
dev: true
/jsonparse/1.3.1:
@@ -6775,7 +6783,7 @@ packages:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'}
dependencies:
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
parse-json: 4.0.0
pify: 3.0.0
strip-bom: 3.0.0
@@ -6955,7 +6963,7 @@ packages:
https-proxy-agent: 5.0.1
is-lambda: 1.0.1
lru-cache: 7.18.3
- minipass: 3.3.4
+ minipass: 3.3.6
minipass-collect: 1.0.2
minipass-fetch: 2.1.2
minipass-flush: 1.0.5
@@ -6980,7 +6988,7 @@ packages:
https-proxy-agent: 5.0.1
is-lambda: 1.0.1
lru-cache: 7.18.3
- minipass: 4.2.0
+ minipass: 4.2.5
minipass-fetch: 3.0.1
minipass-flush: 1.0.5
minipass-pipeline: 1.2.4
@@ -7498,14 +7506,14 @@ packages:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
engines: {node: '>= 8'}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/minipass-fetch/2.1.2:
resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
minipass-sized: 1.0.3
minizlib: 2.1.2
optionalDependencies:
@@ -7516,7 +7524,7 @@ packages:
resolution: {integrity: sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
- minipass: 4.2.0
+ minipass: 4.2.5
minipass-sized: 1.0.3
minizlib: 2.1.2
optionalDependencies:
@@ -7527,28 +7535,28 @@ packages:
resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
engines: {node: '>= 8'}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/minipass-json-stream/1.0.1:
resolution: {integrity: sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==}
dependencies:
jsonparse: 1.3.1
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/minipass-pipeline/1.2.4:
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
engines: {node: '>=8'}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/minipass-sized/1.0.3:
resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
engines: {node: '>=8'}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/minipass/2.9.0:
@@ -7558,24 +7566,12 @@ packages:
yallist: 3.1.1
dev: true
- /minipass/3.3.4:
- resolution: {integrity: sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==}
- engines: {node: '>=8'}
- dependencies:
- yallist: 4.0.0
- dev: true
-
/minipass/3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
dependencies:
yallist: 4.0.0
- /minipass/4.2.0:
- resolution: {integrity: sha512-ExlilAIS7zJ2EWUMaVXi14H+FnZ18kr17kFkGemMqBx6jW0m8P6XfqwYVPEG53ENlgsED+alVP9ZxC3JzkK23Q==}
- engines: {node: '>=8'}
- dev: true
-
/minipass/4.2.5:
resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==}
engines: {node: '>=8'}
@@ -7828,12 +7824,12 @@ packages:
hookable: 5.4.2
http-proxy: 1.18.1_debug@4.3.4
is-primitive: 3.0.1
- jiti: 1.18.2
+ jiti: 1.17.1
klona: 2.0.6
knitwork: 1.0.0
listhen: 1.0.3
mime: 3.0.0
- mlly: 1.2.0
+ mlly: 1.1.1
mri: 1.2.0
node-fetch-native: 1.0.2
ofetch: 1.0.1
@@ -7854,15 +7850,8 @@ packages:
ufo: 1.1.1
unenv: 1.0.3
unimport: 2.2.4_rollup@3.18.0
- unstorage: 1.4.1
+ unstorage: 1.4.0
transitivePeerDependencies:
- - '@azure/app-configuration'
- - '@azure/cosmos'
- - '@azure/data-tables'
- - '@azure/identity'
- - '@azure/keyvault-secrets'
- - '@azure/storage-blob'
- - '@planetscale/database'
- debug
- encoding
- supports-color
@@ -7935,7 +7924,7 @@ packages:
dependencies:
env-paths: 2.2.1
glob: 7.2.3
- graceful-fs: 4.2.10
+ graceful-fs: 4.2.11
make-fetch-happen: 10.2.1
nopt: 6.0.0
npmlog: 6.0.2
@@ -8056,7 +8045,7 @@ packages:
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
make-fetch-happen: 11.0.3
- minipass: 4.2.0
+ minipass: 4.2.5
minipass-fetch: 3.0.1
minipass-json-stream: 1.0.1
minizlib: 2.1.2
@@ -8139,7 +8128,7 @@ packages:
/nuxt-component-meta/0.4.3:
resolution: {integrity: sha512-40wsnbCh2neNdKVrwSiqV/ea7QshYjp3kpfk8JZaxSW/XcgNg2tzka4L+M8caOvQalyAKi6AaENPLaTYOZDbQg==}
dependencies:
- '@nuxt/kit': 3.2.3
+ '@nuxt/kit': 3.3.1
scule: 1.0.0
typescript: 4.9.5
vue-component-meta: 1.0.24_typescript@4.9.5
@@ -8151,7 +8140,7 @@ packages:
/nuxt-config-schema/0.4.5:
resolution: {integrity: sha512-Y5anu5puDfMJfDP7IYjXsn6Dvj262HtjZqa73jCBbFRCc5jnjrs+BEpJJmtPG32ZsqzO2+RL4oTNb3H6IfKZLQ==}
dependencies:
- '@nuxt/kit': 3.2.3
+ '@nuxt/kit': 3.3.1
changelogen: 0.4.1
defu: 6.1.2
jiti: 1.17.1
@@ -8166,7 +8155,7 @@ packages:
resolution: {integrity: sha512-066pfova2nE9GZDvHE9oMkqJCwsnJvOZKU2FOSzRpjfXupH3UPtP9Zw8+C2wvZ82Nel6fuRoIUNQh5Sx8ZeQrQ==}
dependencies:
'@iconify/vue': 4.1.0
- '@nuxt/kit': 3.2.3
+ '@nuxt/kit': 3.3.1
nuxt-config-schema: 0.4.5
transitivePeerDependencies:
- rollup
@@ -8271,10 +8260,10 @@ packages:
h3: 1.1.0
hash-sum: 2.0.0
hookable: 5.4.2
- jiti: 1.18.2
+ jiti: 1.16.2
knitwork: 1.0.0
magic-string: 0.27.0
- mlly: 1.2.0
+ mlly: 1.1.0
nitropack: 2.1.1_debug@4.3.4
nuxi: 3.1.2
ofetch: 1.0.0
@@ -8296,13 +8285,6 @@ packages:
vue-devtools-stub: 0.1.0
vue-router: 4.1.6_vue@3.2.47
transitivePeerDependencies:
- - '@azure/app-configuration'
- - '@azure/cosmos'
- - '@azure/data-tables'
- - '@azure/identity'
- - '@azure/keyvault-secrets'
- - '@azure/storage-blob'
- - '@planetscale/database'
- '@types/node'
- debug
- encoding
@@ -8513,7 +8495,7 @@ packages:
'@npmcli/run-script': 6.0.0
cacache: 17.0.4
fs-minipass: 3.0.1
- minipass: 4.2.0
+ minipass: 4.2.5
npm-package-arg: 10.1.0
npm-packlist: 7.0.4
npm-pick-manifest: 8.0.1
@@ -10002,14 +9984,14 @@ packages:
resolution: {integrity: sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
- minipass: 4.2.0
+ minipass: 4.2.5
dev: true
/ssri/9.0.1:
resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
dependencies:
- minipass: 3.3.4
+ minipass: 3.3.6
dev: true
/stable/0.1.8:
@@ -10178,7 +10160,7 @@ packages:
commander: 10.0.0
consola: 2.15.3
glob: 8.1.0
- jiti: 1.17.1
+ jiti: 1.18.2
json5: 2.2.3
jsonc-parser: 3.2.0
lodash.template: 4.5.0
@@ -10558,7 +10540,7 @@ packages:
acorn: 8.8.2
estree-walker: 3.0.3
magic-string: 0.27.0
- unplugin: 1.1.0
+ unplugin: 1.3.1
dev: true
/undici/5.12.0:
@@ -10860,6 +10842,7 @@ packages:
ufo: 1.1.1
transitivePeerDependencies:
- supports-color
+ dev: false
/untyped/1.2.2:
resolution: {integrity: sha512-EANYd5L6AdpgfldlgMcmvOOnj092nWhy0ybhc7uhEH12ipytDYz89EOegBQKj8qWL3u1wgYnmFjADhsuCJs5Aw==}
@@ -11128,7 +11111,7 @@ packages:
'@antfu/utils': 0.7.2
'@rollup/pluginutils': 5.0.2
debug: 4.3.4
- fs-extra: 11.1.0
+ fs-extra: 11.1.1
kolorist: 1.7.0
sirv: 2.0.2
ufo: 1.1.1
diff --git a/specs/fixtures/basic_layer/app.vue b/specs/fixtures/basic_layer/app.vue
new file mode 100644
index 000000000..2b1be0907
--- /dev/null
+++ b/specs/fixtures/basic_layer/app.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/specs/fixtures/basic_layer/lang/en-GB.json b/specs/fixtures/basic_layer/lang/en-GB.json
new file mode 100644
index 000000000..579679365
--- /dev/null
+++ b/specs/fixtures/basic_layer/lang/en-GB.json
@@ -0,0 +1,11 @@
+{
+ "settings": {
+ "nest": {
+ "foo": {
+ "bar": {
+ "profile": "Profile"
+ }
+ }
+ }
+ }
+}
diff --git a/specs/fixtures/basic_layer/lang/en.json b/specs/fixtures/basic_layer/lang/en.json
new file mode 100644
index 000000000..815a4ddee
--- /dev/null
+++ b/specs/fixtures/basic_layer/lang/en.json
@@ -0,0 +1,6 @@
+{
+ "home": "Homepage",
+ "about": "About us",
+ "posts": "Posts",
+ "dynamic": "Dynamic"
+}
diff --git a/specs/fixtures/basic_layer/lang/fr.json b/specs/fixtures/basic_layer/lang/fr.json
new file mode 100644
index 000000000..41e7a1195
--- /dev/null
+++ b/specs/fixtures/basic_layer/lang/fr.json
@@ -0,0 +1,6 @@
+{
+ "home": "Accueil",
+ "about": "À propos",
+ "posts": "Articles",
+ "dynamic": "Dynamique"
+}
\ No newline at end of file
diff --git a/specs/fixtures/basic_layer/nuxt.config.ts b/specs/fixtures/basic_layer/nuxt.config.ts
new file mode 100644
index 000000000..a936b7fef
--- /dev/null
+++ b/specs/fixtures/basic_layer/nuxt.config.ts
@@ -0,0 +1,62 @@
+import pathe from 'pathe'
+import { resolveFiles } from '@nuxt/kit'
+// https://nuxt.com/docs/guide/directory-structure/nuxt.config
+export default defineNuxtConfig({
+ // extends: ['./layer'],
+ modules: [
+ '@nuxtjs/i18n'
+ // async (_, nuxt) => {
+ // const layers = nuxt.options._layers
+ // // @ts-ignore
+ // console.log(
+ // (
+ // await Promise.all(
+ // layers
+ // .filter(x => x.config?.i18n?.langDir != null)
+ // .map(layer =>
+ // resolveFiles(
+ // pathe.resolve(layer.config.rootDir, layer.config!.i18n!.langDir),
+ // '**/*{json,json5,yaml,yml}'
+ // )
+ // )
+ // )
+ // ).flat()
+ // )
+ // }
+ ],
+ debug: false,
+ i18n: {
+ debug: false,
+ lazy: true,
+ langDir: 'lang',
+ defaultLocale: 'en',
+ locales: [
+ {
+ code: 'en',
+ iso: 'en-US',
+ file: 'en.json',
+ name: 'English'
+ },
+ {
+ code: 'en-GB',
+ iso: 'en-GB',
+ file: 'en-GB.json',
+ name: 'English'
+ }
+ ]
+ // locale: 'en',
+ // messages: {
+ // fr: {
+ // welcome: 'Bienvenue',
+ // home: 'Accueil',
+ // profile: 'Profil'
+ // },
+ // en: {
+ // welcome: 'Welcome',
+ // home: 'Homepage',
+ // profile: 'Profile'
+ // }
+ // }
+ // }
+ }
+})
diff --git a/specs/fixtures/basic_layer/package.json b/specs/fixtures/basic_layer/package.json
new file mode 100644
index 000000000..5e9195c61
--- /dev/null
+++ b/specs/fixtures/basic_layer/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "nuxt3-test-basic-usage-layers",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "nuxi dev",
+ "build": "nuxt build",
+ "start": "node .output/server/index.mjs"
+ },
+ "devDependencies": {
+ "@nuxtjs/i18n": "latest",
+ "nuxt": "latest"
+ }
+}
\ No newline at end of file
diff --git a/specs/fixtures/basic_layer/pages/category/[slug].vue b/specs/fixtures/basic_layer/pages/category/[slug].vue
new file mode 100644
index 000000000..2f5611f7c
--- /dev/null
+++ b/specs/fixtures/basic_layer/pages/category/[slug].vue
@@ -0,0 +1,3 @@
+
+ This is cateory page on '{{ $route.params.slug }}'
+
diff --git a/specs/fixtures/basic_layer/pages/index.vue b/specs/fixtures/basic_layer/pages/index.vue
new file mode 100644
index 000000000..0bbe882f0
--- /dev/null
+++ b/specs/fixtures/basic_layer/pages/index.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+ localePath
+
+ -
+ {{ $t('home') }}
+
+ -
+ {{ $t('home') }}
+
+ -
+ Homepage in English
+
+ -
+ Route by path to: {{ $t('profile') }}
+
+ -
+ Route by name to: {{ $t('profile') }}
+
+ -
+
+ {{ category.title }}
+
+
+
+
+
+ switchLocalePath
+
+ -
+ English
+
+ -
+ Français
+
+
+
+
+ localeRoute
+
+
+
+
diff --git a/specs/fixtures/basic_layer/pages/user/profile.vue b/specs/fixtures/basic_layer/pages/user/profile.vue
new file mode 100644
index 000000000..6c50b8ae3
--- /dev/null
+++ b/specs/fixtures/basic_layer/pages/user/profile.vue
@@ -0,0 +1,3 @@
+
+ This is profile page
+
diff --git a/specs/fixtures/layers/layer-lazy/locales/en.json b/specs/fixtures/layers/layer-lazy/locales/en.json
new file mode 100644
index 000000000..6e5e57ffd
--- /dev/null
+++ b/specs/fixtures/layers/layer-lazy/locales/en.json
@@ -0,0 +1,3 @@
+{
+ "hello": "Hello world!"
+}
\ No newline at end of file
diff --git a/specs/fixtures/layers/layer-lazy/locales/fr.json b/specs/fixtures/layers/layer-lazy/locales/fr.json
new file mode 100644
index 000000000..81d54098a
--- /dev/null
+++ b/specs/fixtures/layers/layer-lazy/locales/fr.json
@@ -0,0 +1,3 @@
+{
+ "hello": "Bonjour le monde!"
+}
\ No newline at end of file
diff --git a/specs/fixtures/layers/layer-lazy/locales/nl.json b/specs/fixtures/layers/layer-lazy/locales/nl.json
new file mode 100644
index 000000000..058abf12e
--- /dev/null
+++ b/specs/fixtures/layers/layer-lazy/locales/nl.json
@@ -0,0 +1,3 @@
+{
+ "hello": "Hallo wereld!"
+}
\ No newline at end of file
diff --git a/specs/fixtures/layers/layer-lazy/nuxt.config.ts b/specs/fixtures/layers/layer-lazy/nuxt.config.ts
new file mode 100644
index 000000000..244878db3
--- /dev/null
+++ b/specs/fixtures/layers/layer-lazy/nuxt.config.ts
@@ -0,0 +1,26 @@
+// https://nuxt.com/docs/guide/directory-structure/nuxt.config
+export default defineNuxtConfig({
+ i18n: {
+ langDir: 'locales',
+ locales: [
+ {
+ code: 'en',
+ iso: 'en-US',
+ file: 'en.json',
+ name: 'English'
+ },
+ {
+ code: 'fr',
+ iso: 'fr-FR',
+ file: 'fr.json',
+ name: 'Français'
+ },
+ {
+ code: 'nl',
+ iso: 'nl-NL',
+ file: 'nl.json',
+ name: 'Nederlands'
+ }
+ ]
+ }
+})
diff --git a/specs/fixtures/layers/layer-lazy/pages/layer-page.vue b/specs/fixtures/layers/layer-lazy/pages/layer-page.vue
new file mode 100644
index 000000000..3a83f01d6
--- /dev/null
+++ b/specs/fixtures/layers/layer-lazy/pages/layer-page.vue
@@ -0,0 +1,17 @@
+
+
+
+ {{ $t('hello') }}
+
+
+ {{ switchLocalePath('nl') }}
+ {{ switchLocalePath('fr') }}
+ {{ switchLocalePath('en') }}
+
+
+
+
+
diff --git a/specs/fixtures/layers/layer-pages/nuxt.config.ts b/specs/fixtures/layers/layer-pages/nuxt.config.ts
new file mode 100644
index 000000000..7ad7077f5
--- /dev/null
+++ b/specs/fixtures/layers/layer-pages/nuxt.config.ts
@@ -0,0 +1,35 @@
+// https://nuxt.com/docs/guide/directory-structure/nuxt.config
+export default defineNuxtConfig({
+ i18n: {
+ locales: [
+ {
+ code: 'en',
+ iso: 'en-US',
+ name: 'English'
+ },
+ {
+ code: 'fr',
+ iso: 'fr-FR',
+ name: 'Français'
+ },
+ {
+ code: 'nl',
+ iso: 'nl-NL',
+ name: 'Nederlands'
+ }
+ ],
+ vueI18n: {
+ messages: {
+ en: {
+ hello: 'Hello world!'
+ },
+ fr: {
+ hello: 'Bonjour le monde!'
+ },
+ nl: {
+ hello: 'Hallo wereld!'
+ }
+ }
+ }
+ }
+})
diff --git a/specs/fixtures/layers/layer-pages/pages/layer-page.vue b/specs/fixtures/layers/layer-pages/pages/layer-page.vue
new file mode 100644
index 000000000..4ad3fe158
--- /dev/null
+++ b/specs/fixtures/layers/layer-pages/pages/layer-page.vue
@@ -0,0 +1,7 @@
+
+
+ {{ $t('hello') }}
+
+
+
+
diff --git a/specs/fixtures/layers/layer-simple/nuxt.config.ts b/specs/fixtures/layers/layer-simple/nuxt.config.ts
new file mode 100644
index 000000000..15b79ce93
--- /dev/null
+++ b/specs/fixtures/layers/layer-simple/nuxt.config.ts
@@ -0,0 +1,6 @@
+// https://nuxt.com/docs/guide/directory-structure/nuxt.config
+export default defineNuxtConfig({
+ i18n: {
+ locales: ['fr', 'nl']
+ }
+})
diff --git a/specs/layers/layers_lazy.spec.ts b/specs/layers/layers_lazy.spec.ts
new file mode 100644
index 000000000..4e6ddd06d
--- /dev/null
+++ b/specs/layers/layers_lazy.spec.ts
@@ -0,0 +1,26 @@
+import { describe, test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+import { getText } from '../helper'
+
+await setup({
+ rootDir: fileURLToPath(new URL(`../fixtures/basic_layer`, import.meta.url)),
+ browser: true,
+ // overrides
+ nuxtConfig: {
+ extends: [fileURLToPath(new URL(`../fixtures/layers/layer-lazy`, import.meta.url))]
+ }
+})
+describe('nuxt layers-lazy', async () => {
+ test('layer provides locale `nl` and translation for key `hello`', async () => {
+ const home = url('/layer-page')
+ const page = await createPage()
+ await page.goto(home)
+
+ expect(await getText(page, '#i18n-layer-target')).toEqual('Hello world!')
+
+ const homeNL = url('/nl/layer-page')
+ await page.goto(homeNL)
+ expect(await getText(page, '#i18n-layer-target')).toEqual('Hallo wereld!')
+ })
+})
diff --git a/specs/layers/layers_page.spec.ts b/specs/layers/layers_page.spec.ts
new file mode 100644
index 000000000..5a9c6bcd8
--- /dev/null
+++ b/specs/layers/layers_page.spec.ts
@@ -0,0 +1,23 @@
+import { describe, test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+import { getText } from '../helper'
+
+describe('nuxt layers-pages', async () => {
+ await setup({
+ rootDir: fileURLToPath(new URL(`../fixtures/basic`, import.meta.url)),
+ browser: true,
+ // overrides
+ nuxtConfig: {
+ extends: [fileURLToPath(new URL(`../fixtures/layers/layer-pages`, import.meta.url))]
+ }
+ })
+
+ test('layer provides locale `nl`', async () => {
+ const home = url('/nl/layer-page')
+ const page = await createPage(undefined, { locale: 'ja' }) // set browser locale
+ await page.goto(home)
+
+ expect(await getText(page, '#i18n-layer-target')).toEqual('Hallo wereld!')
+ })
+})
diff --git a/specs/layers/layers_simple.spec.ts b/specs/layers/layers_simple.spec.ts
new file mode 100644
index 000000000..3eae471aa
--- /dev/null
+++ b/specs/layers/layers_simple.spec.ts
@@ -0,0 +1,23 @@
+import { describe, test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+import { getText } from '../helper'
+
+describe('nuxt layers-simple', async () => {
+ await setup({
+ rootDir: fileURLToPath(new URL(`../fixtures/fallback`, import.meta.url)),
+ browser: true,
+ // overrides
+ nuxtConfig: {
+ extends: [fileURLToPath(new URL(`../fixtures/layers/layer-simple`, import.meta.url))]
+ }
+ })
+
+ test('layer provides locale `nl`', async () => {
+ const home = url('/')
+ const page = await createPage(undefined, { locale: 'ja' }) // set browser locale
+ await page.goto(home)
+
+ expect(await getText(page, '#set-locale-link-nl')).toEqual('nl')
+ })
+})
diff --git a/src/bundler.ts b/src/bundler.ts
index f8e8d627f..60939845b 100644
--- a/src/bundler.ts
+++ b/src/bundler.ts
@@ -6,6 +6,7 @@ import VueI18nVitePlugin from '@intlify/unplugin-vue-i18n/vite'
import { TransformMacroPlugin, TransformMacroPluginOptions } from './transform/macros'
import { ResourceProxyPlugin, ResourceProxyPluginOptions } from './transform/proxy'
import { ResourceDynamicPlugin, ResourceDynamicPluginOptions } from './transform/dynamic'
+import { getLayerLangPaths } from './layers'
import type { Nuxt } from '@nuxt/schema'
import type { NuxtI18nOptions } from './types'
@@ -21,7 +22,9 @@ export async function extendBundler(
langPath: string | null
}
) {
- const { nuxtOptions, hasLocaleFiles, langPath } = options
+ const { nuxtOptions, hasLocaleFiles } = options
+ const langPaths = getLayerLangPaths(nuxt)
+ debug('langPaths -', langPaths)
/**
* setup nitro
@@ -63,8 +66,8 @@ export async function extendBundler(
strictMessage: nuxtOptions.precompile.strictMessage,
escapeHtml: nuxtOptions.precompile.escapeHtml
}
- if (hasLocaleFiles && langPath) {
- webpackPluginOptions.include = [resolve(langPath, './**')]
+ if (hasLocaleFiles && langPaths.length > 0) {
+ webpackPluginOptions.include = langPaths.map(x => resolve(x, './**'))
}
addWebpackPlugin(ResourceProxyPlugin.webpack(proxyOptions))
@@ -97,8 +100,8 @@ export async function extendBundler(
strictMessage: nuxtOptions.precompile.strictMessage,
escapeHtml: nuxtOptions.precompile.escapeHtml
}
- if (hasLocaleFiles && langPath) {
- vitePluginOptions.include = [resolve(langPath, './**')]
+ if (hasLocaleFiles && langPaths.length > 0) {
+ vitePluginOptions.include = langPaths.map(x => resolve(x, './**'))
}
addVitePlugin(ResourceProxyPlugin.vite(proxyOptions))
diff --git a/src/layers.ts b/src/layers.ts
new file mode 100644
index 000000000..25bd6bc56
--- /dev/null
+++ b/src/layers.ts
@@ -0,0 +1,154 @@
+import type { Nuxt } from '@nuxt/schema'
+import type { LocaleObject } from 'vue-i18n-routing'
+import type { NuxtI18nOptions } from './types'
+import createDebug from 'debug'
+import pathe from 'pathe'
+
+const debug = createDebug('@nuxtjs/i18n:layers')
+
+const getLocaleFiles = (locale: LocaleObject): string[] => {
+ if (locale.file != null) return [locale.file]
+ if (locale.files != null) return locale.files
+ return []
+}
+
+const localeFilesToRelative = (projectLangDir: string, layerLangDir: string, files: string[]) => {
+ const absoluteFiles = files.map(file => pathe.resolve(layerLangDir, file))
+ const relativeFiles = absoluteFiles.map(file => pathe.relative(projectLangDir, file))
+ return relativeFiles
+}
+
+const getProjectPath = (nuxt: Nuxt, ...target: string[]) => {
+ const projectLayer = nuxt.options._layers[0]
+ return pathe.resolve(projectLayer.config.rootDir, ...target)
+}
+
+export const applyLayerOptions = (options: NuxtI18nOptions, nuxt: Nuxt) => {
+ const project = nuxt.options._layers[0]
+ const layers = nuxt.options._layers
+
+ // No layers to merge
+ if (layers.length === 1) return
+
+ const resolvedLayerPaths = layers.map(l => pathe.resolve(project.config.rootDir, l.config.rootDir))
+ debug('using layers at paths -', resolvedLayerPaths)
+
+ const mergedLocales = mergeLayerLocales(nuxt)
+ debug('merged locales - ', mergedLocales)
+
+ options.locales = mergedLocales
+}
+
+export const mergeLayerPages = (analyzer: (pathOverride: string) => void, nuxt: Nuxt) => {
+ const project = nuxt.options._layers[0]
+ const layers = nuxt.options._layers
+
+ // No layers to merge
+ if (layers.length === 1) return
+
+ for (const l of layers) {
+ const lPath = pathe.resolve(project.config.rootDir, l.config.rootDir, l.config.dir?.pages ?? 'pages')
+ analyzer(lPath)
+ }
+}
+
+export const mergeLayerLocales = (nuxt: Nuxt) => {
+ const projectLayer = nuxt.options._layers[0]
+ const projectI18n = projectLayer.config.i18n
+
+ if (projectI18n == null) {
+ debug('project layer `i18n` configuration is required')
+ return []
+ }
+
+ /**
+ * Merge locales when `lazy: false`
+ */
+ const mergeSimpleLocales = () => {
+ if (projectI18n.locales == null) return []
+
+ const firstI18nLayer = nuxt.options._layers.find(x => x.config.i18n?.locales && x.config.i18n?.locales?.length > 0)
+ if (firstI18nLayer == null) return []
+
+ const localeType = typeof firstI18nLayer.config.i18n?.locales?.at(0)
+ const isStringLocales = (val: unknown): val is string[] => localeType === 'string'
+
+ const mergedLocales: string[] | LocaleObject[] = []
+ for (const layer of nuxt.options._layers) {
+ if (layer.config.i18n?.locales == null) continue
+
+ for (const locale of layer.config.i18n.locales) {
+ if (isStringLocales(mergedLocales)) {
+ if (typeof locale !== 'string') continue
+ if (mergedLocales.includes(locale)) continue
+
+ mergedLocales.push(locale)
+ continue
+ }
+
+ if (typeof locale === 'string') continue
+ const localeEntry = mergedLocales.find(x => x.code === locale.code)
+
+ if (localeEntry == null) {
+ mergedLocales.push(locale)
+ } else {
+ Object.assign(localeEntry, locale, localeEntry)
+ }
+ }
+ }
+
+ return mergedLocales
+ }
+
+ const mergeLazyLocales = () => {
+ if (projectI18n.langDir == null) {
+ debug('project layer `i18n.langDir` is required')
+ return []
+ }
+
+ const projectLangDir = getProjectPath(nuxt, projectI18n.langDir)
+ debug('project path', getProjectPath(nuxt))
+
+ const mergedLocales: LocaleObject[] = []
+ for (const layer of nuxt.options._layers) {
+ if (layer.config.i18n?.locales == null) continue
+ if (layer.config.i18n?.langDir == null) continue
+
+ const layerLangDir = pathe.resolve(layer.config.rootDir, layer.config.i18n.langDir)
+ debug('layer langDir -', layerLangDir)
+
+ for (const locale of layer.config.i18n.locales) {
+ if (typeof locale === 'string') continue
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { file, files, ...entry } = locale
+ const localeEntry = mergedLocales.find(x => x.code === locale.code)
+
+ const fileEntries = getLocaleFiles(locale)
+ const relativeFiles = localeFilesToRelative(projectLangDir, layerLangDir, fileEntries)
+
+ if (localeEntry == null) {
+ mergedLocales.push({ ...entry, files: relativeFiles })
+ } else {
+ localeEntry.files = [...relativeFiles, ...(localeEntry?.files ?? [])]
+ }
+ }
+ }
+
+ return mergedLocales
+ }
+
+ return projectI18n.lazy ? mergeLazyLocales() : mergeSimpleLocales()
+}
+
+/**
+ * Returns an array of absolute paths to each layers `langDir`
+ */
+export const getLayerLangPaths = (nuxt: Nuxt) => {
+ return (
+ nuxt.options._layers
+ .filter(layer => layer.config.i18n?.langDir != null)
+ // @ts-ignore
+ .map(layer => pathe.resolve(layer.config.rootDir, layer.config.i18n.langDir)) as string[]
+ )
+}
diff --git a/src/module.ts b/src/module.ts
index 97e215fe1..ca75c21a1 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -31,6 +31,7 @@ import {
} from './constants'
import { formatMessage, getNormalizedLocales, resolveLocales, getPackageManagerType } from './utils'
import { distDir, runtimeDir, pkgModulesDir } from './dirs'
+import { applyLayerOptions } from './layers'
import type { NuxtI18nOptions } from './types'
import type { DefineLocaleMessage, LocaleMessages } from 'vue-i18n'
@@ -79,6 +80,8 @@ export default defineNuxtModule({
throw new Error(formatMessage(`Cannot support nuxt version: ${getNuxtVersion(nuxt)}`))
}
+ applyLayerOptions(options, nuxt)
+
if (options.strategy === 'no_prefix' && options.differentDomains) {
console.warn(
formatMessage(
diff --git a/src/pages.ts b/src/pages.ts
index c627988b5..6ba7677ab 100644
--- a/src/pages.ts
+++ b/src/pages.ts
@@ -7,6 +7,7 @@ import { parse as parseSFC, compileScript } from '@vue/compiler-sfc'
import { walk } from 'estree-walker'
import MagicString from 'magic-string'
import { formatMessage, getRoutePath, parseSegment } from './utils'
+import { mergeLayerPages } from './layers'
import { resolve, parse as parsePath } from 'pathe'
import { NUXT_I18N_COMPOSABLE_DEFINE_ROUTE } from './constants'
@@ -61,7 +62,11 @@ export function setupPages(
pagesDir,
pages: new Map()
}
+
analyzeNuxtPages(ctx, pages)
+ const analyzer = (pageDirOverride: string) => analyzeNuxtPages(ctx, pages, pageDirOverride)
+ mergeLayerPages(analyzer, nuxt)
+
const localizedPages = localizeRoutes(pages, {
...options,
includeUprefixedFallback,
@@ -78,8 +83,8 @@ export function setupPages(
* Construct the map of full paths from nuxtpage to support custom routes.
* `NuxtPage` of the nested route doesn't have a slash (`/`) and isn’t the full path.
*/
-export function analyzeNuxtPages(ctx: NuxtPageAnalizeContext, pages: NuxtPage[]): void {
- const pagesPath = resolve(ctx.srcDir, ctx.pagesDir)
+export function analyzeNuxtPages(ctx: NuxtPageAnalizeContext, pages: NuxtPage[], pageDirOverride?: string): void {
+ const pagesPath = resolve(ctx.srcDir, pageDirOverride ?? ctx.pagesDir)
for (const page of pages) {
const splited = page.file.split(pagesPath)
if (splited.length === 2 && splited[1]) {