From a7fc4de25edc7e8e8d3247acb1b6aab5f952d486 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sat, 26 Nov 2022 17:34:17 +0900 Subject: [PATCH] feat!: custom routes optimized for Nuxt 3 file based routing (#1673) * feat!: custom routes optimized for Nuxt 3 file based routing * finish implementation * fix: update custom routes & ignoring for page components * replace to customRoutes option --- docs/content/2.guide/14.migrating.md | 75 ++++ docs/content/2.guide/4.custom-paths.md | 170 +++---- .../2.guide/5.ignoring-localized-routes.md | 14 +- docs/content/3.options/2.routing.md | 15 +- package.json | 14 +- playground/nuxt.config.ts | 4 +- pnpm-lock.yaml | 418 ++++++++++++++---- specs/custom_route_paths/component.spec.ts | 2 +- .../module_configration.spec.ts | 6 +- .../disable/component.spec.ts | 3 +- .../disable/module_configration.spec.ts | 6 +- .../pick/component.spec.ts | 3 +- .../pick/module_configration.spec.ts | 6 +- src/constants.ts | 2 +- src/pages.ts | 166 +++++-- src/types.ts | 4 + src/utils.ts | 141 +++++- test/__snapshots__/gen.test.ts.snap | 6 +- .../dynamic/pages/articles/[name].vue | 6 +- test/pages/__snapshots__/basic.test.ts.snap | 60 --- .../__snapshots__/custom_route.test.ts.snap | 266 +++++------ test/pages/analyze_nuxt_page.test.ts | 132 ++++++ test/pages/basic.test.ts | 40 -- test/pages/custom_route.test.ts | 212 +++++---- .../__snapshots__/disable.test.ts.snap | 57 +-- .../__snapshots__/pick.test.ts.snap | 110 ++--- test/pages/ignore_route/disable.test.ts | 68 +-- test/pages/ignore_route/pick.test.ts | 78 ++-- test/pages/utils.ts | 10 +- test/utils.test.ts | 19 + 30 files changed, 1386 insertions(+), 727 deletions(-) delete mode 100644 test/pages/__snapshots__/basic.test.ts.snap create mode 100644 test/pages/analyze_nuxt_page.test.ts delete mode 100644 test/pages/basic.test.ts create mode 100644 test/utils.test.ts diff --git a/docs/content/2.guide/14.migrating.md b/docs/content/2.guide/14.migrating.md index 9336d7e13..62ea8d1e2 100644 --- a/docs/content/2.guide/14.migrating.md +++ b/docs/content/2.guide/14.migrating.md @@ -7,6 +7,62 @@ Follow this guide to upgrade from one major version to the other. ## Upgrading from `nuxtjs/i18n` v7.x +### Change the route key rules in `pages` option + +The key of route set in the `pages` option has been changed to be file-based relative to the `pages/` directory in Nuxt, and **excluding the leading `/`**. + +The reason is that it is more intuitive to match Nuxt file-based routing. + + +Nuxt2: + +```asciidoc +pages/ +├── about.vue +├── users/ +├──── _id/ +├────── profile.vue +├── index.vue +``` + +```js {}[nuxt.config.js] +i18n: { + parsePages: false, + pages: { + about: { + fr: '/a-propos', + }, + 'users/_id/profile': { + fr: '/u/:id/profil', + } + } +} +``` + +Nuxt3: + +```asciidoc +pages/ +├── about.vue +├── users/ +├──── [id]/ +├────── profile.vue +├── index.vue +``` + +```ts {}[nuxt.config.ts] +i18n: { + customRoutes: 'config', + pages: { + about: { + fr: '/a-propos', + }, + 'users/[id]/profile': { + fr: '/u/[id]/profil', + } + } +} +``` ### Deprecated `localeLocation()` @@ -50,6 +106,25 @@ defineI18nRoute({ ``` +### Deprecated `parsePages` option + +Use `customRoutes` option. because the option name `parsePages` is not intuitive. + +```diff {}[nuxt.config.ts] + export default defineNuxtConfig({ + modules: [ + '@nuxtjs/i18n' + ], + + i18n: { + // ... +- parsePages: false, ++ customRoutes: 'config', + // ... + } + }) +``` + ### Deprecated `vuex` option Use `dynamicRouteParams` option. because, vuex no longer requires in Nuxt3. diff --git a/docs/content/2.guide/4.custom-paths.md b/docs/content/2.guide/4.custom-paths.md index a2b2ef259..a3f32c10e 100644 --- a/docs/content/2.guide/4.custom-paths.md +++ b/docs/content/2.guide/4.custom-paths.md @@ -3,7 +3,7 @@ title: Custom route paths description: 'Customize the names of the paths for specific locale.' --- -In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are 2 ways of configuring custom paths for your pages: [Page component](#page-component) or via the [Nuxt configuration](#nuxt-configuration). +In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are 2 ways of configuring custom paths for your [Module configuration](#nodule-configuration) or your pages [Page component](#page-component). ::alert{type="warning"} @@ -11,51 +11,22 @@ Custom paths are not supported when using the `no-prefix` [strategy](/guide/rout :: -### Page component - -You can use the `defineI18nRoute` compiler macro to set some custom paths for each page component. +### Module configuration -```html {}[pages/about.vue] - -``` +Make sure you set the `customRoutes` option to `config` and add your custom paths in the `pages` option: -To configure a custom path for a dynamic route, you need to put the params in the URI similarly to how you would do it in vue-router. - -```html {}[pages/articles/[name].vue] - -``` - -::alert{type="info"} +::alert{type="warning"} -`defineI18nRoute` compiler macro is tree-shaked out at build time and is not included in the dist files. +`parsePages` option will be deprecated in the v8 official release. :: -### Nuxt configuration - -Make sure you set the `parsePages` option to `false` to disable babel parsing and add your custom paths in the `pages` option: - -```js {}[nuxt.config.js] +```ts {}[nuxt.config.ts] export default defineNuxtConfig({ // ... i18n: { - parsePages: false, // Disable babel parsing + customRoutes: 'config', // disable custom route with page components pages: { about: { en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale) @@ -69,79 +40,42 @@ export default defineNuxtConfig({ }) ``` -Note that each key within the `pages` object should correspond to the relative file path of the route within your `pages/` directory excluding the leading `/`. - -Customized route paths must start with a `/` and not include the locale prefix. - -#### Example 1 - -Say you have some nested pages like: - -```asciidoc -pages/ -├── [nested]/ -├──── [route]/ -├────── index.vue -├────── [...slug].vue -``` - -Here's how you would configure these particular pages in the configuration: - -```js {}[nuxt.config.js] -export default defineNuxtConfig({ - // ... +Note that each key within the `pages` object should **correspond to the relative file-based path (excluding `.vue` file extension) of the route within your `pages/` directory excluding the leading `/`**. - i18n: { - parsePages: false, - pages: { - ':nested/:route': { - en: '/mycustompath/:nested/:route' // Params need to be put back here as you would with vue-router - }, - ':nested/:route/:slug(.*)*': { - en: '/mycustompath/:nested/*' // * will match the entire route path after /:nested/ - } - } - }, - - // ... -}) -``` +Customized route paths **must start with a `/`** and **not include the locale prefix**. -#### Example 2 +#### Example 1: Localize the part of URL -With the following `pages` directory: +You have some routes with the following `pages` directory: ```asciidoc pages/ ├── about.vue ├── services/ +├──── coaching.vue ├──── index.vue ├──── development/ -├────── index.vue -├────── app/ -├──────── index.vue -├────── website/ -├──────── index.vue -├──── coaching/ +├────── app.vue +├────── website.vue ├────── index.vue ``` You would need to set up your `pages` property as follows: -```js {}[nuxt.config.js] +```ts {}[nuxt.config.ts] export default defineNuxtConfig({ // ... i18n: { - parsePages: false, + customRoutes: 'config', pages: { about: { fr: '/a-propos', }, - services: { + 'services/index': { fr: '/offres', }, - 'services/development': { + 'services/development/index': { fr: '/offres/developement', }, 'services/development/app': { @@ -161,3 +95,71 @@ export default defineNuxtConfig({ ``` If a custom path is missing for one of the locales, the `defaultLocale` custom path is used, if set. + +#### Example 2: Dynamic Routes + +Say you have some dynamic routes like: + +```asciidoc +pages/ +├── blog/ +├──── [date]/ +├────── [slug].vue +``` + +Here's how you would configure these particular pages in the configuration: + +```ts {}[nuxt.config.ts] +export default defineNuxtConfig({ + // ... + + i18n: { + customRoutes: 'config', + pages: { + 'blog/[date]/[slug]': { + // params need to be put back here as you would with Nuxt Dynamic Routes + // https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes + ja: '/blog/tech/[date]/[slug]' + // ... + }, + } + }, + + // ... +}) +``` + +### Page component + +You can use the `defineI18nRoute` compiler macro to set some custom paths for each page component. + +```html {}[pages/about.vue] + +``` + +To configure a custom path for a dynamic route, you need to use it in double square brackets in the paths similarly to how you would do it in [Nuxt Dynamic Routes](https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes): + +```html {}[pages/articles/[name].vue] + +``` + +::alert{type="info"} + +`defineI18nRoute` compiler macro is tree-shaked out at build time and is not included in the dist files. + +:: diff --git a/docs/content/2.guide/5.ignoring-localized-routes.md b/docs/content/2.guide/5.ignoring-localized-routes.md index 5ad2d2b3d..c630de1da 100644 --- a/docs/content/2.guide/5.ignoring-localized-routes.md +++ b/docs/content/2.guide/5.ignoring-localized-routes.md @@ -9,12 +9,12 @@ This feature is not supported with the `no-prefix` [strategy](/guide/routing-str :: -If you'd like some pages to be available in some languages only, you can configure the list of supported languages to override the global settings. The options can be specified within either the page components themselves or globaly, within then module options. +If you'd like some pages to be available in some languages only, you can configure the list of supported languages to override the global settings. The options can be specified within either the page components themselves or globaly, within then module configration. ### Pick localized routes ::code-group - ::code-block{label="Page component" active} + ::code-block{label="Page components" active} ```js {}[pages/about.vue] ``` :: - ::code-block{label="Nuxt configuration"} + ::code-block{label="Module configuration"} ```js {}[nuxt.config.js] i18n: { - parsePages: false, + customRoutes: false, pages: { about: { en: false, @@ -40,17 +40,17 @@ If you'd like some pages to be available in some languages only, you can configu ### Disable localized routes ::code-group - ::code-block{label="Page component" active} + ::code-block{label="Page components" active} ```js {}[pages/about.vue] ``` :: - ::code-block{label="Nuxt configuration"} + ::code-block{label="Module configuration"} ```js {}[nuxt.config.js] i18n: { - parsePages: false, + customRoutes: 'config', pages: { about: false } diff --git a/docs/content/3.options/2.routing.md b/docs/content/3.options/2.routing.md index dbccb73c7..087dd9ac4 100644 --- a/docs/content/3.options/2.routing.md +++ b/docs/content/3.options/2.routing.md @@ -91,12 +91,25 @@ Routes generation strategy. Can be set to one of the following: Whether [custom paths](/guide/custom-paths) are extracted from page files +::alert{type="warning"} + +`parsePages` option will be deprecated in the v8 official release. + +:: + +## `customRoutes` + +- type: `string` (`page` or `config`) | `undefined` +- default: `page` + +Whether [custom paths](/guide/custom-paths) are extracted from page files + ## `pages` - type: `object` - default: `{}` -If `parsePages` option is disabled, the module will look for custom routes in the `pages` option. Refer to the [Routing](/guide/routing-strategies) for usage. +If `customRoutes` option is disabled with `config`, the module will look for custom routes in the `pages` option. Refer to the [Routing](/guide/routing-strategies) for usage. ## `onBeforeLanguageSwitch` diff --git a/package.json b/package.json index 9957a1a1a..6704a27e7 100644 --- a/package.json +++ b/package.json @@ -97,8 +97,8 @@ "@types/debug": "^4.1.7", "@types/js-cookie": "^3.0.1", "@types/rimraf": "^3", - "@typescript-eslint/eslint-plugin": "^5.43.0", - "@typescript-eslint/parser": "^5.43.0", + "@typescript-eslint/eslint-plugin": "^5.44.0", + "@typescript-eslint/parser": "^5.44.0", "bumpp": "^8.2.1", "changelogithub": "^0.12.4", "debug": "^4.3.3", @@ -107,16 +107,16 @@ "eslint-plugin-prettier": "^4.0.0", "gh-changelogen": "^0.2.6", "jiti": "^1.14.0", - "jsdom": "^20.0.2", + "jsdom": "^20.0.3", "lint-staged": "^12.1.2", "npm-run-all": "^4.1.5", "nuxt": "^3.0.0", - "playwright": "^1.27.1", - "prettier": "^2.7.0", + "playwright": "^1.28.1", + "prettier": "^2.8.0", "rimraf": "^3.0.2", "ts-essentials": "^9.1.2", - "typescript": "^4.8.4", - "vitest": "^0.25.2", + "typescript": "^4.9.3", + "vitest": "^0.25.3", "vue": "^3.2.44", "yorkie": "^2.0.0" }, diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 6c8dd2502..70f45e720 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -54,15 +54,15 @@ export default defineNuxtConfig({ // strategy: 'prefix', strategy: 'prefix_and_default', // rootRedirect: '/ja/about-ja', - // parsePages: false, dynamicRouteParams: true, + // customRoutes: 'config', pages: { about: { ja: '/about-ja' } }, // differentDomains: true, - // skipSettingLocaleOnNavigate: true, + skipSettingLocaleOnNavigate: true, detectBrowserLanguage: false, /* detectBrowserLanguage: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def47b406..b10536514 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: '@types/debug': ^4.1.7 '@types/js-cookie': ^3.0.1 '@types/rimraf': ^3 - '@typescript-eslint/eslint-plugin': ^5.43.0 - '@typescript-eslint/parser': ^5.43.0 + '@typescript-eslint/eslint-plugin': ^5.44.0 + '@typescript-eslint/parser': ^5.44.0 '@vue/compiler-sfc': ^3.2.44 bumpp: ^8.2.1 changelogithub: ^0.12.4 @@ -36,7 +36,7 @@ importers: is-https: ^4.0.0 jiti: ^1.14.0 js-cookie: ^3.0.1 - jsdom: ^20.0.2 + jsdom: ^20.0.3 knitwork: ^1.0.0 lint-staged: ^12.1.2 magic-string: ^0.26.7 @@ -45,14 +45,14 @@ importers: nuxt: ^3.0.0 pathe: ^1.0.0 pkg-types: ^1.0.1 - playwright: ^1.27.1 - prettier: ^2.7.0 + playwright: ^1.28.1 + prettier: ^2.8.0 rimraf: ^3.0.2 ts-essentials: ^9.1.2 - typescript: ^4.8.4 + typescript: ^4.9.3 ufo: ^1.0.0 unplugin: ^1.0.0 - vitest: ^0.25.2 + vitest: ^0.25.3 vue: ^3.2.44 vue-i18n: ^9.3.0-beta.10 vue-i18n-routing: ^0.10.1 @@ -87,25 +87,25 @@ importers: '@types/debug': 4.1.7 '@types/js-cookie': 3.0.2 '@types/rimraf': 3.0.2 - '@typescript-eslint/eslint-plugin': 5.43.0_nqj4bdx4ekws7aecttskpih4py - '@typescript-eslint/parser': 5.43.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu + '@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a bumpp: 8.2.1 changelogithub: 0.12.6 eslint: 8.28.0 eslint-config-prettier: 8.5.0_eslint@8.28.0 - eslint-plugin-prettier: 4.2.1_pgxuib4rd7wiymfktharf5ydt4 + eslint-plugin-prettier: 4.2.1_cwlo2dingkvfydnaculr42urve gh-changelogen: 0.2.8 jiti: 1.16.0 - jsdom: 20.0.2 + jsdom: 20.0.3 lint-staged: 12.5.0 npm-run-all: 4.1.5 nuxt: 3.0.0_vpzpx7scyqasdo2b4titzifguu - playwright: 1.28.0 - prettier: 2.7.1 + playwright: 1.28.1 + prettier: 2.8.0 rimraf: 3.0.2 ts-essentials: 9.3.0_typescript@4.9.3 typescript: 4.9.3 - vitest: 0.25.2_jsdom@20.0.2 + vitest: 0.25.3_jsdom@20.0.3 vue: 3.2.45 yorkie: 2.0.0 @@ -477,6 +477,15 @@ packages: dev: true optional: true + /@esbuild/android-arm/0.15.15: + resolution: {integrity: sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64/0.15.14: resolution: {integrity: sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==} engines: {node: '>=12'} @@ -486,6 +495,15 @@ packages: dev: true optional: true + /@esbuild/linux-loong64/0.15.15: + resolution: {integrity: sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@eslint/eslintrc/1.3.3: resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1035,7 +1053,7 @@ packages: slash: 4.0.0 dev: true - /@rollup/plugin-alias/4.0.2_rollup@3.3.0: + /@rollup/plugin-alias/4.0.2_rollup@3.4.0: resolution: {integrity: sha512-1hv7dBOZZwo3SEupxn4UA2N0EDThqSSS+wI1St1TNTBtOZvUchyIClyHcnDcjjrReTPZ47Faedrhblv4n+T5UQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1044,7 +1062,7 @@ packages: rollup: optional: true dependencies: - rollup: 3.3.0 + rollup: 3.4.0 slash: 4.0.0 dev: true @@ -1066,7 +1084,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-commonjs/23.0.2_rollup@3.3.0: + /@rollup/plugin-commonjs/23.0.2_rollup@3.4.0: resolution: {integrity: sha512-e9ThuiRf93YlVxc4qNIurvv+Hp9dnD+4PjOqQs5vAYfcZ3+AXSrcdzXnVjWxcGQOa6KGJFcRZyUI3ktWLavFjg==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1075,13 +1093,13 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.3.0 + '@rollup/pluginutils': 5.0.2_rollup@3.4.0 commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.0.3 is-reference: 1.2.1 magic-string: 0.26.7 - rollup: 3.3.0 + rollup: 3.4.0 dev: true /@rollup/plugin-inject/5.0.2_rollup@2.79.1: @@ -1112,7 +1130,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-json/5.0.1_rollup@3.3.0: + /@rollup/plugin-json/5.0.1_rollup@3.4.0: resolution: {integrity: sha512-QCwhZZLvM8nRcTHyR1vOgyTMiAnjiNj1ebD/BMRvbO1oc/z14lZH6PfxXeegee2B6mky/u9fia4fxRM4TqrUaw==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1121,8 +1139,8 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.3.0 - rollup: 3.3.0 + '@rollup/pluginutils': 5.0.2_rollup@3.4.0 + rollup: 3.4.0 dev: true /@rollup/plugin-node-resolve/15.0.1_rollup@2.79.1: @@ -1143,7 +1161,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-node-resolve/15.0.1_rollup@3.3.0: + /@rollup/plugin-node-resolve/15.0.1_rollup@3.4.0: resolution: {integrity: sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1152,13 +1170,13 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.3.0 + '@rollup/pluginutils': 5.0.2_rollup@3.4.0 '@types/resolve': 1.20.2 deepmerge: 4.2.2 is-builtin-module: 3.2.0 is-module: 1.0.0 resolve: 1.22.1 - rollup: 3.3.0 + rollup: 3.4.0 dev: true /@rollup/plugin-replace/5.0.1_rollup@2.79.1: @@ -1175,7 +1193,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-replace/5.0.1_rollup@3.3.0: + /@rollup/plugin-replace/5.0.1_rollup@3.4.0: resolution: {integrity: sha512-Z3MfsJ4CK17BfGrZgvrcp/l6WXoKb0kokULO+zt/7bmcyayokDaQ2K3eDJcRLCTAlp5FPI4/gz9MHAsosz4Rag==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1184,9 +1202,9 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.2_rollup@3.3.0 + '@rollup/pluginutils': 5.0.2_rollup@3.4.0 magic-string: 0.26.7 - rollup: 3.3.0 + rollup: 3.4.0 dev: true /@rollup/plugin-wasm/6.0.1_rollup@2.79.1: @@ -1237,7 +1255,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/pluginutils/5.0.2_rollup@3.3.0: + /@rollup/pluginutils/5.0.2_rollup@3.4.0: resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1249,7 +1267,7 @@ packages: '@types/estree': 1.0.0 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 3.3.0 + rollup: 3.4.0 dev: true /@tootallnate/once/2.0.0: @@ -1323,8 +1341,8 @@ packages: resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} dev: true - /@typescript-eslint/eslint-plugin/5.43.0_nqj4bdx4ekws7aecttskpih4py: - resolution: {integrity: sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==} + /@typescript-eslint/eslint-plugin/5.44.0_fnsv2sbzcckq65bwfk7a5xwslu: + resolution: {integrity: sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1334,10 +1352,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.43.0_hsf322ms6xhhd4b5ne6lb74y4a - '@typescript-eslint/scope-manager': 5.43.0 - '@typescript-eslint/type-utils': 5.43.0_hsf322ms6xhhd4b5ne6lb74y4a - '@typescript-eslint/utils': 5.43.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/scope-manager': 5.44.0 + '@typescript-eslint/type-utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a debug: 4.3.4 eslint: 8.28.0 ignore: 5.2.0 @@ -1350,8 +1368,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.43.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==} + /@typescript-eslint/parser/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a: + resolution: {integrity: sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1360,9 +1378,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.43.0 - '@typescript-eslint/types': 5.43.0 - '@typescript-eslint/typescript-estree': 5.43.0_typescript@4.9.3 + '@typescript-eslint/scope-manager': 5.44.0 + '@typescript-eslint/types': 5.44.0 + '@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3 debug: 4.3.4 eslint: 8.28.0 typescript: 4.9.3 @@ -1370,16 +1388,16 @@ packages: - supports-color dev: true - /@typescript-eslint/scope-manager/5.43.0: - resolution: {integrity: sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==} + /@typescript-eslint/scope-manager/5.44.0: + resolution: {integrity: sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.43.0 - '@typescript-eslint/visitor-keys': 5.43.0 + '@typescript-eslint/types': 5.44.0 + '@typescript-eslint/visitor-keys': 5.44.0 dev: true - /@typescript-eslint/type-utils/5.43.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==} + /@typescript-eslint/type-utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a: + resolution: {integrity: sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -1388,8 +1406,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.43.0_typescript@4.9.3 - '@typescript-eslint/utils': 5.43.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3 + '@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a debug: 4.3.4 eslint: 8.28.0 tsutils: 3.21.0_typescript@4.9.3 @@ -1398,13 +1416,13 @@ packages: - supports-color dev: true - /@typescript-eslint/types/5.43.0: - resolution: {integrity: sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==} + /@typescript-eslint/types/5.44.0: + resolution: {integrity: sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.43.0_typescript@4.9.3: - resolution: {integrity: sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==} + /@typescript-eslint/typescript-estree/5.44.0_typescript@4.9.3: + resolution: {integrity: sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1412,8 +1430,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.43.0 - '@typescript-eslint/visitor-keys': 5.43.0 + '@typescript-eslint/types': 5.44.0 + '@typescript-eslint/visitor-keys': 5.44.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1424,17 +1442,17 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.43.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==} + /@typescript-eslint/utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a: + resolution: {integrity: sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.43.0 - '@typescript-eslint/types': 5.43.0 - '@typescript-eslint/typescript-estree': 5.43.0_typescript@4.9.3 + '@typescript-eslint/scope-manager': 5.44.0 + '@typescript-eslint/types': 5.44.0 + '@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3 eslint: 8.28.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.28.0 @@ -1444,11 +1462,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys/5.43.0: - resolution: {integrity: sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==} + /@typescript-eslint/visitor-keys/5.44.0: + resolution: {integrity: sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.43.0 + '@typescript-eslint/types': 5.44.0 eslint-visitor-keys: 3.3.0 dev: true @@ -2773,6 +2791,15 @@ packages: dev: true optional: true + /esbuild-android-64/0.15.15: + resolution: {integrity: sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + /esbuild-android-arm64/0.15.14: resolution: {integrity: sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==} engines: {node: '>=12'} @@ -2782,6 +2809,15 @@ packages: dev: true optional: true + /esbuild-android-arm64/0.15.15: + resolution: {integrity: sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + /esbuild-darwin-64/0.15.14: resolution: {integrity: sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==} engines: {node: '>=12'} @@ -2791,6 +2827,15 @@ packages: dev: true optional: true + /esbuild-darwin-64/0.15.15: + resolution: {integrity: sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /esbuild-darwin-arm64/0.15.14: resolution: {integrity: sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==} engines: {node: '>=12'} @@ -2800,6 +2845,15 @@ packages: dev: true optional: true + /esbuild-darwin-arm64/0.15.15: + resolution: {integrity: sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + /esbuild-freebsd-64/0.15.14: resolution: {integrity: sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==} engines: {node: '>=12'} @@ -2809,6 +2863,15 @@ packages: dev: true optional: true + /esbuild-freebsd-64/0.15.15: + resolution: {integrity: sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /esbuild-freebsd-arm64/0.15.14: resolution: {integrity: sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==} engines: {node: '>=12'} @@ -2818,6 +2881,15 @@ packages: dev: true optional: true + /esbuild-freebsd-arm64/0.15.15: + resolution: {integrity: sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-32/0.15.14: resolution: {integrity: sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==} engines: {node: '>=12'} @@ -2827,6 +2899,15 @@ packages: dev: true optional: true + /esbuild-linux-32/0.15.15: + resolution: {integrity: sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-64/0.15.14: resolution: {integrity: sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==} engines: {node: '>=12'} @@ -2836,6 +2917,15 @@ packages: dev: true optional: true + /esbuild-linux-64/0.15.15: + resolution: {integrity: sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-arm/0.15.14: resolution: {integrity: sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==} engines: {node: '>=12'} @@ -2845,6 +2935,15 @@ packages: dev: true optional: true + /esbuild-linux-arm/0.15.15: + resolution: {integrity: sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-arm64/0.15.14: resolution: {integrity: sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==} engines: {node: '>=12'} @@ -2854,6 +2953,15 @@ packages: dev: true optional: true + /esbuild-linux-arm64/0.15.15: + resolution: {integrity: sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-mips64le/0.15.14: resolution: {integrity: sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==} engines: {node: '>=12'} @@ -2863,6 +2971,15 @@ packages: dev: true optional: true + /esbuild-linux-mips64le/0.15.15: + resolution: {integrity: sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-ppc64le/0.15.14: resolution: {integrity: sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==} engines: {node: '>=12'} @@ -2872,6 +2989,15 @@ packages: dev: true optional: true + /esbuild-linux-ppc64le/0.15.15: + resolution: {integrity: sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-riscv64/0.15.14: resolution: {integrity: sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==} engines: {node: '>=12'} @@ -2881,6 +3007,15 @@ packages: dev: true optional: true + /esbuild-linux-riscv64/0.15.15: + resolution: {integrity: sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-linux-s390x/0.15.14: resolution: {integrity: sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==} engines: {node: '>=12'} @@ -2890,6 +3025,15 @@ packages: dev: true optional: true + /esbuild-linux-s390x/0.15.15: + resolution: {integrity: sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + /esbuild-netbsd-64/0.15.14: resolution: {integrity: sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==} engines: {node: '>=12'} @@ -2899,6 +3043,15 @@ packages: dev: true optional: true + /esbuild-netbsd-64/0.15.15: + resolution: {integrity: sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /esbuild-openbsd-64/0.15.14: resolution: {integrity: sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==} engines: {node: '>=12'} @@ -2908,6 +3061,15 @@ packages: dev: true optional: true + /esbuild-openbsd-64/0.15.15: + resolution: {integrity: sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + /esbuild-sunos-64/0.15.14: resolution: {integrity: sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==} engines: {node: '>=12'} @@ -2917,6 +3079,15 @@ packages: dev: true optional: true + /esbuild-sunos-64/0.15.15: + resolution: {integrity: sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + /esbuild-windows-32/0.15.14: resolution: {integrity: sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==} engines: {node: '>=12'} @@ -2926,6 +3097,15 @@ packages: dev: true optional: true + /esbuild-windows-32/0.15.15: + resolution: {integrity: sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + /esbuild-windows-64/0.15.14: resolution: {integrity: sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==} engines: {node: '>=12'} @@ -2935,6 +3115,15 @@ packages: dev: true optional: true + /esbuild-windows-64/0.15.15: + resolution: {integrity: sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /esbuild-windows-arm64/0.15.14: resolution: {integrity: sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==} engines: {node: '>=12'} @@ -2944,6 +3133,15 @@ packages: dev: true optional: true + /esbuild-windows-arm64/0.15.15: + resolution: {integrity: sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /esbuild/0.15.14: resolution: {integrity: sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==} engines: {node: '>=12'} @@ -2974,6 +3172,36 @@ packages: esbuild-windows-arm64: 0.15.14 dev: true + /esbuild/0.15.15: + resolution: {integrity: sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.15 + '@esbuild/linux-loong64': 0.15.15 + esbuild-android-64: 0.15.15 + esbuild-android-arm64: 0.15.15 + esbuild-darwin-64: 0.15.15 + esbuild-darwin-arm64: 0.15.15 + esbuild-freebsd-64: 0.15.15 + esbuild-freebsd-arm64: 0.15.15 + esbuild-linux-32: 0.15.15 + esbuild-linux-64: 0.15.15 + esbuild-linux-arm: 0.15.15 + esbuild-linux-arm64: 0.15.15 + esbuild-linux-mips64le: 0.15.15 + esbuild-linux-ppc64le: 0.15.15 + esbuild-linux-riscv64: 0.15.15 + esbuild-linux-s390x: 0.15.15 + esbuild-netbsd-64: 0.15.15 + esbuild-openbsd-64: 0.15.15 + esbuild-sunos-64: 0.15.15 + esbuild-windows-32: 0.15.15 + esbuild-windows-64: 0.15.15 + esbuild-windows-arm64: 0.15.15 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -3017,7 +3245,7 @@ packages: eslint: 8.28.0 dev: true - /eslint-plugin-prettier/4.2.1_pgxuib4rd7wiymfktharf5ydt4: + /eslint-plugin-prettier/4.2.1_cwlo2dingkvfydnaculr42urve: resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -3030,7 +3258,7 @@ packages: dependencies: eslint: 8.28.0 eslint-config-prettier: 8.5.0_eslint@8.28.0 - prettier: 2.7.1 + prettier: 2.8.0 prettier-linter-helpers: 1.0.0 dev: true @@ -4129,8 +4357,8 @@ packages: dependencies: argparse: 2.0.1 - /jsdom/20.0.2: - resolution: {integrity: sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==} + /jsdom/20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} engines: {node: '>=14'} peerDependencies: canvas: ^2.5.0 @@ -4157,7 +4385,7 @@ packages: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.2 - w3c-xmlserializer: 3.0.0 + w3c-xmlserializer: 4.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 @@ -4610,7 +4838,7 @@ packages: optional: true dependencies: defu: 6.1.1 - esbuild: 0.15.14 + esbuild: 0.15.15 fs-extra: 10.1.0 globby: 13.1.2 jiti: 1.16.0 @@ -5404,19 +5632,19 @@ packages: mlly: 1.0.0 pathe: 1.0.0 - /playwright-core/1.28.0: - resolution: {integrity: sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==} + /playwright-core/1.28.1: + resolution: {integrity: sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==} engines: {node: '>=14'} hasBin: true dev: true - /playwright/1.28.0: - resolution: {integrity: sha512-kyOXGc5y1mgi+hgEcCIyE1P1+JumLrxS09nFHo5sdJNzrucxPRAGwM4A2X3u3SDOfdgJqx61yIoR6Av+5plJPg==} + /playwright/1.28.1: + resolution: {integrity: sha512-92Sz6XBlfHlb9tK5UCDzIFAuIkHHpemA9zwUaqvo+w7sFMSmVMGmvKcbptof/eJObq63PGnMhM75x7qxhTR78Q==} engines: {node: '>=14'} hasBin: true requiresBuild: true dependencies: - playwright-core: 1.28.0 + playwright-core: 1.28.1 dev: true /postcss-calc/8.2.4_postcss@8.4.19: @@ -5768,8 +5996,8 @@ packages: fast-diff: 1.2.0 dev: true - /prettier/2.7.1: - resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + /prettier/2.8.0: + resolution: {integrity: sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -5979,7 +6207,7 @@ packages: glob: 7.2.3 dev: true - /rollup-plugin-dts/5.0.0_6annma2bj33shm6er7hwi5u4z4: + /rollup-plugin-dts/5.0.0_iyyukaj3pdiwftuon3ena43c4i: resolution: {integrity: sha512-OO8ayCvuJCKaQSShyVTARxGurVVk4ulzbuvz+0zFd1f93vlnWFU5pBMT7HFeS6uj7MvvZLx4kUAarGATSU1+Ng==} engines: {node: '>=v14'} peerDependencies: @@ -5987,7 +6215,7 @@ packages: typescript: ^4.1 dependencies: magic-string: 0.26.7 - rollup: 3.3.0 + rollup: 3.4.0 typescript: 4.9.3 optionalDependencies: '@babel/code-frame': 7.18.6 @@ -6035,8 +6263,8 @@ packages: fsevents: 2.3.2 dev: true - /rollup/3.3.0: - resolution: {integrity: sha512-wqOV/vUJCYEbWsXvwCkgGWvgaEnsbn4jxBQWKpN816CqsmCimDmCNJI83c6if7QVD4v/zlyRzxN7U2yDT5rfoA==} + /rollup/3.4.0: + resolution: {integrity: sha512-4g8ZrEFK7UbDvy3JF+d5bLiC8UKkS3n/27/cnVeESwB1LVPl6MoPL32/6+SCQ1vHTp6Mvp2veIHtwELhi+uXEw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -6695,16 +6923,16 @@ packages: resolution: {integrity: sha512-i2mkbLNFZDJJdpsbg4JflHldKeF3J0K+mLGUdh8jrHBSTHZBw8qFWI7t/AUrGjHxa/O/vkIod65LXu9ktPiUHw==} hasBin: true dependencies: - '@rollup/plugin-alias': 4.0.2_rollup@3.3.0 - '@rollup/plugin-commonjs': 23.0.2_rollup@3.3.0 - '@rollup/plugin-json': 5.0.1_rollup@3.3.0 - '@rollup/plugin-node-resolve': 15.0.1_rollup@3.3.0 - '@rollup/plugin-replace': 5.0.1_rollup@3.3.0 - '@rollup/pluginutils': 5.0.2_rollup@3.3.0 + '@rollup/plugin-alias': 4.0.2_rollup@3.4.0 + '@rollup/plugin-commonjs': 23.0.2_rollup@3.4.0 + '@rollup/plugin-json': 5.0.1_rollup@3.4.0 + '@rollup/plugin-node-resolve': 15.0.1_rollup@3.4.0 + '@rollup/plugin-replace': 5.0.1_rollup@3.4.0 + '@rollup/pluginutils': 5.0.2_rollup@3.4.0 chalk: 5.1.2 consola: 2.15.3 defu: 6.1.1 - esbuild: 0.15.14 + esbuild: 0.15.15 globby: 13.1.2 hookable: 5.4.2 jiti: 1.16.0 @@ -6717,8 +6945,8 @@ packages: pkg-types: 1.0.1 pretty-bytes: 6.0.0 rimraf: 3.0.2 - rollup: 3.3.0 - rollup-plugin-dts: 5.0.0_6annma2bj33shm6er7hwi5u4z4 + rollup: 3.4.0 + rollup-plugin-dts: 5.0.0_iyyukaj3pdiwftuon3ena43c4i scule: 1.0.0 typescript: 4.9.3 untyped: 1.0.0 @@ -7039,8 +7267,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest/0.25.2_jsdom@20.0.2: - resolution: {integrity: sha512-qqkzfzglEFbQY7IGkgSJkdOhoqHjwAao/OrphnHboeYHC5JzsVFoLCaB2lnAy8krhj7sbrFTVRApzpkTOeuDWQ==} + /vitest/0.25.3_jsdom@20.0.3: + resolution: {integrity: sha512-/UzHfXIKsELZhL7OaM2xFlRF8HRZgAHtPctacvNK8H4vOcbJJAMEgbWNGSAK7Y9b1NBe5SeM7VTuz2RsTHFJJA==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -7068,7 +7296,7 @@ packages: acorn-walk: 8.2.0 chai: 4.3.7 debug: 4.3.4 - jsdom: 20.0.2 + jsdom: 20.0.3 local-pkg: 0.4.2 source-map: 0.6.1 strip-literal: 0.4.2 @@ -7211,9 +7439,9 @@ packages: '@vue/server-renderer': 3.2.45_vue@3.2.45 '@vue/shared': 3.2.45 - /w3c-xmlserializer/3.0.0: - resolution: {integrity: sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==} - engines: {node: '>=12'} + /w3c-xmlserializer/4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} dependencies: xml-name-validator: 4.0.0 dev: true diff --git a/specs/custom_route_paths/component.spec.ts b/specs/custom_route_paths/component.spec.ts index 091eeeeab..3b762337a 100644 --- a/specs/custom_route_paths/component.spec.ts +++ b/specs/custom_route_paths/component.spec.ts @@ -9,7 +9,7 @@ await setup({ nuxtConfig: { i18n: { defaultLocale: 'en', - parsePages: true + customRoutes: 'page' } } }) diff --git a/specs/custom_route_paths/module_configration.spec.ts b/specs/custom_route_paths/module_configration.spec.ts index cb659762f..1f69b2533 100644 --- a/specs/custom_route_paths/module_configration.spec.ts +++ b/specs/custom_route_paths/module_configration.spec.ts @@ -10,12 +10,12 @@ await setup({ nuxtConfig: { i18n: { defaultLocale: 'en', - parsePages: false, + customRoutes: 'config', pages: { - about: { + 'about/index': { fr: '/about-fr' }, - blog: { + 'blog/index': { en: '/news' }, 'blog/article': { diff --git a/specs/ignoring_localized_route/disable/component.spec.ts b/specs/ignoring_localized_route/disable/component.spec.ts index 2de4acc0e..d43b72097 100644 --- a/specs/ignoring_localized_route/disable/component.spec.ts +++ b/specs/ignoring_localized_route/disable/component.spec.ts @@ -10,7 +10,8 @@ await setup({ // overrides nuxtConfig: { i18n: { - defaultLocale: 'en' + defaultLocale: 'en', + customRoutes: 'page' } } }) diff --git a/specs/ignoring_localized_route/disable/module_configration.spec.ts b/specs/ignoring_localized_route/disable/module_configration.spec.ts index 1a6570815..538fbf97a 100644 --- a/specs/ignoring_localized_route/disable/module_configration.spec.ts +++ b/specs/ignoring_localized_route/disable/module_configration.spec.ts @@ -11,10 +11,10 @@ await setup({ nuxtConfig: { i18n: { defaultLocale: 'en', - parsePages: false, + customRoutes: 'config', pages: { - about: false, - blog: { + 'about/index': false, + 'blog/index': { en: '/news' }, 'blog/article': { diff --git a/specs/ignoring_localized_route/pick/component.spec.ts b/specs/ignoring_localized_route/pick/component.spec.ts index a130dfc5d..7f1a7cb75 100644 --- a/specs/ignoring_localized_route/pick/component.spec.ts +++ b/specs/ignoring_localized_route/pick/component.spec.ts @@ -8,7 +8,8 @@ await setup({ // overrides nuxtConfig: { i18n: { - defaultLocale: 'en' + defaultLocale: 'en', + customRoutes: 'page' } } }) diff --git a/specs/ignoring_localized_route/pick/module_configration.spec.ts b/specs/ignoring_localized_route/pick/module_configration.spec.ts index 2beb16739..fe736ea55 100644 --- a/specs/ignoring_localized_route/pick/module_configration.spec.ts +++ b/specs/ignoring_localized_route/pick/module_configration.spec.ts @@ -11,12 +11,12 @@ await setup({ nuxtConfig: { i18n: { defaultLocale: 'en', - parsePages: false, + customRoutes: 'config', pages: { - about: { + 'about/index': { fr: false }, - blog: { + 'blog/index': { en: '/news' }, 'blog/article': { diff --git a/src/constants.ts b/src/constants.ts index 2a9478379..560a21747 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -55,7 +55,7 @@ export const DEFAULT_OPTIONS = { differentDomains: false, baseUrl: '', dynamicRouteParams: false, - parsePages: true, + customRoutes: 'page', pages: {}, skipSettingLocaleOnNavigate: false, onBeforeLanguageSwitch: () => '', diff --git a/src/pages.ts b/src/pages.ts index c0b490476..88d6160a9 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -1,12 +1,13 @@ import createDebug from 'debug' import { extendPages } from '@nuxt/kit' import { I18nRoute, localizeRoutes, DefaultLocalizeRoutesPrefixable } from 'vue-i18n-routing' -import { isString } from '@intlify/shared' +import { isString, isBoolean } from '@intlify/shared' import fs from 'node:fs' import { parse as parseSFC, compileScript } from '@vue/compiler-sfc' import { walk } from 'estree-walker' import MagicString from 'magic-string' -import { formatMessage } from './utils' +import { formatMessage, getRoutePath, parseSegment } from './utils' +import { resolve, parse as parsePath } from 'pathe' import type { Nuxt, NuxtPage } from '@nuxt/schema' import type { RouteOptionsResolver, ComputedRouteOptions, LocalizeRoutesPrefixableOptions } from 'vue-i18n-routing' @@ -15,6 +16,18 @@ import type { Node, ObjectExpression, ArrayExpression } from '@babel/types' const debug = createDebug('@nuxtjs/i18n:pages') +export type AnalizedNuxtPageMeta = { + inRoot: boolean + path: string +} + +export type NuxtPageAnalizeContext = { + stack: string[] + srcDir: string + pagesDir: string + pages: Map +} + export function setupPages( options: Required, nuxt: Nuxt, @@ -35,16 +48,24 @@ export function setupPages( }) const pagesDir = nuxt.options.dir && nuxt.options.dir.pages ? nuxt.options.dir.pages : 'pages' + const srcDir = nuxt.options.srcDir const { trailingSlash } = additionalOptions - debug(`pagesDir: ${pagesDir}, tailingSlash: ${trailingSlash}`) + debug(`pagesDir: ${pagesDir}, srcDir: ${srcDir}, tailingSlash: ${trailingSlash}`) extendPages(pages => { debug('pages making ...', pages) + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + analyzeNuxtPages(ctx, pages) const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback, localizeRoutesPrefixable, - optionsResolver: getRouteOptionsResolver(pagesDir, options) + optionsResolver: getRouteOptionsResolver(ctx, options) }) pages.splice(0, pages.length) pages.unshift(...(localizedPages as NuxtPage[])) @@ -52,23 +73,77 @@ 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) + for (const page of pages) { + const splited = page.file.split(pagesPath) + if (splited.length === 2 && splited[1]) { + const { dir, name } = parsePath(splited[1]) + let path = '' + if (ctx.stack.length > 0) { + path += `${dir.slice(1, dir.length)}/${name}` + } else { + if (dir !== '/') { + path += `${dir.slice(1, dir.length)}/` + } + path += name + } + const p: AnalizedNuxtPageMeta = { + inRoot: ctx.stack.length === 0, + path + } + ctx.pages.set(page, p) + + if (page.children && page.children.length > 0) { + ctx.stack.push(page.path) + analyzeNuxtPages(ctx, page.children) + ctx.stack.pop() + } + } + } +} + export function getRouteOptionsResolver( - pagesDir: string, - options: Pick, 'pages' | 'defaultLocale' | 'parsePages'> + ctx: NuxtPageAnalizeContext, + options: Pick, 'pages' | 'defaultLocale' | 'parsePages' | 'customRoutes'> ): RouteOptionsResolver { - const { pages, defaultLocale, parsePages } = options - debug('parsePages on getRouteOptionsResolver', parsePages) + const { pages, defaultLocale, parsePages, customRoutes } = options + + let useConfig = false + if (isBoolean(parsePages)) { + console.warn( + formatMessage( + `'parsePages' option is deprecated. Please use 'customRoutes' option instead. We will remove it in v8 official release.` + ) + ) + useConfig = !parsePages + } else { + useConfig = customRoutes === 'config' + } + debug('getRouteOptionsResolver useConfig', useConfig) + return (route, localeCodes): ComputedRouteOptions | null => { - const ret = !parsePages - ? getRouteOptionsFromPages(pagesDir, route, localeCodes, pages, defaultLocale) + const ret = useConfig + ? getRouteOptionsFromPages(ctx, route, localeCodes, pages, defaultLocale) : getRouteOptionsFromComponent(route, localeCodes) debug('getRouteOptionsResolver resolved', route.path, route.name, ret) return ret } } +function resolveRoutePath(path: string): string { + const normalizePath = path.slice(1, path.length) // remove `/` + const tokens = parseSegment(normalizePath) + const routePath = getRoutePath(tokens) + return routePath +} + function getRouteOptionsFromPages( - pagesDir: string, + ctx: NuxtPageAnalizeContext, route: I18nRoute, localeCodes: string[], pages: CustomRoutePages, @@ -78,18 +153,25 @@ function getRouteOptionsFromPages( locales: localeCodes, paths: {} } - const pattern = new RegExp(`${pagesDir}/`, 'i') - // prettier-ignore - const path = route.chunkName - ? route.chunkName.replace(pattern, '') // for webpack - : route.path // vite and webpack - ? route.path.substring(1) // extract `/` - : route.name - const pageOptions = path ? pages[path] : undefined + + // get `AnalizedNuxtPageMeta` to use Vue Router path mapping + const pageMeta = ctx.pages.get(route as unknown as NuxtPage) + + // skip if no `AnalizedNuxtPageMeta` + if (pageMeta == null) { + console.warn( + formatMessage(`Couldn't find AnalizedNuxtPageMeta by NuxtPage (${route.path}), so no custom route for it`) + ) + return options + } + + const pageOptions = pageMeta.path ? pages[pageMeta.path] : undefined + // routing disabled if (pageOptions === false) { return null } + // skip if no page options defined if (!pageOptions) { return options @@ -103,14 +185,14 @@ function getRouteOptionsFromPages( const customLocalePath = pageOptions[locale] if (isString(customLocalePath)) { // set custom path if any - options.paths[locale] = customLocalePath + options.paths[locale] = resolveRoutePath(customLocalePath) continue } const customDefaultLocalePath = pageOptions[defaultLocale] if (isString(customDefaultLocalePath)) { // set default locale's custom path if any - options.paths[locale] = customDefaultLocalePath + options.paths[locale] = resolveRoutePath(customDefaultLocalePath) } } @@ -120,21 +202,41 @@ function getRouteOptionsFromPages( function getRouteOptionsFromComponent(route: I18nRoute, localeCodes: string[]) { debug('getRouteOptionsFromComponent', route) const file = route.component || route.file + + // localize disabled if no file (vite) or component (webpack) if (!isString(file)) { return null } + const options: ComputedRouteOptions = { + locales: localeCodes, + paths: {} + } + const componentOptions = readComponent(file) + + // skip if page components not defined if (componentOptions == null) { - return { - locales: localeCodes, - paths: {} - } - } else if (componentOptions === false) { + return options + } + + // localize disabled + if (componentOptions === false) { return null - } else { - return componentOptions } + + options.locales = componentOptions.locales || localeCodes + + // construct paths object + const locales = Object.keys(componentOptions.paths || {}) + for (const locale of locales) { + const customLocalePath = componentOptions.paths[locale] + if (isString(customLocalePath)) { + options.paths[locale] = resolveRoutePath(customLocalePath) + } + } + + return options } function readComponent(target: string) { @@ -197,7 +299,7 @@ function verifyObjectValue(properties: ObjectExpression['properties']) { if (prop.value.type === 'ArrayExpression') { ret = verifyLocalesArrayExpression(prop.value.elements) } else { - console.warn(formatMessage(`'locale' value is required array expression`)) + console.warn(formatMessage(`'locale' value is required array`)) ret = false } } else if ( @@ -207,12 +309,12 @@ function verifyObjectValue(properties: ObjectExpression['properties']) { if (prop.value.type === 'ObjectExpression') { ret = verifyPathsObjectExpress(prop.value.properties) } else { - console.warn(formatMessage(`'paths' value is required object expression`)) + console.warn(formatMessage(`'paths' value is required object`)) ret = false } } } else { - console.warn(formatMessage(`'defineI18nRoute' object expression properties type is required object property`)) + console.warn(formatMessage(`'defineI18nRoute' is required object`)) ret = false } } @@ -231,7 +333,7 @@ function verifyPathsObjectExpress(properties: ObjectExpression['properties']) { ret = false } } else { - console.warn(formatMessage(`'paths' is required object property`)) + console.warn(formatMessage(`'paths' is required object`)) ret = false } } diff --git a/src/types.ts b/src/types.ts index 3643f74a1..6653026f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,10 @@ export type NuxtI18nOptions = { langDir?: string | null lazy?: boolean | LazyOptions pages?: CustomRoutePages + customRoutes?: 'page' | 'config' + /** + * @deprecated `'parsePages' option is deprecated. Please use 'customRoutes' option instead. We will remove it in v8 official release.` + */ parsePages?: boolean rootRedirect?: string | null | RootRedirectOptions routesNameSeparator?: string diff --git a/src/utils.ts b/src/utils.ts index fdf84e0cf..2f52aad54 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import { resolveFiles } from '@nuxt/kit' import { parse } from 'pathe' +import { encodePath } from 'ufo' import { isObject, isString } from '@intlify/shared' import { NUXT_I18N_MODULE_ID } from './constants' @@ -27,7 +28,7 @@ export async function resolveLocales(path: string, locales: LocaleObject[]): Pro const files = await resolveFiles(path, '**/*{json,json5,yaml,yml}') return files.map(file => { const parsed = parse(file) - const locale = findLocales(locales, parsed.base) + const locale = locales.find((locale: string | LocaleObject) => isObject(locale) && locale.file === parsed.base) return locales == null ? { path: file, @@ -38,8 +39,138 @@ export async function resolveLocales(path: string, locales: LocaleObject[]): Pro }) } -function findLocales(locales: NonNullable, filename: string) { - // @ts-ignore - const ret = locales.find((locale: string | LocaleObject) => isObject(locale) && locale.file === filename) - return ret != null ? (ret as LocaleObject) : null +/** + * sergment parser, forked from the below: + * - original repository url: https://github.com/nuxt/framework + * - code url: https://github.com/nuxt/framework/blob/main/packages/nuxt/src/pages/utils.ts + * - author: Nuxt Framework Team + * - license: MIT + */ + +enum SegmentParserState { + initial, + static, + dynamic, + optional, + catchall +} + +enum SegmentTokenType { + static, + dynamic, + optional, + catchall +} + +interface SegmentToken { + type: SegmentTokenType + value: string +} + +const PARAM_CHAR_RE = /[\w\d_.]/ + +export function parseSegment(segment: string) { + let state: SegmentParserState = SegmentParserState.initial + let i = 0 + + let buffer = '' + const tokens: SegmentToken[] = [] + + function consumeBuffer() { + if (!buffer) { + return + } + if (state === SegmentParserState.initial) { + throw new Error('wrong state') + } + + tokens.push({ + type: + state === SegmentParserState.static + ? SegmentTokenType.static + : state === SegmentParserState.dynamic + ? SegmentTokenType.dynamic + : state === SegmentParserState.optional + ? SegmentTokenType.optional + : SegmentTokenType.catchall, + value: buffer + }) + + buffer = '' + } + + while (i < segment.length) { + const c = segment[i] + + switch (state) { + case SegmentParserState.initial: + buffer = '' + if (c === '[') { + state = SegmentParserState.dynamic + } else { + i-- + state = SegmentParserState.static + } + break + + case SegmentParserState.static: + if (c === '[') { + consumeBuffer() + state = SegmentParserState.dynamic + } else { + buffer += c + } + break + + case SegmentParserState.catchall: + case SegmentParserState.dynamic: + case SegmentParserState.optional: + if (buffer === '...') { + buffer = '' + state = SegmentParserState.catchall + } + if (c === '[' && state === SegmentParserState.dynamic) { + state = SegmentParserState.optional + } + if (c === ']' && (state !== SegmentParserState.optional || buffer[buffer.length - 1] === ']')) { + if (!buffer) { + throw new Error('Empty param') + } else { + consumeBuffer() + } + state = SegmentParserState.initial + } else if (PARAM_CHAR_RE.test(c)) { + buffer += c + } else { + // eslint-disable-next-line no-console + // console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`) + } + break + } + i++ + } + + if (state === SegmentParserState.dynamic) { + throw new Error(`Unfinished param "${buffer}"`) + } + + consumeBuffer() + + return tokens +} + +export function getRoutePath(tokens: SegmentToken[]): string { + return tokens.reduce((path, token) => { + // prettier-ignore + return ( + path + + (token.type === SegmentTokenType.optional + ? `:${token.value}?` + : token.type === SegmentTokenType.dynamic + ? `:${token.value}` + : token.type === SegmentTokenType.catchall + ? `:${token.value}(.*)*` + : encodePath(token.value)) + ) + }, '/') } diff --git a/test/__snapshots__/gen.test.ts.snap b/test/__snapshots__/gen.test.ts.snap index fb5dea1d6..57c3906f0 100644 --- a/test/__snapshots__/gen.test.ts.snap +++ b/test/__snapshots__/gen.test.ts.snap @@ -22,7 +22,7 @@ export const resolveNuxtI18nOptions = async (context) => { return nuxtI18nOptions } -export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,parsePages: true,pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) +export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,customRoutes: \\"page\\",pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) export const nuxtI18nInternalOptions = Object({__normalizedLocales: [Object({\\"code\\":\\"en\\"})]}) export const NUXT_I18N_MODULE_ID = \\"@nuxtjs/i18n\\" @@ -75,7 +75,7 @@ export const resolveNuxtI18nOptions = async (context) => { return nuxtI18nOptions } -export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,parsePages: true,pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) +export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,customRoutes: \\"page\\",pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) export const nuxtI18nInternalOptions = Object({__normalizedLocales: [Object({\\"code\\":\\"en\\"})]}) export const NUXT_I18N_MODULE_ID = \\"@nuxtjs/i18n\\" @@ -102,7 +102,7 @@ export const resolveNuxtI18nOptions = async (context) => { return nuxtI18nOptions } -export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,parsePages: true,pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) +export const nuxtI18nOptionsDefault = Object({vueI18n: undefined,locales: [],defaultLocale: \\"\\",defaultDirection: \\"ltr\\",routesNameSeparator: \\"___\\",trailingSlash: false,defaultLocaleRouteNameSuffix: \\"default\\",strategy: \\"prefix_except_default\\",lazy: false,langDir: null,rootRedirect: null,detectBrowserLanguage: Object({\\"alwaysRedirect\\":false,\\"cookieCrossOrigin\\":false,\\"cookieDomain\\":null,\\"cookieKey\\":\\"i18n_redirected\\",\\"cookieSecure\\":false,\\"fallbackLocale\\":\\"\\",\\"redirectOn\\":\\"root\\",\\"useCookie\\":true}),differentDomains: false,baseUrl: \\"\\",dynamicRouteParams: false,customRoutes: \\"page\\",pages: Object({}),skipSettingLocaleOnNavigate: false,onBeforeLanguageSwitch: (() => \\"\\"),onLanguageSwitched: (() => null),types: undefined,debug: false}) export const nuxtI18nInternalOptions = Object({__normalizedLocales: [Object({\\"code\\":\\"en\\"})]}) export const NUXT_I18N_MODULE_ID = \\"@nuxtjs/i18n\\" diff --git a/test/fixtures/custom_route/dynamic/pages/articles/[name].vue b/test/fixtures/custom_route/dynamic/pages/articles/[name].vue index 0132cd18e..510c17338 100644 --- a/test/fixtures/custom_route/dynamic/pages/articles/[name].vue +++ b/test/fixtures/custom_route/dynamic/pages/articles/[name].vue @@ -3,9 +3,9 @@ import { defineI18nRoute } from '#i18n' defineI18nRoute({ paths: { - en: '/articles/:name', - fr: '/articles/:name', - ja: '/記事/:name' + en: '/articles/[name]', + fr: '/articles/[name]', + ja: '/記事/[name]' } }) diff --git a/test/pages/__snapshots__/basic.test.ts.snap b/test/pages/__snapshots__/basic.test.ts.snap deleted file mode 100644 index ca8df95b9..000000000 --- a/test/pages/__snapshots__/basic.test.ts.snap +++ /dev/null @@ -1,60 +0,0 @@ -// Vitest Snapshot v1 - -exports[`localized 1`] = ` -[ - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___en", - "path": "", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/about", - }, - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___ja", - "path": "", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/ja/about", - }, - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___fr", - "path": "", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/fr/about", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___en", - "path": "/", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___ja", - "path": "/ja", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___fr", - "path": "/fr", - }, -] -`; diff --git a/test/pages/__snapshots__/custom_route.test.ts.snap b/test/pages/__snapshots__/custom_route.test.ts.snap index 1ce5a8eb4..14c3984ba 100644 --- a/test/pages/__snapshots__/custom_route.test.ts.snap +++ b/test/pages/__snapshots__/custom_route.test.ts.snap @@ -1,131 +1,195 @@ // Vitest Snapshot v1 -exports[`component > JavaScript 1`] = ` +exports[`#1649 1`] = ` [ { - "children": [], - "path": "/about-us", + "children": [ + { + "children": [], + "file": "/path/to/1649/pages/account/addresses.vue", + "name": "account-addresses___en", + "path": "addresses", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/index.vue", + "name": "account___en", + "path": "", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/profile.vue", + "name": "account-profile___en", + "path": "profile", + }, + ], + "file": "/path/to/1649/pages/account.vue", + "path": "/account", }, { - "children": [], - "path": "/ja/about-ja", + "children": [ + { + "children": [], + "file": "/path/to/1649/pages/account/addresses.vue", + "name": "account-addresses___ja", + "path": "addresses", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/index.vue", + "name": "account___ja", + "path": "", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/profile.vue", + "name": "account-profile___ja", + "path": "profile", + }, + ], + "file": "/path/to/1649/pages/account.vue", + "path": "/ja/account", }, { - "children": [], - "path": "/fr/a-propos", + "children": [ + { + "children": [], + "file": "/path/to/1649/pages/account/addresses.vue", + "name": "account-addresses___fr", + "path": "/fr/compte/adresses", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/index.vue", + "name": "account___fr", + "path": "", + }, + { + "children": [], + "file": "/path/to/1649/pages/account/profile.vue", + "name": "account-profile___fr", + "path": "/fr/compte/profil", + }, + ], + "file": "/path/to/1649/pages/account.vue", + "path": "/fr/compte", }, -] -`; - -exports[`component > dynamic route 1`] = ` -[ { "children": [], - "name": "articles-name___en", - "path": "/articles/:name", + "file": "/path/to/1649/pages/index.vue", + "name": "index___en", + "path": "/", }, { "children": [], - "name": "articles-name___ja", - "path": "/ja/記事/:name", + "file": "/path/to/1649/pages/index.vue", + "name": "index___ja", + "path": "/ja", }, { "children": [], - "name": "articles-name___fr", - "path": "/fr/articles/:name", + "file": "/path/to/1649/pages/index.vue", + "name": "index___fr", + "path": "/fr", }, ] `; -exports[`component > simple 1`] = ` +exports[`Modeule configration > dynamic parameters 1`] = ` [ { "children": [], - "path": "/about-us", + "file": "/path/to/nuxt-app/pages/blog/[date]/[slug].vue", + "name": "blog-date-slug___en", + "path": "/blog/:date/:slug", }, { "children": [], - "path": "/ja/about-ja", + "file": "/path/to/nuxt-app/pages/blog/[date]/[slug].vue", + "name": "blog-date-slug___ja", + "path": "/ja/blog/tech/:date/:slug", }, { "children": [], - "path": "/fr/a-propos", + "file": "/path/to/nuxt-app/pages/blog/[date]/[slug].vue", + "name": "blog-date-slug___fr", + "path": "/fr/blog/:date/:slug", }, ] `; -exports[`component > with definePageMeta 1`] = ` +exports[`Modeule configration > simple 1`] = ` [ { "children": [], + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/about-us", }, { "children": [], - "path": "/ja/about-ja", + "file": "/path/to/nuxt-app/pages/about.vue", + "path": "/ja/about-us", }, { "children": [], + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/fr/a-propos", }, ] `; -exports[`configuration > nested complex route 1`] = ` +exports[`Modeule configration > the part of URL 1`] = ` [ { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___en", + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/about", }, { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___ja", + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/ja/about", }, { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___fr", + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/fr/a-propos", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", - "name": "services-development-app___en", - "path": "/services/development/app", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching___en", + "path": "/services/coaching", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", - "name": "services-development-app___ja", - "path": "/ja/services/development/app", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching___ja", + "path": "/ja/services/coaching", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", - "name": "services-development-app___fr", - "path": "/fr/offres/developement/app", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching___fr", + "path": "/fr/offres/formation", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___en", - "path": "/services/development/coaching", + "file": "/path/to/nuxt-app/pages/services/development/app.vue", + "name": "services-development-app___en", + "path": "/services/development/app", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___ja", - "path": "/ja/services/development/coaching", + "file": "/path/to/nuxt-app/pages/services/development/app.vue", + "name": "services-development-app___ja", + "path": "/ja/services/development/app", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___fr", - "path": "/fr/services/development/coaching", + "file": "/path/to/nuxt-app/pages/services/development/app.vue", + "name": "services-development-app___fr", + "path": "/fr/offres/developement/app", }, { "children": [], @@ -147,19 +211,19 @@ exports[`configuration > nested complex route 1`] = ` }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website___en", "path": "/services/development/website", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website___ja", "path": "/ja/services/development/website", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website___fr", "path": "/fr/offres/developement/site-web", }, @@ -184,125 +248,73 @@ exports[`configuration > nested complex route 1`] = ` ] `; -exports[`configuration > nested dynamic route 1`] = ` +exports[`Page components > JavaScript 1`] = ` [ { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug___en", - "path": "/mycustompath/:nested/:slug(.*)*", + "path": "/about-us", }, { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug___ja", - "path": "/ja/mycustompath/:nested/:slug(.*)*", + "path": "/ja/about-ja", }, { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug___fr", - "path": "/fr/mycustompath/:nested/:slug(.*)*", + "path": "/fr/a-propos", }, +] +`; + +exports[`Page components > dynamic route 1`] = ` +[ { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue", - "name": "nested-route___en", - "path": "/mycustompath/:nested/:route", + "name": "articles-name___en", + "path": "/articles/:name", }, { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue", - "name": "nested-route___ja", - "path": "/ja/mycustompath/:nested/:route", + "name": "articles-name___ja", + "path": "/ja/%E8%A8%98%E4%BA%8B/:name", }, { "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue", - "name": "nested-route___fr", - "path": "/fr/mycustompath/:nested/:route", + "name": "articles-name___fr", + "path": "/fr/articles/:name", }, ] `; -exports[`configuration > nested static route 1`] = ` +exports[`Page components > simple 1`] = ` [ { "children": [], - "file": "/path/to/nuxt-app/pages/nested/route/index.vue", - "name": "nested-route___en", - "path": "/mycustompath/nested/route", + "path": "/about-us", }, { "children": [], - "file": "/path/to/nuxt-app/pages/nested/route/index.vue", - "name": "nested-route___ja", - "path": "/ja/mycustompath/nested/route", + "path": "/ja/about-ja", }, { "children": [], - "file": "/path/to/nuxt-app/pages/nested/route/index.vue", - "name": "nested-route___fr", - "path": "/fr/mycustompath/nested/route", + "path": "/fr/a-propos", }, ] `; -exports[`configuration > simple 1`] = ` +exports[`Page components > with definePageMeta 1`] = ` [ - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___en", - "path": "", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/about", - }, - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___ja", - "path": "/ja/about-ja", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/ja/about-ja", - }, - { - "children": [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___fr", - "path": "", - }, - ], - "file": "/path/to/nuxt-app/pages/about.vue", - "path": "/fr/about", - }, { "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___en", - "path": "/", + "path": "/about-us", }, { "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___ja", - "path": "/ja", + "path": "/ja/about-ja", }, { "children": [], - "file": "/path/to/nuxt-app/pages/index.vue", - "name": "index___fr", - "path": "/fr", + "path": "/fr/a-propos", }, ] `; diff --git a/test/pages/analyze_nuxt_page.test.ts b/test/pages/analyze_nuxt_page.test.ts new file mode 100644 index 000000000..2957cca05 --- /dev/null +++ b/test/pages/analyze_nuxt_page.test.ts @@ -0,0 +1,132 @@ +import { test, expect } from 'vitest' +import { analyzeNuxtPages } from '../../src/pages' + +import type { NuxtPage } from '@nuxt/schema' +import type { NuxtPageAnalizeContext, AnalizedNuxtPageMeta } from '../../src/pages' + +test('analyzeNuxtPages', () => { + const pages: NuxtPage[] = [ + { + name: 'catch', + path: '/:catch(.*)*', + file: '/path/to/nuxt-app/pages/[...catch].vue', + children: [] + }, + { + path: '/account', + file: '/path/to/nuxt-app/pages/account.vue', + children: [ + { + name: 'account-addresses', + path: 'addresses', + file: '/path/to/nuxt-app/pages/account/addresses.vue', + children: [] + }, + { + name: 'account-fooid', + path: 'foo:id', + file: '/path/to/nuxt-app/pages/account/foo[id].vue', + children: [] + }, + { + name: 'account', + path: '', + file: '/path/to/nuxt-app/pages/account/index.vue', + children: [] + }, + { + name: 'account-profile', + path: 'profile', + file: '/path/to/nuxt-app/pages/account/profile.vue', + children: [] + } + ] + }, + { + name: 'blog-date-slug', + path: '/blog/:date/:slug', + file: '/path/to/nuxt-app/pages/blog/[date]/[slug].vue', + children: [] + }, + { + name: 'foo', + path: '/foo', + file: '/path/to/nuxt-app/pages/foo.vue', + children: [ + { + name: 'foo-bar', + path: 'bar', + file: '/path/to/nuxt-app/pages/foo/bar.vue', + children: [ + { + name: 'foo-bar', + path: 'bar', + file: '/path/to/nuxt-app/pages/foo/bar.vue', + children: [ + { + name: 'foo-bar-buz', + path: 'buz', + file: '/path/to/nuxt-app/pages/foo/bar/buz.vue', + children: [] + } + ] + } + ] + }, + { + name: 'foo-hoge-piyo', + path: 'hoge/:piyo', + file: '/path/to/nuxt-app/pages/foo/hoge/[piyo].vue', + children: [] + }, + { + name: 'foo-qux', + path: 'qux', + file: '/path/to/nuxt-app/pages/foo/qux.vue', + children: [] + } + ] + }, + { + name: 'index', + path: '/', + file: '/path/to/nuxt-app/pages/index.vue', + children: [] + }, + { + name: 'users-profile', + path: '/uses/:profile?', + file: '/path/to/nuxt-app/pages/users/[[profile]].vue', + children: [] + } + ] + + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + analyzeNuxtPages(ctx, pages) + + expect(ctx.stack.length).toBe(0) + expect([...ctx.pages.values()]).toEqual([ + { inRoot: true, path: '[...catch]' }, + { inRoot: true, path: 'account' }, + { inRoot: false, path: 'account/addresses' }, + { inRoot: false, path: 'account/foo[id]' }, + { inRoot: false, path: 'account/index' }, + { inRoot: false, path: 'account/profile' }, + { inRoot: true, path: 'blog/[date]/[slug]' }, + { inRoot: true, path: 'foo' }, + { inRoot: false, path: 'foo/bar' }, + { inRoot: false, path: 'foo/bar' }, + { inRoot: false, path: 'foo/bar/buz' }, + { inRoot: false, path: 'foo/hoge/[piyo]' }, + { inRoot: false, path: 'foo/qux' }, + { inRoot: true, path: 'index' }, + { inRoot: true, path: 'users/[[profile]]' } + ]) +}) diff --git a/test/pages/basic.test.ts b/test/pages/basic.test.ts deleted file mode 100644 index 517de977d..000000000 --- a/test/pages/basic.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { vi, test, expect } from 'vitest' -import { localizeRoutes } from 'vue-i18n-routing' -import { getRouteOptionsResolver } from '../../src/pages' -import fs from 'node:fs' -import { getNuxtOptions } from './utils' - -import type { NuxtI18nOptions } from '../../src/types' -import type { NuxtPage } from './utils' - -test('localized', async () => { - vi.spyOn(fs, 'readFileSync').mockReturnValue('') - - const options = getNuxtOptions({}) - const pages: NuxtPage[] = [ - { - path: '/about', - file: '/path/to/nuxt-app/pages/about.vue', - children: [ - { - name: 'about', - path: '', - file: '/path/to/nuxt-app/pages/about/index.vue', - children: [] - } - ] - }, - { - name: 'index', - path: '/', - file: '/path/to/nuxt-app/pages/index.vue', - children: [] - } - ] - const localizedPages = localizeRoutes(pages, { - ...options, - includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) - }) - expect(localizedPages).toMatchSnapshot() -}) diff --git a/test/pages/custom_route.test.ts b/test/pages/custom_route.test.ts index 2cc22d6e7..eed086ec9 100644 --- a/test/pages/custom_route.test.ts +++ b/test/pages/custom_route.test.ts @@ -1,91 +1,42 @@ +import { vi, test, expect } from 'vitest' import fs from 'node:fs' import { resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import { vi, describe, test, expect } from 'vitest' import { localizeRoutes } from 'vue-i18n-routing' -import { getRouteOptionsResolver } from '../../src/pages' +import { getRouteOptionsResolver, analyzeNuxtPages } from '../../src/pages' import { getNuxtOptions, stripFilePropertyFromPages } from './utils' +import { NuxtPageAnalizeContext, AnalizedNuxtPageMeta } from '../../src/pages' import type { NuxtI18nOptions } from '../../src/types' - -const __dirname = fileURLToPath(new URL('.', import.meta.url)) +import type { NuxtPage } from './utils' describe.each([ { case: 'simple', - options: getNuxtOptions({ about: { ja: '/about-ja' } }), - pages: [ - { - path: '/about', - file: '/path/to/nuxt-app/pages/about.vue', - children: [ - { - name: 'about', - path: '', - file: '/path/to/nuxt-app/pages/about/index.vue', - children: [] - } - ] - }, - { - name: 'index', - path: '/', - file: '/path/to/nuxt-app/pages/index.vue', - children: [] - } - ] - }, - { - case: 'nested static route', options: getNuxtOptions({ - 'nested/route': { - en: '/mycustompath/nested/route' - } - }), - pages: [ - { - name: 'nested-route', - path: '/nested/route', - file: '/path/to/nuxt-app/pages/nested/route/index.vue', - children: [] - } - ] - }, - { - case: 'nested dynamic route', - options: getNuxtOptions({ - ':nested/:route': { - en: '/mycustompath/:nested/:route' - }, - ':nested/:route/:slug(.*)*': { - en: '/mycustompath/:nested/:slug(.*)*' + about: { + en: '/about-us', + fr: '/a-propos', + es: '/sobre' } }), pages: [ { - name: 'nested-route-slug', - path: '/:nested/:route/:slug(.*)*', - file: '/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue', - children: [] - }, - { - name: 'nested-route', - path: '/:nested/:route', - file: '/path/to/nuxt-app/pages/[nested]/[route]/index.vue', + path: '/about', + file: '/path/to/nuxt-app/pages/about.vue', children: [] } ] }, { - case: 'nested complex route', + case: 'the part of URL', options: getNuxtOptions({ about: { fr: '/a-propos' }, - services: { + 'services/index': { fr: '/offres' }, - 'services/development': { + 'services/development/index': { fr: '/offres/developement' }, 'services/development/app': { @@ -100,21 +51,20 @@ describe.each([ }), pages: [ { - name: 'about', path: '/about', - file: '/path/to/nuxt-app/pages/about/index.vue', + file: '/path/to/nuxt-app/pages/about.vue', children: [] }, { - name: 'services-development-app', - path: '/services/development/app', - file: '/path/to/nuxt-app/pages/services/development/app/index.vue', + name: 'services-coaching', + path: '/services/coaching', + file: '/path/to/nuxt-app/pages/services/coaching.vue', children: [] }, { - name: 'services-development-coaching', - path: '/services/development/coaching', - file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue', + name: 'services-development-app', + path: '/services/development/app', + file: '/path/to/nuxt-app/pages/services/development/app.vue', children: [] }, { @@ -126,7 +76,7 @@ describe.each([ { name: 'services-development-website', path: '/services/development/website', - file: '/path/to/nuxt-app/pages/services/development/website/index.vue', + file: '/path/to/nuxt-app/pages/services/development/website.vue', children: [] }, { @@ -136,16 +86,43 @@ describe.each([ children: [] } ] + }, + { + case: 'dynamic parameters', + options: getNuxtOptions({ + 'blog/[date]/[slug]': { + ja: '/blog/tech/[date]/[slug]' + } + }), + pages: [ + { + name: 'blog-date-slug', + path: '/blog/:date/:slug', + file: '/path/to/nuxt-app/pages/blog/[date]/[slug].vue', + children: [] + } + ] } -])('configuration', ({ case: _case, options, pages }) => { +])('Modeule configration', ({ case: _case, options, pages }) => { test(_case, async () => { vi.spyOn(fs, 'readFileSync').mockReturnValue('') + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) + expect(localizedPages).toMatchSnapshot() }) }) @@ -153,7 +130,7 @@ describe.each([ describe.each([ { case: 'simple', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { path: '/about', @@ -164,7 +141,7 @@ describe.each([ }, { case: 'dynamic route', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { name: 'articles-name', @@ -176,7 +153,7 @@ describe.each([ }, { case: 'with definePageMeta', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { path: '/about', @@ -187,7 +164,7 @@ describe.each([ }, { case: 'JavaScript', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { path: '/about', @@ -196,13 +173,90 @@ describe.each([ } ] } -])('component', ({ case: _case, options, pages }) => { +])('Page components', ({ case: _case, options, pages }) => { test(_case, async () => { + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot() }) }) + +test('#1649', async () => { + const pages = [ + { + path: '/account', + file: '/path/to/1649/pages/account.vue', + children: [ + { + name: 'account-addresses', + path: 'addresses', + file: '/path/to/1649/pages/account/addresses.vue', + children: [] + }, + { + name: 'account', + path: '', + file: '/path/to/1649/pages/account/index.vue', + children: [] + }, + { + name: 'account-profile', + path: 'profile', + file: '/path/to/1649/pages/account/profile.vue', + children: [] + } + ] + }, + { + name: 'index', + path: '/', + file: '/path/to/1649/pages/index.vue', + children: [] + } + ] + + const options = getNuxtOptions({ + account: { + fr: '/compte' + }, + 'account/profile': { + fr: '/compte/profil' + }, + 'account/addresses': { + fr: '/compte/adresses' + } + }) + + vi.spyOn(fs, 'readFileSync').mockReturnValue('') + + const srcDir = '/path/to/1649' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) + const localizedPages = localizeRoutes(pages, { + ...options, + includeUprefixedFallback: false, + optionsResolver: getRouteOptionsResolver(ctx, options as Required) + }) + + expect(localizedPages).toMatchSnapshot() +}) diff --git a/test/pages/ignore_route/__snapshots__/disable.test.ts.snap b/test/pages/ignore_route/__snapshots__/disable.test.ts.snap index 1330c5ba2..e5ca4f142 100644 --- a/test/pages/ignore_route/__snapshots__/disable.test.ts.snap +++ b/test/pages/ignore_route/__snapshots__/disable.test.ts.snap @@ -1,46 +1,24 @@ // Vitest Snapshot v1 -exports[`component > basic 1`] = ` +exports[`Module configuration > nested complex route 1`] = ` [ { "children": [], + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/about", }, -] -`; - -exports[`configuration > nested complex route 1`] = ` -[ { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about", - "path": "/about", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching", + "path": "/services/coaching", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/app.vue", "name": "services-development-app", "path": "/services/development/app", }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___en", - "path": "/services/development/coaching", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___ja", - "path": "/ja/services/development/coaching", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___fr", - "path": "/fr/services/development/coaching", - }, { "children": [], "file": "/path/to/nuxt-app/pages/services/development/index.vue", @@ -49,7 +27,7 @@ exports[`configuration > nested complex route 1`] = ` }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website", "path": "/services/development/website", }, @@ -62,14 +40,8 @@ exports[`configuration > nested complex route 1`] = ` ] `; -exports[`configuration > nested dynamic route 1`] = ` +exports[`Module configuration > nested dynamic route 1`] = ` [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug", - "path": "/:nested/:route/:slug(.*)*", - }, { "children": [], "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue", @@ -79,7 +51,7 @@ exports[`configuration > nested dynamic route 1`] = ` ] `; -exports[`configuration > nested static route 1`] = ` +exports[`Module configuration > nested static route 1`] = ` [ { "children": [], @@ -90,7 +62,7 @@ exports[`configuration > nested static route 1`] = ` ] `; -exports[`configuration > simple 1`] = ` +exports[`Module configuration > simple 1`] = ` [ { "children": [ @@ -124,3 +96,12 @@ exports[`configuration > simple 1`] = ` }, ] `; + +exports[`Page components > basic 1`] = ` +[ + { + "children": [], + "path": "/about", + }, +] +`; diff --git a/test/pages/ignore_route/__snapshots__/pick.test.ts.snap b/test/pages/ignore_route/__snapshots__/pick.test.ts.snap index 307aa9348..73c23dbca 100644 --- a/test/pages/ignore_route/__snapshots__/pick.test.ts.snap +++ b/test/pages/ignore_route/__snapshots__/pick.test.ts.snap @@ -1,72 +1,35 @@ // Vitest Snapshot v1 -exports[`component > dynamic route 1`] = ` +exports[`Module configuration > nested complex route 1`] = ` [ { "children": [], - "name": "articles-name___fr", - "path": "/fr/articles/:name", + "file": "/path/to/nuxt-app/pages/about.vue", + "path": "/about", }, -] -`; - -exports[`component > simple 1`] = ` -[ { "children": [], + "file": "/path/to/nuxt-app/pages/about.vue", "path": "/ja/about", }, { "children": [], - "path": "/fr/about", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching___en", + "path": "/services/coaching", }, -] -`; - -exports[`configuration > nested complex route 1`] = ` -[ { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___en", - "path": "/about", + "file": "/path/to/nuxt-app/pages/services/coaching.vue", + "name": "services-coaching___ja", + "path": "/ja/services/coaching", }, { "children": [], - "file": "/path/to/nuxt-app/pages/about/index.vue", - "name": "about___ja", - "path": "/ja/about", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", - "name": "services-development-app___en", - "path": "/services/development/app", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/app/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/app.vue", "name": "services-development-app___ja", "path": "/ja/services/development/app", }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___en", - "path": "/services/development/coaching", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___ja", - "path": "/ja/services/development/coaching", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/development/coaching/index.vue", - "name": "services-development-coaching___fr", - "path": "/fr/services/development/coaching", - }, { "children": [], "file": "/path/to/nuxt-app/pages/services/development/index.vue", @@ -81,13 +44,13 @@ exports[`configuration > nested complex route 1`] = ` }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website___en", "path": "/services/development/website", }, { "children": [], - "file": "/path/to/nuxt-app/pages/services/development/website/index.vue", + "file": "/path/to/nuxt-app/pages/services/development/website.vue", "name": "services-development-website___ja", "path": "/ja/services/development/website", }, @@ -97,29 +60,11 @@ exports[`configuration > nested complex route 1`] = ` "name": "services___en", "path": "/services", }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/services/index.vue", - "name": "services___ja", - "path": "/ja/services", - }, ] `; -exports[`configuration > nested dynamic route 1`] = ` +exports[`Module configuration > nested dynamic route 1`] = ` [ - { - "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug___ja", - "path": "/ja/:nested/:route/:slug(.*)*", - }, - { - "children": [], - "file": "/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue", - "name": "nested-route-slug___fr", - "path": "/fr/:nested/:route/:slug(.*)*", - }, { "children": [], "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue", @@ -135,7 +80,7 @@ exports[`configuration > nested dynamic route 1`] = ` ] `; -exports[`configuration > nested static route 1`] = ` +exports[`Module configuration > nested static route 1`] = ` [ { "children": [], @@ -152,7 +97,7 @@ exports[`configuration > nested static route 1`] = ` ] `; -exports[`configuration > simple 1`] = ` +exports[`Module configuration > simple 1`] = ` [ { "children": [ @@ -198,3 +143,26 @@ exports[`configuration > simple 1`] = ` }, ] `; + +exports[`Page components > dynamic route 1`] = ` +[ + { + "children": [], + "name": "articles-name___fr", + "path": "/fr/articles/:name", + }, +] +`; + +exports[`Page components > simple 1`] = ` +[ + { + "children": [], + "path": "/ja/about", + }, + { + "children": [], + "path": "/fr/about", + }, +] +`; diff --git a/test/pages/ignore_route/disable.test.ts b/test/pages/ignore_route/disable.test.ts index 0f9455ece..281e3efb7 100644 --- a/test/pages/ignore_route/disable.test.ts +++ b/test/pages/ignore_route/disable.test.ts @@ -3,10 +3,12 @@ import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { vi, describe, test, expect } from 'vitest' import { localizeRoutes } from 'vue-i18n-routing' -import { getRouteOptionsResolver } from '../../../src/pages' +import { getRouteOptionsResolver, analyzeNuxtPages } from '../../../src/pages' import { getNuxtOptions, stripFilePropertyFromPages } from '../utils' +import { NuxtPageAnalizeContext, AnalizedNuxtPageMeta } from '../../../src/pages' import type { NuxtI18nOptions } from '../../../src/types' +import type { NuxtPage } from '../utils' const __dirname = fileURLToPath(new URL('.', import.meta.url)) @@ -38,7 +40,7 @@ describe.each([ { case: 'nested static route', options: getNuxtOptions({ - 'nested/route': false + 'nested/route/index': false }), pages: [ { @@ -52,16 +54,9 @@ describe.each([ { case: 'nested dynamic route', options: getNuxtOptions({ - ':nested/:route': false, - ':nested/:route/:slug(.*)*': false + '[nested]/[route]/index': false }), pages: [ - { - name: 'nested-route-slug', - path: '/:nested/:route/:slug(.*)*', - file: '/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue', - children: [] - }, { name: 'nested-route', path: '/:nested/:route', @@ -74,29 +69,28 @@ describe.each([ case: 'nested complex route', options: getNuxtOptions({ about: false, - services: false, - 'services/development': false, + 'services/index': false, + 'services/development/index': false, 'services/development/app': false, 'services/development/website': false, 'services/coaching': false }), pages: [ { - name: 'about', path: '/about', - file: '/path/to/nuxt-app/pages/about/index.vue', + file: '/path/to/nuxt-app/pages/about.vue', children: [] }, { - name: 'services-development-app', - path: '/services/development/app', - file: '/path/to/nuxt-app/pages/services/development/app/index.vue', + name: 'services-coaching', + path: '/services/coaching', + file: '/path/to/nuxt-app/pages/services/coaching.vue', children: [] }, { - name: 'services-development-coaching', - path: '/services/development/coaching', - file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue', + name: 'services-development-app', + path: '/services/development/app', + file: '/path/to/nuxt-app/pages/services/development/app.vue', children: [] }, { @@ -108,7 +102,7 @@ describe.each([ { name: 'services-development-website', path: '/services/development/website', - file: '/path/to/nuxt-app/pages/services/development/website/index.vue', + file: '/path/to/nuxt-app/pages/services/development/website.vue', children: [] }, { @@ -119,14 +113,25 @@ describe.each([ } ] } -])('configuration', ({ case: _case, options, pages }) => { +])('Module configuration', ({ case: _case, options, pages }) => { test(_case, async () => { vi.spyOn(fs, 'readFileSync').mockReturnValue('') + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) + const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) expect(localizedPages).toMatchSnapshot() }) @@ -135,7 +140,7 @@ describe.each([ describe.each([ { case: 'basic', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { path: '/about', @@ -144,12 +149,23 @@ describe.each([ } ] } -])('component', ({ case: _case, options, pages }) => { +])('Page components', ({ case: _case, options, pages }) => { test(_case, async () => { + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) + const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot() }) diff --git a/test/pages/ignore_route/pick.test.ts b/test/pages/ignore_route/pick.test.ts index 0ad194321..c7cc975fc 100644 --- a/test/pages/ignore_route/pick.test.ts +++ b/test/pages/ignore_route/pick.test.ts @@ -3,10 +3,12 @@ import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { vi, describe, test, expect } from 'vitest' import { localizeRoutes } from 'vue-i18n-routing' -import { getRouteOptionsResolver } from '../../../src/pages' +import { getRouteOptionsResolver, analyzeNuxtPages } from '../../../src/pages' import { getNuxtOptions, stripFilePropertyFromPages } from '../utils' +import { NuxtPageAnalizeContext, AnalizedNuxtPageMeta } from '../../../src/pages' import type { NuxtI18nOptions } from '../../../src/types' +import type { NuxtPage } from '../utils' const __dirname = fileURLToPath(new URL('.', import.meta.url)) @@ -38,7 +40,7 @@ describe.each([ { case: 'nested static route', options: getNuxtOptions({ - 'nested/route': { + 'nested/route/index': { en: false } }), @@ -54,20 +56,11 @@ describe.each([ { case: 'nested dynamic route', options: getNuxtOptions({ - ':nested/:route': { - en: false - }, - ':nested/:route/:slug(.*)*': { + '[nested]/[route]/index': { en: false } }), pages: [ - { - name: 'nested-route-slug', - path: '/:nested/:route/:slug(.*)*', - file: '/path/to/nuxt-app/pages/[nested]/[route]/[...slug].vue', - children: [] - }, { name: 'nested-route', path: '/:nested/:route', @@ -82,14 +75,16 @@ describe.each([ about: { fr: false }, - services: { - fr: false + 'services/index': { + fr: false, + ja: false }, - 'services/development': { + 'services/development/index': { fr: false }, 'services/development/app': { - fr: false + fr: false, + en: false }, 'services/development/website': { fr: false @@ -100,21 +95,20 @@ describe.each([ }), pages: [ { - name: 'about', path: '/about', - file: '/path/to/nuxt-app/pages/about/index.vue', + file: '/path/to/nuxt-app/pages/about.vue', children: [] }, { - name: 'services-development-app', - path: '/services/development/app', - file: '/path/to/nuxt-app/pages/services/development/app/index.vue', + name: 'services-coaching', + path: '/services/coaching', + file: '/path/to/nuxt-app/pages/services/coaching.vue', children: [] }, { - name: 'services-development-coaching', - path: '/services/development/coaching', - file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue', + name: 'services-development-app', + path: '/services/development/app', + file: '/path/to/nuxt-app/pages/services/development/app.vue', children: [] }, { @@ -126,7 +120,7 @@ describe.each([ { name: 'services-development-website', path: '/services/development/website', - file: '/path/to/nuxt-app/pages/services/development/website/index.vue', + file: '/path/to/nuxt-app/pages/services/development/website.vue', children: [] }, { @@ -137,14 +131,25 @@ describe.each([ } ] } -])('configuration', ({ case: _case, options, pages }) => { +])('Module configuration', ({ case: _case, options, pages }) => { test(_case, async () => { vi.spyOn(fs, 'readFileSync').mockReturnValue('') + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) + const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) expect(localizedPages).toMatchSnapshot() }) @@ -153,7 +158,7 @@ describe.each([ describe.each([ { case: 'simple', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { path: '/about', @@ -164,7 +169,7 @@ describe.each([ }, { case: 'dynamic route', - options: getNuxtOptions({}, true), + options: getNuxtOptions({}, 'page'), pages: [ { name: 'articles-name', @@ -174,12 +179,23 @@ describe.each([ } ] } -])('component', ({ case: _case, options, pages }) => { +])('Page components', ({ case: _case, options, pages }) => { test(_case, async () => { + const srcDir = '/path/to/nuxt-app' + const pagesDir = 'pages' + const ctx: NuxtPageAnalizeContext = { + stack: [], + srcDir, + pagesDir, + pages: new Map() + } + + analyzeNuxtPages(ctx, pages) + const localizedPages = localizeRoutes(pages, { ...options, includeUprefixedFallback: false, - optionsResolver: getRouteOptionsResolver('pages', options as Required) + optionsResolver: getRouteOptionsResolver(ctx, options as Required) }) expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot() }) diff --git a/test/pages/utils.ts b/test/pages/utils.ts index da0b12131..a4959cb3f 100644 --- a/test/pages/utils.ts +++ b/test/pages/utils.ts @@ -5,11 +5,15 @@ import type { NuxtHooks } from '@nuxt/schema' type ExtractArrayType = T extends (infer U)[] ? U : never export type NuxtPage = ExtractArrayType[0]> -export function getNuxtOptions(pages: Required['pages'], parsePages = false): NuxtI18nOptions { +export function getNuxtOptions( + pages: Required['pages'], + customRoutes: Required['customRoutes'] = 'config', + defaultLocale = 'en' +): NuxtI18nOptions { return { - parsePages, + customRoutes, pages, - defaultLocale: 'en', + defaultLocale, strategy: 'prefix_except_default', defaultLocaleRouteNameSuffix: 'default', trailingSlash: false, diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 000000000..ead0fdb7f --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,19 @@ +import { parseSegment, getRoutePath } from '../src/utils' + +test('parseSegment', () => { + const tokens = parseSegment('[foo]_[bar]:[...buz]_buz_[[qux]]') + expect(tokens).toEqual([ + { type: 1, value: 'foo' }, + { type: 0, value: '_' }, + { type: 1, value: 'bar' }, + { type: 0, value: ':' }, + { type: 3, value: 'buz' }, + { type: 0, value: '_buz_' }, + { type: 2, value: 'qux' } + ]) +}) + +test('getRoutePath', () => { + const tokens = parseSegment('[foo]_[bar]:[...buz]_buz_[[qux]]') + expect(getRoutePath(tokens)).toBe(`/:foo_:bar::buz(.*)*_buz_:qux?`) +})