Repository: nuxt-community/i18n-module
Pull request #1546 (2022-10-07 01:34:41)

feat: support ignoring localized routes

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *nuxt-i18n-v8* processing.


|  Name | Link |
|---------------------------------|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 10d082d3122acc62612e03af4ca430f87e380d19 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/nuxt-i18n-v8/deploys/633f82336f225000083a2ba2 |

Patch:
diff --git a/TODO.md b/TODO.md
index eee1e494..318f26e2 100644
--- a/TODO.md
+++ b/TODO.md
@@ -17,9 +17,9 @@ This todo is based on [nuxt/i18n](https://i18n.nuxtjs.org/) docs.
 - [x] Custom route paths
   - [x] In-component options
   - [x] Module's configuration
-- [ ] Ignoring localized routes
-  - [ ] Pick localized routes
-  - [ ] Disable localized routes
+- [x] Ignoring localized routes
+  - [x] Pick localized routes
+  - [x] Disable localized routes
 - [x] Browser language detection
 - [ ] SEO
   - [x] Requirements
diff --git a/docs/content/30.guide/14.migrating.md b/docs/content/30.guide/14.migrating.md
index bd98dc33..5b63746d 100644
--- a/docs/content/30.guide/14.migrating.md
+++ b/docs/content/30.guide/14.migrating.md
@@ -8,7 +8,7 @@ Follow this guide to upgrade from one major version to the other.
 ## Upgrading from `nuxtjs/i18n` v7.x
 
 ::alert{type="warning"}
-// TODO:
+// TODO: Add more details
 ::
 
 ### Deprecated `localeLocation`
@@ -18,7 +18,7 @@ Use `localeRoute` instead for Options API style. The reason for deprecation is d
 ### Deprecated export APIs in nuxt context
 
 ::alert{type="warning"}
-// TODO:
+// TODO: Add more details
 ::
 
 ```js {}[/plugins/myplugin.mjs]
@@ -35,3 +35,15 @@ export default ({ app }) => {
 ### Deprecated `$nuxtI18nHead`
 
 Use `localeHead` instead for Options API style.
+
+::alert{type="warning"}
+// TODO: Add more details
+::
+
+### Deprecated `nuxtI18n` option
+
+Use `defineI18nRoute` compiler macro.
+
+::alert{type="warning"}
+// TODO: Add more details
+::
\ No newline at end of file
diff --git a/docs/content/30.guide/4.custom-paths.md b/docs/content/30.guide/4.custom-paths.md
index 3b549ff5..0f8c0146 100644
--- a/docs/content/30.guide/4.custom-paths.md
+++ b/docs/content/30.guide/4.custom-paths.md
@@ -3,13 +3,13 @@ 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: [Compiler macro](#compiler-macro) 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 pages: [Page component](#page-component) or via the [Nuxt configuration](#nuxt-configuration).
 
 ::alert{type="warning"}
 Custom paths are not supported when using the `no-prefix` [strategy](/strategies).
 ::
 
-### Compiler macro
+### Page component
 
 You can use the `defineI18nRoute` compiler macro to set some custom paths for each page component.
 
diff --git a/docs/content/30.guide/5.ignoring-localized-routes.md b/docs/content/30.guide/5.ignoring-localized-routes.md
index bce577c2..52784ab6 100644
--- a/docs/content/30.guide/5.ignoring-localized-routes.md
+++ b/docs/content/30.guide/5.ignoring-localized-routes.md
@@ -3,11 +3,6 @@ title: Ignoring localized routes
 description: 'Customize localized route exlusions per page component.'
 ---
 
-::alert{type="warning"}
-// TODO:
-🚧 This feature is not implemented yet.
-::
-
 ::alert{type="warning"}
 This feature is not supported with the `no-prefix` [strategy](/strategies).
 ::
@@ -16,22 +11,17 @@ If you'd like some pages to be available in some languages only, you can configu
 
 ### Pick localized routes
 
-::alert{type="warning"}
-// TODO:
-🚧 This feature is not implemented yet.
-::
-
 ::code-group
   ::code-block{label="Page component" active}
   ```js {}[pages/about.vue]
-  export default {
-    nuxtI18n: {
-      locales: ['fr', 'es']
-    }
-  }
+  <script setup>
+  defineI18nRoute({
+    locales: ['fr', 'es']
+  })
+  </script>
   ```
   ::
-  ::code-block{label="Module configuration"}
+  ::code-block{label="Nuxt configuration"}
   ```js {}[nuxt.config.js]
   i18n: {
     parsePages: false,
@@ -47,31 +37,25 @@ If you'd like some pages to be available in some languages only, you can configu
 
 ### Disable localized routes
 
-::alert{type="warning"}
-// TODO:
-🚧 This feature is not implemented yet.
-::
-
 ::code-group
-::code-block{label="Page component" active}
-
-```js {}[pages/about.vue]
-export default {
-  nuxtI18n: false
-}
-```
-
-::
-::code-block{label="Module configuration"}
-
-```js {}[nuxt.config.js]
-i18n: {
-  parsePages: false,
-  pages: {
-    about: false
+  ::code-block{label="Page component" active}
+  ```js {}[pages/about.vue]
+  export default {
+    nuxtI18n: false
   }
-}
-```
-
-::
+  <script setup>
+  defineI18nRoute(false)
+  </script>
+  ```
+  ::
+  ::code-block{label="Nuxt configuration"}
+  ```js {}[nuxt.config.js]
+  i18n: {
+    parsePages: false,
+    pages: {
+      about: false
+    }
+  }
+  ```
+  ::
 ::
diff --git a/docs/content/50.API/2.compiler-macros.md b/docs/content/50.API/2.compiler-macros.md
index d6d1a1f9..9eb7f054 100644
--- a/docs/content/50.API/2.compiler-macros.md
+++ b/docs/content/50.API/2.compiler-macros.md
@@ -22,23 +22,32 @@ defineI18nRoute({
 ## Type
 
 ```ts
-defineI18nRoute(route: I18nRoute) => void
+defineI18nRoute(route: I18nRoute | false) => void
 
 interface I18nRoute {
-  paths?: Record<Locale, string>
+  paths?: Record<string, string>
+  locales?: string[]
 }
 ```
 
 ## Parameters
 
-### `paths`
+### `false`
 
-- **Type**: `I18nRoute`
+  Disable localize for the target page component route.
+
+### `I18nRoute`
 
   An object accepting the following i18n route settings:
 
-  **`paths`**
+  - **`paths`**
+  
+    - **Type**: `Record<Locale, string>`
+    
+      Customize page component routes per locale. You can specify static and dynamic paths for vue-router.
+
+  - **`locales`**
   
-  - **Type**: `Record<Locale, string>`
+    - **Type**: `string[]`
   
-    Customize page component routes per locale. You can specify static and dynamic paths for vue-router.
+      Some locales to which the page component should be localized.
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 810b0581..a8b94b46 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -45,10 +45,10 @@ export default defineNuxtConfig({
     // strategy: 'no_prefix',
     // strategy: 'prefix',
     // strategy: 'prefix_and_default',
-    parsePages: true,
+    parsePages: false,
     pages: {
       about: {
-        ja: '/about-ja'
+        ja: false
       }
     },
     // differentDomains: true,
diff --git a/specs/custom_route_paths/compiler_macro.spec.ts b/specs/custom_route_paths/component.spec.ts
similarity index 100%
rename from specs/custom_route_paths/compiler_macro.spec.ts
rename to specs/custom_route_paths/component.spec.ts
diff --git a/specs/fixtures/basic/pages/about/index.vue b/specs/fixtures/basic/pages/about/index.vue
index 1e3fcfa7..06ffa3c7 100644
--- a/specs/fixtures/basic/pages/about/index.vue
+++ b/specs/fixtures/basic/pages/about/index.vue
@@ -8,16 +8,6 @@ const localePath = useLocalePath()
 const code = computed(() => {
   return localeProperties.value.code
 })
-
-/*
-// TODO: defineNuxtI18n macro
-defineNuxtI18n({
-  paths: {
-    en: '/about-us',
-    fr: '/a-propos'
-  }
-})
-*/
 </script>
 
 <template>
diff --git a/specs/fixtures/basic/pages/ignore-routes/disable.vue b/specs/fixtures/basic/pages/ignore-routes/disable.vue
new file mode 100644
index 00000000..5caff576
--- /dev/null
+++ b/specs/fixtures/basic/pages/ignore-routes/disable.vue
@@ -0,0 +1,7 @@
+<script setup>
+defineI18nRoute(false)
+</script>
+
+<template>
+  <p>ignore localized route disable test</p>
+</template>
diff --git a/specs/fixtures/basic/pages/ignore-routes/pick.vue b/specs/fixtures/basic/pages/ignore-routes/pick.vue
new file mode 100644
index 00000000..4ca5d868
--- /dev/null
+++ b/specs/fixtures/basic/pages/ignore-routes/pick.vue
@@ -0,0 +1,9 @@
+<script setup>
+defineI18nRoute({
+  paths: ['fr']
+})
+</script>
+
+<template>
+  <p>ignore localized route pick test</p>
+</template>
diff --git a/specs/fixtures/basic/pages/index.vue b/specs/fixtures/basic/pages/index.vue
index ddc201a2..eca8bc32 100644
--- a/specs/fixtures/basic/pages/index.vue
+++ b/specs/fixtures/basic/pages/index.vue
@@ -50,6 +50,12 @@ useHead({
       <code id="home-use-locale-head">{{ i18nHead }}</code>
     </section>
     <NuxtLink id="link-about" exact :to="localePath('about')">{{ $t('about') }}</NuxtLink>
-    <NuxtLink id="link-blog" exact :to="localePath('blog')">{{ $t('blog') }}</NuxtLink>
+    <NuxtLink id="link-blog" :to="localePath('blog')">{{ $t('blog') }}</NuxtLink>
+    <NuxtLink id="link-ignore-disable" :to="localePath('/ignore-routes/disable')"
+      >go to ignoring localized disable route</NuxtLink
+    >
+    <NuxtLink id="link-ignore-pick" :to="localePath('/ignore-routes/pick')"
+      >go to ignoring localized pick route</NuxtLink
+    >
   </div>
 </template>
diff --git a/specs/ignoring_localized_route/disable/component.spec.ts b/specs/ignoring_localized_route/disable/component.spec.ts
new file mode 100644
index 00000000..41a0b5f4
--- /dev/null
+++ b/specs/ignoring_localized_route/disable/component.spec.ts
@@ -0,0 +1,38 @@
+import { test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+
+import type { Response } from 'playwright'
+
+await setup({
+  rootDir: fileURLToPath(new URL(`../../fixtures/basic`, import.meta.url)),
+  browser: true,
+  // overrides
+  nuxtConfig: {
+    i18n: {
+      defaultLocale: 'en'
+    }
+  }
+})
+
+test('can not access to disable route path', async () => {
+  const home = url('/')
+  const page = await createPage()
+  await page.goto(home)
+
+  // click `fr` switching link
+  await page.locator('#lang-switcher-with-nuxt-link a').click()
+
+  // disalbe href with <NuxtLink>
+  expect(await page.locator('#link-ignore-disable').getAttribute('href')).toBe(null)
+
+  // disalbe direct url access
+  let res: Response | (Error & { status: () => number }) | null = null
+  try {
+    res = await page.goto(url('/fr/ignore-routes/disable'))
+  } catch (error: unknown) {
+    res = error as Error & { status: () => number }
+  }
+  // 404
+  expect(res!.status()).toBe(404) // eslint-disable-line @typescript-eslint/no-non-null-assertion
+})
diff --git a/specs/ignoring_localized_route/disable/module_configration.spec.ts b/specs/ignoring_localized_route/disable/module_configration.spec.ts
new file mode 100644
index 00000000..cf5e25c1
--- /dev/null
+++ b/specs/ignoring_localized_route/disable/module_configration.spec.ts
@@ -0,0 +1,48 @@
+import { test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+
+import type { Response } from 'playwright'
+
+await setup({
+  rootDir: fileURLToPath(new URL(`../../fixtures/basic`, import.meta.url)),
+  browser: true,
+  // overrides
+  nuxtConfig: {
+    i18n: {
+      defaultLocale: 'en',
+      parsePages: false,
+      pages: {
+        about: false,
+        blog: {
+          en: '/news'
+        },
+        'blog/article': {
+          en: '/news/article'
+        }
+      }
+    }
+  }
+})
+
+test('can not access to disable route path', async () => {
+  const home = url('/')
+  const page = await createPage()
+  await page.goto(home)
+
+  // click `fr` switching link
+  await page.locator('#lang-switcher-with-nuxt-link a').click()
+
+  // disalbe href with <NuxtLink>
+  expect(await page.locator('#link-about').getAttribute('href')).toBe(null)
+
+  // disalbe direct url access
+  let res: Response | (Error & { status: () => number }) | null = null
+  try {
+    res = await page.goto(url('/fr/about'))
+  } catch (error: unknown) {
+    res = error as Error & { status: () => number }
+  }
+  // 404
+  expect(res!.status()).toBe(404) // eslint-disable-line @typescript-eslint/no-non-null-assertion
+})
diff --git a/specs/ignoring_localized_route/pick/component.spec.ts b/specs/ignoring_localized_route/pick/component.spec.ts
new file mode 100644
index 00000000..df720ef4
--- /dev/null
+++ b/specs/ignoring_localized_route/pick/component.spec.ts
@@ -0,0 +1,26 @@
+import { test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+
+await setup({
+  rootDir: fileURLToPath(new URL(`../../fixtures/basic`, import.meta.url)),
+  browser: true,
+  // overrides
+  nuxtConfig: {
+    i18n: {
+      defaultLocale: 'en'
+    }
+  }
+})
+
+test('can not access to pick route path', async () => {
+  const home = url('/')
+  const page = await createPage()
+  await page.goto(home)
+
+  // click `fr` switching link
+  await page.locator('#lang-switcher-with-nuxt-link a').click()
+
+  // pick href with <NuxtLink>
+  expect(await page.locator('#link-ignore-pick').getAttribute('href')).toBe('/fr/ignore-routes/pick')
+})
diff --git a/specs/ignoring_localized_route/pick/module_configration.spec.ts b/specs/ignoring_localized_route/pick/module_configration.spec.ts
new file mode 100644
index 00000000..935e7032
--- /dev/null
+++ b/specs/ignoring_localized_route/pick/module_configration.spec.ts
@@ -0,0 +1,50 @@
+import { test, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, url, createPage } from '@nuxt/test-utils'
+
+import type { Response } from 'playwright'
+
+await setup({
+  rootDir: fileURLToPath(new URL(`../../fixtures/basic`, import.meta.url)),
+  browser: true,
+  // overrides
+  nuxtConfig: {
+    i18n: {
+      defaultLocale: 'en',
+      parsePages: false,
+      pages: {
+        about: {
+          fr: false
+        },
+        blog: {
+          en: '/news'
+        },
+        'blog/article': {
+          en: '/news/article'
+        }
+      }
+    }
+  }
+})
+
+test('can not access to pick route path', async () => {
+  const home = url('/')
+  const page = await createPage()
+  await page.goto(home)
+
+  // click `fr` switching link
+  await page.locator('#lang-switcher-with-nuxt-link a').click()
+
+  // disalbe href with <NuxtLink>
+  expect(await page.locator('#link-about').getAttribute('href')).toBe(null)
+
+  // disalbe direct url access
+  let res: Response | (Error & { status: () => number }) | null = null
+  try {
+    res = await page.goto(url('/fr/about'))
+  } catch (error: unknown) {
+    res = error as Error & { status: () => number }
+  }
+  // 404
+  expect(res!.status()).toBe(404) // eslint-disable-line @typescript-eslint/no-non-null-assertion
+})
diff --git a/src/macros.ts b/src/macros.ts
index ca2ec72e..0c768265 100644
--- a/src/macros.ts
+++ b/src/macros.ts
@@ -19,6 +19,12 @@ export interface TransformMacroPluginOptions {
 
 const debug = createDebug('@nuxtjs/i18n:macros')
 
+/**
+ * TODO:
+ *  `paths`, `locales` completions like `unplugin-vue-router`
+ *  ref: https://github.com/posva/unplugin-vue-router
+ */
+
 export const TransformMacroPlugin = createUnplugin((options: TransformMacroPluginOptions) => {
   return {
     name: 'nuxtjs:i18n-macros-transform',
diff --git a/src/pages.ts b/src/pages.ts
index 8f5cb955..30980c95 100644
--- a/src/pages.ts
+++ b/src/pages.ts
@@ -117,31 +117,31 @@ function getRouteOptionsFromPages(
 
 function getRouteOptionsFromComponent(route: I18nRoute, localeCodes: string[]) {
   debug('getRouteOptionsFromComponent', route)
-  const options: ComputedRouteOptions = {
-    locales: localeCodes,
-    paths: {}
-  }
-
   const file = route.component || route.file
   if (!isString(file)) {
     return null
   }
 
   const componentOptions = readComponent(file)
-  if (componentOptions != null) {
-    options.paths = componentOptions.paths
+  if (componentOptions == null) {
+    return {
+      locales: localeCodes,
+      paths: {}
+    }
+  } else if (componentOptions === false) {
+    return null
+  } else {
+    return componentOptions
   }
-
-  return options
 }
 
 function readComponent(target: string) {
-  let options: ComputedRouteOptions | null = null
+  let options: ComputedRouteOptions | false | undefined = undefined
   try {
     const content = fs.readFileSync(target, 'utf8').toString()
     const { 0: match, index = 0 } =
       content.match(new RegExp(`\\b${'defineI18nRoute'}\\s*\\(\\s*`)) || ({} as RegExpMatchArray)
-    const macroContent = match ? extractObject(content.slice(index + match.length)) : 'undefined'
+    const macroContent = match ? extractValue(content.slice(index + match.length)) : 'undefined'
     options = new Function(`return (${macroContent})`)()
   } catch (e: unknown) {
     console.warn(formatMessage(`Couldn't read component data at ${target}: (${(e as Error).message})`))
@@ -157,18 +157,23 @@ const starts = {
   '"': '"',
   "'": "'"
 }
-const QUOTE_RE = /["']/
+const REGEX_QUOTE = /["']/
+const REGEX_FALSE = /false/
 
-function extractObject(code: string) {
+function extractValue(code: string) {
   // Strip comments
   code = code.replace(/^\s*\/\/.*$/gm, '')
 
+  if (REGEX_FALSE.test(code)) {
+    return 'false'
+  }
+
   const stack: string[] = []
   let result = ''
   do {
     if (stack[0] === code[0] && result.slice(-1) !== '\\') {
       stack.shift()
-    } else if (code[0] in starts && !QUOTE_RE.test(stack[0])) {
+    } else if (code[0] in starts && !REGEX_QUOTE.test(stack[0])) {
       stack.unshift(starts[code[0] as keyof typeof starts])
     }
     result += code[0]
diff --git a/src/runtime/composables.ts b/src/runtime/composables.ts
index b3ee89ed..e3c2f6c7 100644
--- a/src/runtime/composables.ts
+++ b/src/runtime/composables.ts
@@ -131,6 +131,12 @@ const warnRuntimeUsage = (method: string) =>
       'compiled away and passing it at runtime has no effect.'
   )
 
+/**
+ * TODO:
+ *  `paths`, `locales` completions like `unplugin-vue-router`
+ *  ref: https://github.com/posva/unplugin-vue-router
+ */
+
 /**
  * The i18n custom route for page components
  */
@@ -141,15 +147,19 @@ export interface I18nRoute {
    * @description You can specify static and dynamic paths for vue-router.
    */
   paths?: Record<Locale, string>
+  /**
+   * Some locales to which the page component should be localized.
+   */
+  locales?: string[]
 }
 
 /**
- * Deinfe custom route for page component
+ * Define custom route for page component
  *
  * @param route - The custou route
  */
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function defineI18nRoute(route: I18nRoute): void {
+export function defineI18nRoute(route: I18nRoute | false): void {
   if (process.dev) {
     warnRuntimeUsage('defineI18nRoute')
   }


Test patch:
diff --git a/test/fixtures/dynamic/pages/articles/[name].vue b/test/fixtures/custom_route/dynamic/pages/articles/[name].vue
similarity index 100%
rename from test/fixtures/dynamic/pages/articles/[name].vue
rename to test/fixtures/custom_route/dynamic/pages/articles/[name].vue
diff --git a/test/fixtures/script-setup/pages/about.vue b/test/fixtures/custom_route/simple/pages/about.vue
similarity index 100%
rename from test/fixtures/script-setup/pages/about.vue
rename to test/fixtures/custom_route/simple/pages/about.vue
diff --git a/test/fixtures/ignore_route/disable/pages/about.vue b/test/fixtures/ignore_route/disable/pages/about.vue
new file mode 100644
index 00000000..34fc781e
--- /dev/null
+++ b/test/fixtures/ignore_route/disable/pages/about.vue
@@ -0,0 +1,5 @@
+<script setup lang="ts">
+import { defineI18nRoute } from '#i18n'
+
+defineI18nRoute(false)
+</script>
diff --git a/test/fixtures/ignore_route/pick/dynamic/pages/articles/[name].vue b/test/fixtures/ignore_route/pick/dynamic/pages/articles/[name].vue
new file mode 100644
index 00000000..c05f871b
--- /dev/null
+++ b/test/fixtures/ignore_route/pick/dynamic/pages/articles/[name].vue
@@ -0,0 +1,7 @@
+<script setup lang="ts">
+import { defineI18nRoute } from '#i18n'
+
+defineI18nRoute({
+  locales: ['fr']
+})
+</script>
diff --git a/test/fixtures/ignore_route/pick/simple/pages/about.vue b/test/fixtures/ignore_route/pick/simple/pages/about.vue
new file mode 100644
index 00000000..5137d38f
--- /dev/null
+++ b/test/fixtures/ignore_route/pick/simple/pages/about.vue
@@ -0,0 +1,7 @@
+<script setup lang="ts">
+import { defineI18nRoute } from '#i18n'
+
+defineI18nRoute({
+  locales: ['fr', 'ja']
+})
+</script>
diff --git a/test/fixtures/simple/pages/about.vue b/test/fixtures/simple/pages/about.vue
deleted file mode 100644
index a5e64095..00000000
--- a/test/fixtures/simple/pages/about.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script lang="ts">
-import { defineComponent } from 'vue'
-import { defineI18nRoute } from '#i18n'
-
-export default defineComponent({
-  setup() {
-    defineI18nRoute({
-      paths: {
-        en: '/about-us',
-        fr: '/a-propos',
-        ja: '/about-ja'
-      }
-    })
-    return {}
-  }
-})
-</script>
diff --git a/test/pages.test.ts b/test/pages.test.ts
deleted file mode 100644
index d29aecce..00000000
--- a/test/pages.test.ts
+++ /dev/null
@@ -1,288 +0,0 @@
-import { vi, describe, test, expect } from 'vitest'
-import { localizeRoutes } from 'vue-i18n-routing'
-import { getRouteOptionsResolver } from '../src/pages'
-import fs from 'node:fs'
-import { fileURLToPath } from 'node:url'
-import { resolve } from 'node:path'
-
-import type { NuxtI18nOptions } from '../src/types'
-import { I18nRoute } from 'vue-i18n-routing'
-import type { NuxtHooks } from '@nuxt/schema'
-
-type ExtractArrayType<T> = T extends (infer U)[] ? U : never
-type NuxtPage = ExtractArrayType<Parameters<NuxtHooks['pages:extend']>[0]>
-
-const __dirname = fileURLToPath(new URL('.', import.meta.url))
-
-function getNuxtOptions(pages: Required<NuxtI18nOptions>['pages'], parsePages = false): NuxtI18nOptions {
-  return {
-    parsePages,
-    pages,
-    defaultLocale: 'en',
-    strategy: 'prefix_except_default',
-    defaultLocaleRouteNameSuffix: 'default',
-    trailingSlash: false,
-    routesNameSeparator: '___',
-    locales: [
-      { code: 'en', iso: 'en-US', file: 'en.json', name: 'English' },
-      { code: 'ja', iso: 'ja-JP', file: 'ja.json', name: 'Japanses' },
-      { code: 'fr', iso: 'fr-FR', file: 'fr.json', name: 'Français' }
-    ]
-  }
-}
-
-function stripFilePropertyFromPages(routes: I18nRoute[]) {
-  return routes.map(route => {
-    delete route.file
-    if (route.children) {
-      route.children = stripFilePropertyFromPages(route.children)
-    }
-    return route
-  })
-}
-
-test('basic', 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<NuxtI18nOptions>)
-  })
-  expect(localizedPages).toMatchSnapshot()
-})
-
-describe('custom route', () => {
-  describe('configuration', () => {
-    test('simple', async () => {
-      vi.spyOn(fs, 'readFileSync').mockReturnValue('')
-
-      const options = getNuxtOptions({ about: { ja: '/about-ja' } })
-      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<NuxtI18nOptions>)
-      })
-      expect(localizedPages).toMatchSnapshot()
-    })
-
-    test('nested static route', async () => {
-      vi.spyOn(fs, 'readFileSync').mockReturnValue('')
-
-      const options = getNuxtOptions({
-        'nested/route': {
-          en: '/mycustompath/nested/route'
-        }
-      })
-      const pages: NuxtPage[] = [
-        {
-          name: 'nested-route',
-          path: '/nested/route',
-          file: '/path/to/nuxt-app/pages/nested/route/index.vue',
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(localizedPages).toMatchSnapshot()
-    })
-
-    test('nested dynamic route', async () => {
-      vi.spyOn(fs, 'readFileSync').mockReturnValue('')
-
-      const options = getNuxtOptions({
-        ':nested/:route': {
-          en: '/mycustompath/:nested/:route'
-        },
-        ':nested/:route/:slug(.*)*': {
-          en: '/mycustompath/:nested/:slug(.*)*'
-        }
-      })
-      const pages: NuxtPage[] = [
-        {
-          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',
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(localizedPages).toMatchSnapshot()
-    })
-
-    test('nested complex route', async () => {
-      const options = getNuxtOptions({
-        about: {
-          fr: '/a-propos'
-        },
-        services: {
-          fr: '/offres'
-        },
-        'services/development': {
-          fr: '/offres/developement'
-        },
-        'services/development/app': {
-          fr: '/offres/developement/app'
-        },
-        'services/development/website': {
-          fr: '/offres/developement/site-web'
-        },
-        'services/coaching': {
-          fr: '/offres/formation'
-        }
-      })
-      const pages: NuxtPage[] = [
-        {
-          name: 'about',
-          path: '/about',
-          file: '/path/to/nuxt-app/pages/about/index.vue',
-          children: []
-        },
-        {
-          name: 'services-development-app',
-          path: '/services/development/app',
-          file: '/path/to/nuxt-app/pages/services/development/app/index.vue',
-          children: []
-        },
-        {
-          name: 'services-development-coaching',
-          path: '/services/development/coaching',
-          file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue',
-          children: []
-        },
-        {
-          name: 'services-development',
-          path: '/services/development',
-          file: '/path/to/nuxt-app/pages/services/development/index.vue',
-          children: []
-        },
-        {
-          name: 'services-development-website',
-          path: '/services/development/website',
-          file: '/path/to/nuxt-app/pages/services/development/website/index.vue',
-          children: []
-        },
-        {
-          name: 'services',
-          path: '/services',
-          file: '/path/to/nuxt-app/pages/services/index.vue',
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(localizedPages).toMatchSnapshot()
-    })
-  })
-
-  describe('component', () => {
-    test('simple', async () => {
-      const options = getNuxtOptions({}, true)
-      const pages: NuxtPage[] = [
-        {
-          path: '/about',
-          file: resolve(__dirname, './fixtures/simple/pages/about.vue'),
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
-    })
-
-    test('script-setup', async () => {
-      const options = getNuxtOptions({}, true)
-      const pages: NuxtPage[] = [
-        {
-          path: '/about',
-          file: resolve(__dirname, './fixtures/script-setup/pages/about.vue'),
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
-    })
-
-    test('dynami route', async () => {
-      const options = getNuxtOptions({}, true)
-      const pages: NuxtPage[] = [
-        {
-          name: 'articles-name',
-          path: '/articles/:name',
-          file: resolve(__dirname, './fixtures/dynamic/pages/articles/[name].vue'),
-          children: []
-        }
-      ]
-      const localizedPages = localizeRoutes(pages, {
-        ...options,
-        includeUprefixedFallback: false,
-        optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
-      })
-      expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
-    })
-  })
-})
diff --git a/test/pages/__snapshots__/basic.test.ts.snap b/test/pages/__snapshots__/basic.test.ts.snap
new file mode 100644
index 00000000..ca8df95b
--- /dev/null
+++ b/test/pages/__snapshots__/basic.test.ts.snap
@@ -0,0 +1,60 @@
+// 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/__snapshots__/pages.test.ts.snap b/test/pages/__snapshots__/custom_route.test.ts.snap
similarity index 78%
rename from test/__snapshots__/pages.test.ts.snap
rename to test/pages/__snapshots__/custom_route.test.ts.snap
index f0ee3080..fd124940 100644
--- a/test/__snapshots__/pages.test.ts.snap
+++ b/test/pages/__snapshots__/custom_route.test.ts.snap
@@ -1,65 +1,6 @@
 // Vitest Snapshot v1
 
-exports[`basic 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",
-  },
-]
-`;
-
-exports[`custom route > component > dynami route 1`] = `
+exports[`component > dynamic route 1`] = `
 [
   {
     "children": [],
@@ -79,24 +20,7 @@ exports[`custom route > component > dynami route 1`] = `
 ]
 `;
 
-exports[`custom route > component > script-setup 1`] = `
-[
-  {
-    "children": [],
-    "path": "/about-us",
-  },
-  {
-    "children": [],
-    "path": "/ja/about-ja",
-  },
-  {
-    "children": [],
-    "path": "/fr/a-propos",
-  },
-]
-`;
-
-exports[`custom route > component > simple 1`] = `
+exports[`component > simple 1`] = `
 [
   {
     "children": [],
@@ -113,7 +37,7 @@ exports[`custom route > component > simple 1`] = `
 ]
 `;
 
-exports[`custom route > configuration > nested complex route 1`] = `
+exports[`configuration > nested complex route 1`] = `
 [
   {
     "children": [],
@@ -226,7 +150,7 @@ exports[`custom route > configuration > nested complex route 1`] = `
 ]
 `;
 
-exports[`custom route > configuration > nested dynamic route 1`] = `
+exports[`configuration > nested dynamic route 1`] = `
 [
   {
     "children": [],
@@ -267,7 +191,7 @@ exports[`custom route > configuration > nested dynamic route 1`] = `
 ]
 `;
 
-exports[`custom route > configuration > nested static route 1`] = `
+exports[`configuration > nested static route 1`] = `
 [
   {
     "children": [],
@@ -290,7 +214,7 @@ exports[`custom route > configuration > nested static route 1`] = `
 ]
 `;
 
-exports[`custom route > configuration > simple 1`] = `
+exports[`configuration > simple 1`] = `
 [
   {
     "children": [
diff --git a/test/pages/basic.test.ts b/test/pages/basic.test.ts
new file mode 100644
index 00000000..517de977
--- /dev/null
+++ b/test/pages/basic.test.ts
@@ -0,0 +1,40 @@
+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<NuxtI18nOptions>)
+  })
+  expect(localizedPages).toMatchSnapshot()
+})
diff --git a/test/pages/custom_route.test.ts b/test/pages/custom_route.test.ts
new file mode 100644
index 00000000..b5f14977
--- /dev/null
+++ b/test/pages/custom_route.test.ts
@@ -0,0 +1,186 @@
+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 { getNuxtOptions, stripFilePropertyFromPages } from './utils'
+
+import type { NuxtI18nOptions } from '../../src/types'
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
+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(.*)*'
+      }
+    }),
+    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',
+        children: []
+      }
+    ]
+  },
+  {
+    case: 'nested complex route',
+    options: getNuxtOptions({
+      about: {
+        fr: '/a-propos'
+      },
+      services: {
+        fr: '/offres'
+      },
+      'services/development': {
+        fr: '/offres/developement'
+      },
+      'services/development/app': {
+        fr: '/offres/developement/app'
+      },
+      'services/development/website': {
+        fr: '/offres/developement/site-web'
+      },
+      'services/coaching': {
+        fr: '/offres/formation'
+      }
+    }),
+    pages: [
+      {
+        name: 'about',
+        path: '/about',
+        file: '/path/to/nuxt-app/pages/about/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-app',
+        path: '/services/development/app',
+        file: '/path/to/nuxt-app/pages/services/development/app/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-coaching',
+        path: '/services/development/coaching',
+        file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development',
+        path: '/services/development',
+        file: '/path/to/nuxt-app/pages/services/development/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-website',
+        path: '/services/development/website',
+        file: '/path/to/nuxt-app/pages/services/development/website/index.vue',
+        children: []
+      },
+      {
+        name: 'services',
+        path: '/services',
+        file: '/path/to/nuxt-app/pages/services/index.vue',
+        children: []
+      }
+    ]
+  }
+])('configuration', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    vi.spyOn(fs, 'readFileSync').mockReturnValue('')
+
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(localizedPages).toMatchSnapshot()
+  })
+})
+
+describe.each([
+  {
+    case: 'simple',
+    options: getNuxtOptions({}, true),
+    pages: [
+      {
+        path: '/about',
+        file: resolve(__dirname, '../fixtures/custom_route/simple/pages/about.vue'),
+        children: []
+      }
+    ]
+  },
+  {
+    case: 'dynamic route',
+    options: getNuxtOptions({}, true),
+    pages: [
+      {
+        name: 'articles-name',
+        path: '/articles/:name',
+        file: resolve(__dirname, '../fixtures/custom_route/dynamic/pages/articles/[name].vue'),
+        children: []
+      }
+    ]
+  }
+])('component', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
+  })
+})
diff --git a/test/pages/ignore_route/__snapshots__/disable.test.ts.snap b/test/pages/ignore_route/__snapshots__/disable.test.ts.snap
new file mode 100644
index 00000000..1330c5ba
--- /dev/null
+++ b/test/pages/ignore_route/__snapshots__/disable.test.ts.snap
@@ -0,0 +1,126 @@
+// Vitest Snapshot v1
+
+exports[`component > basic 1`] = `
+[
+  {
+    "children": [],
+    "path": "/about",
+  },
+]
+`;
+
+exports[`configuration > nested complex route 1`] = `
+[
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/about/index.vue",
+    "name": "about",
+    "path": "/about",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/development/app/index.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",
+    "name": "services-development",
+    "path": "/services/development",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/development/website/index.vue",
+    "name": "services-development-website",
+    "path": "/services/development/website",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/index.vue",
+    "name": "services",
+    "path": "/services",
+  },
+]
+`;
+
+exports[`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",
+    "name": "nested-route",
+    "path": "/:nested/:route",
+  },
+]
+`;
+
+exports[`configuration > nested static route 1`] = `
+[
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/nested/route/index.vue",
+    "name": "nested-route",
+    "path": "/nested/route",
+  },
+]
+`;
+
+exports[`configuration > simple 1`] = `
+[
+  {
+    "children": [
+      {
+        "children": [],
+        "file": "/path/to/nuxt-app/pages/about/index.vue",
+        "name": "about",
+        "path": "",
+      },
+    ],
+    "file": "/path/to/nuxt-app/pages/about.vue",
+    "path": "/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/ignore_route/__snapshots__/pick.test.ts.snap b/test/pages/ignore_route/__snapshots__/pick.test.ts.snap
new file mode 100644
index 00000000..307aa934
--- /dev/null
+++ b/test/pages/ignore_route/__snapshots__/pick.test.ts.snap
@@ -0,0 +1,200 @@
+// Vitest Snapshot v1
+
+exports[`component > dynamic route 1`] = `
+[
+  {
+    "children": [],
+    "name": "articles-name___fr",
+    "path": "/fr/articles/:name",
+  },
+]
+`;
+
+exports[`component > simple 1`] = `
+[
+  {
+    "children": [],
+    "path": "/ja/about",
+  },
+  {
+    "children": [],
+    "path": "/fr/about",
+  },
+]
+`;
+
+exports[`configuration > nested complex route 1`] = `
+[
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/about/index.vue",
+    "name": "about___en",
+    "path": "/about",
+  },
+  {
+    "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",
+    "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",
+    "name": "services-development___en",
+    "path": "/services/development",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/development/index.vue",
+    "name": "services-development___ja",
+    "path": "/ja/services/development",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/development/website/index.vue",
+    "name": "services-development-website___en",
+    "path": "/services/development/website",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/development/website/index.vue",
+    "name": "services-development-website___ja",
+    "path": "/ja/services/development/website",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/services/index.vue",
+    "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`] = `
+[
+  {
+    "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",
+    "name": "nested-route___ja",
+    "path": "/ja/:nested/:route",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/[nested]/[route]/index.vue",
+    "name": "nested-route___fr",
+    "path": "/fr/:nested/:route",
+  },
+]
+`;
+
+exports[`configuration > nested static route 1`] = `
+[
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/nested/route/index.vue",
+    "name": "nested-route___ja",
+    "path": "/ja/nested/route",
+  },
+  {
+    "children": [],
+    "file": "/path/to/nuxt-app/pages/nested/route/index.vue",
+    "name": "nested-route___fr",
+    "path": "/fr/nested/route",
+  },
+]
+`;
+
+exports[`configuration > simple 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___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/ignore_route/disable.test.ts b/test/pages/ignore_route/disable.test.ts
new file mode 100644
index 00000000..0f9455ec
--- /dev/null
+++ b/test/pages/ignore_route/disable.test.ts
@@ -0,0 +1,156 @@
+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 { getNuxtOptions, stripFilePropertyFromPages } from '../utils'
+
+import type { NuxtI18nOptions } from '../../../src/types'
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
+describe.each([
+  {
+    case: 'simple',
+    options: getNuxtOptions({ about: false }),
+    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': false
+    }),
+    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': false,
+      ':nested/:route/:slug(.*)*': 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',
+        file: '/path/to/nuxt-app/pages/[nested]/[route]/index.vue',
+        children: []
+      }
+    ]
+  },
+  {
+    case: 'nested complex route',
+    options: getNuxtOptions({
+      about: false,
+      services: false,
+      'services/development': 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',
+        children: []
+      },
+      {
+        name: 'services-development-app',
+        path: '/services/development/app',
+        file: '/path/to/nuxt-app/pages/services/development/app/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-coaching',
+        path: '/services/development/coaching',
+        file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development',
+        path: '/services/development',
+        file: '/path/to/nuxt-app/pages/services/development/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-website',
+        path: '/services/development/website',
+        file: '/path/to/nuxt-app/pages/services/development/website/index.vue',
+        children: []
+      },
+      {
+        name: 'services',
+        path: '/services',
+        file: '/path/to/nuxt-app/pages/services/index.vue',
+        children: []
+      }
+    ]
+  }
+])('configuration', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    vi.spyOn(fs, 'readFileSync').mockReturnValue('')
+
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(localizedPages).toMatchSnapshot()
+  })
+})
+
+describe.each([
+  {
+    case: 'basic',
+    options: getNuxtOptions({}, true),
+    pages: [
+      {
+        path: '/about',
+        file: resolve(__dirname, '../../fixtures/ignore_route/disable/pages/about.vue'),
+        children: []
+      }
+    ]
+  }
+])('component', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
+  })
+})
diff --git a/test/pages/ignore_route/pick.test.ts b/test/pages/ignore_route/pick.test.ts
new file mode 100644
index 00000000..0ad19432
--- /dev/null
+++ b/test/pages/ignore_route/pick.test.ts
@@ -0,0 +1,186 @@
+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 { getNuxtOptions, stripFilePropertyFromPages } from '../utils'
+
+import type { NuxtI18nOptions } from '../../../src/types'
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
+describe.each([
+  {
+    case: 'simple',
+    options: getNuxtOptions({ about: { ja: false } }),
+    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: false
+      }
+    }),
+    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: false
+      },
+      ':nested/:route/:slug(.*)*': {
+        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',
+        file: '/path/to/nuxt-app/pages/[nested]/[route]/index.vue',
+        children: []
+      }
+    ]
+  },
+  {
+    case: 'nested complex route',
+    options: getNuxtOptions({
+      about: {
+        fr: false
+      },
+      services: {
+        fr: false
+      },
+      'services/development': {
+        fr: false
+      },
+      'services/development/app': {
+        fr: false
+      },
+      'services/development/website': {
+        fr: false
+      },
+      'services/coaching': {
+        fr: false
+      }
+    }),
+    pages: [
+      {
+        name: 'about',
+        path: '/about',
+        file: '/path/to/nuxt-app/pages/about/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-app',
+        path: '/services/development/app',
+        file: '/path/to/nuxt-app/pages/services/development/app/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-coaching',
+        path: '/services/development/coaching',
+        file: '/path/to/nuxt-app/pages/services/development/coaching/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development',
+        path: '/services/development',
+        file: '/path/to/nuxt-app/pages/services/development/index.vue',
+        children: []
+      },
+      {
+        name: 'services-development-website',
+        path: '/services/development/website',
+        file: '/path/to/nuxt-app/pages/services/development/website/index.vue',
+        children: []
+      },
+      {
+        name: 'services',
+        path: '/services',
+        file: '/path/to/nuxt-app/pages/services/index.vue',
+        children: []
+      }
+    ]
+  }
+])('configuration', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    vi.spyOn(fs, 'readFileSync').mockReturnValue('')
+
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(localizedPages).toMatchSnapshot()
+  })
+})
+
+describe.each([
+  {
+    case: 'simple',
+    options: getNuxtOptions({}, true),
+    pages: [
+      {
+        path: '/about',
+        file: resolve(__dirname, '../../fixtures/ignore_route/pick/simple/pages/about.vue'),
+        children: []
+      }
+    ]
+  },
+  {
+    case: 'dynamic route',
+    options: getNuxtOptions({}, true),
+    pages: [
+      {
+        name: 'articles-name',
+        path: '/articles/:name',
+        file: resolve(__dirname, '../../fixtures/ignore_route/pick/dynamic/pages/articles/[name].vue'),
+        children: []
+      }
+    ]
+  }
+])('component', ({ case: _case, options, pages }) => {
+  test(_case, async () => {
+    const localizedPages = localizeRoutes(pages, {
+      ...options,
+      includeUprefixedFallback: false,
+      optionsResolver: getRouteOptionsResolver('pages', options as Required<NuxtI18nOptions>)
+    })
+    expect(stripFilePropertyFromPages(localizedPages)).toMatchSnapshot()
+  })
+})
diff --git a/test/pages/utils.ts b/test/pages/utils.ts
new file mode 100644
index 00000000..da0b1213
--- /dev/null
+++ b/test/pages/utils.ts
@@ -0,0 +1,33 @@
+import type { NuxtI18nOptions } from '../../src/types'
+import type { I18nRoute } from 'vue-i18n-routing'
+import type { NuxtHooks } from '@nuxt/schema'
+
+type ExtractArrayType<T> = T extends (infer U)[] ? U : never
+export type NuxtPage = ExtractArrayType<Parameters<NuxtHooks['pages:extend']>[0]>
+
+export function getNuxtOptions(pages: Required<NuxtI18nOptions>['pages'], parsePages = false): NuxtI18nOptions {
+  return {
+    parsePages,
+    pages,
+    defaultLocale: 'en',
+    strategy: 'prefix_except_default',
+    defaultLocaleRouteNameSuffix: 'default',
+    trailingSlash: false,
+    routesNameSeparator: '___',
+    locales: [
+      { code: 'en', iso: 'en-US', file: 'en.json', name: 'English' },
+      { code: 'ja', iso: 'ja-JP', file: 'ja.json', name: 'Japanses' },
+      { code: 'fr', iso: 'fr-FR', file: 'fr.json', name: 'Français' }
+    ]
+  }
+}
+
+export function stripFilePropertyFromPages(pages: I18nRoute[]) {
+  return pages.map(page => {
+    delete page.file
+    if (page.children) {
+      page.children = stripFilePropertyFromPages(page.children)
+    }
+    return page
+  })
+}