From 1ab8688509cb523713fca687f55fd9e145146ac1 Mon Sep 17 00:00:00 2001 From: Rigo-m Date: Wed, 16 Oct 2024 17:10:24 +0200 Subject: [PATCH] feat: custom locale pattern matcher --- docs/guide/getting-started.md | 14 + package-lock.json | 4 +- playground/locales/de-de.json | 1 + playground/locales/en-us.json | 1 + .../locales/pages/articles-id/de-de.json | 1 + .../locales/pages/articles-id/en-us.json | 1 + .../locales/pages/articles-id/ru-ru.json | 1 + .../locales/pages/dir1-subdir/de-de.json | 1 + .../locales/pages/dir1-subdir/en-us.json | 1 + .../locales/pages/dir1-subdir/ru-ru.json | 1 + playground/locales/pages/dir1/de-de.json | 1 + playground/locales/pages/dir1/en-us.json | 1 + playground/locales/pages/dir1/ru-ru.json | 1 + playground/locales/pages/dir2-slug/de-de.json | 1 + playground/locales/pages/dir2-slug/en-us.json | 1 + playground/locales/pages/dir2-slug/ru-ru.json | 1 + playground/locales/pages/index/de-de.json | 1 + playground/locales/pages/index/en-us.json | 1 + playground/locales/pages/index/ru-ru.json | 1 + playground/locales/pages/news-id/de-de.json | 1 + playground/locales/pages/news-id/en-us.json | 1 + playground/locales/pages/news-id/ru-ru.json | 1 + playground/locales/pages/page/de-de.json | 1 + playground/locales/pages/page/en-us.json | 1 + playground/locales/pages/page/ru-ru.json | 1 + playground/locales/pages/subpage/de-de.json | 1 + playground/locales/pages/subpage/en-us.json | 1 + playground/locales/pages/subpage/ru-ru.json | 1 + .../locales/pages/unlocalized/de-de.json | 1 + .../locales/pages/unlocalized/en-us.json | 1 + .../locales/pages/unlocalized/ru-ru.json | 1 + playground/locales/ru-ru.json | 1 + src/module.ts | 12 +- src/page-manager.ts | 23 +- src/runtime/server/middleware/i18n-loader.ts | 10 +- src/types.ts | 1 + src/utils.ts | 10 +- test/custom-regex.test.ts | 386 ++++++++++++++++++ test/fixtures/custom-regex-fail/app.vue | 12 + .../custom-regex-fail/locales/de.json | 5 + .../custom-regex-fail/locales/en.json | 6 + .../fixtures/custom-regex-fail/nuxt.config.ts | 18 + test/fixtures/custom-regex-fail/package.json | 10 + .../custom-regex-fail/pages/index.vue | 18 + test/fixtures/custom-regex/locales/de-de.json | 6 + test/fixtures/custom-regex/locales/en-us.json | 7 + .../locales/pages/activity-locale/de-de.json | 1 + .../locales/pages/activity-locale/en-us.json | 1 + .../locales/pages/activity-locale/ru-ru.json | 1 + .../locales/pages/activity/de-de.json | 1 + .../locales/pages/activity/en-us.json | 1 + .../locales/pages/activity/ru-ru.json | 1 + .../locales/pages/articles-id/de-de.json | 1 + .../locales/pages/articles-id/en-us.json | 1 + .../locales/pages/articles-id/ru-ru.json | 1 + .../locales/pages/index/de-de.json | 4 + .../locales/pages/index/en-us.json | 6 + .../locales/pages/index/ru-ru.json | 4 + .../locales/pages/locale-conf/de-de.json | 1 + .../locales/pages/locale-conf/en-us.json | 1 + .../locales/pages/locale-conf/ru-ru.json | 1 + .../locales/pages/locale-test/de-de.json | 1 + .../locales/pages/locale-test/en-us.json | 1 + .../locales/pages/locale-test/ru-ru.json | 1 + .../locales/pages/news-id/de-de.json | 1 + .../locales/pages/news-id/en-us.json | 1 + .../locales/pages/news-id/ru-ru.json | 1 + .../locales/pages/page/de-de.json | 8 + .../locales/pages/page/en-us.json | 8 + .../locales/pages/page/ru-ru.json | 7 + .../locales/pages/page2/de-de.json | 1 + .../locales/pages/page2/en-us.json | 1 + .../locales/pages/page2/ru-ru.json | 1 + .../locales/pages/unlocalized/de-de.json | 1 + .../locales/pages/unlocalized/en-us.json | 1 + .../locales/pages/unlocalized/ru-ru.json | 1 + test/fixtures/custom-regex/locales/ru-ru.json | 3 + test/fixtures/custom-regex/nuxt.config.ts | 33 ++ test/fixtures/custom-regex/package.json | 10 + .../custom-regex/pages/activity-locale.vue | 35 ++ .../pages/activity-locale/hiking.vue | 14 + .../pages/activity-locale/skiing.vue | 21 + test/fixtures/custom-regex/pages/activity.vue | 48 +++ .../pages/activity/hiking-locale.vue | 21 + .../custom-regex/pages/activity/hiking.vue | 11 + .../pages/activity/skiing-locale.vue | 21 + .../custom-regex/pages/activity/skiing.vue | 14 + .../custom-regex/pages/articles/[id].vue | 56 +++ .../custom-regex/pages/dir1/[slug].vue | 11 + test/fixtures/custom-regex/pages/index.vue | 16 + .../custom-regex/pages/locale-conf.vue | 83 ++++ .../custom-regex/pages/locale-test.vue | 76 ++++ .../fixtures/custom-regex/pages/news/[id].vue | 95 +++++ test/fixtures/custom-regex/pages/page.vue | 81 ++++ test/fixtures/custom-regex/pages/page2.vue | 81 ++++ .../custom-regex/pages/unlocalized.vue | 81 ++++ .../custom-regex/server/api/getArticles.js | 8 + .../custom-regex/server/api/getNews.js | 25 ++ .../custom-regex/server/tsconfig.json | 3 + 99 files changed, 1464 insertions(+), 16 deletions(-) create mode 100644 playground/locales/de-de.json create mode 100644 playground/locales/en-us.json create mode 100644 playground/locales/pages/articles-id/de-de.json create mode 100644 playground/locales/pages/articles-id/en-us.json create mode 100644 playground/locales/pages/articles-id/ru-ru.json create mode 100644 playground/locales/pages/dir1-subdir/de-de.json create mode 100644 playground/locales/pages/dir1-subdir/en-us.json create mode 100644 playground/locales/pages/dir1-subdir/ru-ru.json create mode 100644 playground/locales/pages/dir1/de-de.json create mode 100644 playground/locales/pages/dir1/en-us.json create mode 100644 playground/locales/pages/dir1/ru-ru.json create mode 100644 playground/locales/pages/dir2-slug/de-de.json create mode 100644 playground/locales/pages/dir2-slug/en-us.json create mode 100644 playground/locales/pages/dir2-slug/ru-ru.json create mode 100644 playground/locales/pages/index/de-de.json create mode 100644 playground/locales/pages/index/en-us.json create mode 100644 playground/locales/pages/index/ru-ru.json create mode 100644 playground/locales/pages/news-id/de-de.json create mode 100644 playground/locales/pages/news-id/en-us.json create mode 100644 playground/locales/pages/news-id/ru-ru.json create mode 100644 playground/locales/pages/page/de-de.json create mode 100644 playground/locales/pages/page/en-us.json create mode 100644 playground/locales/pages/page/ru-ru.json create mode 100644 playground/locales/pages/subpage/de-de.json create mode 100644 playground/locales/pages/subpage/en-us.json create mode 100644 playground/locales/pages/subpage/ru-ru.json create mode 100644 playground/locales/pages/unlocalized/de-de.json create mode 100644 playground/locales/pages/unlocalized/en-us.json create mode 100644 playground/locales/pages/unlocalized/ru-ru.json create mode 100644 playground/locales/ru-ru.json create mode 100644 test/custom-regex.test.ts create mode 100755 test/fixtures/custom-regex-fail/app.vue create mode 100755 test/fixtures/custom-regex-fail/locales/de.json create mode 100755 test/fixtures/custom-regex-fail/locales/en.json create mode 100644 test/fixtures/custom-regex-fail/nuxt.config.ts create mode 100644 test/fixtures/custom-regex-fail/package.json create mode 100755 test/fixtures/custom-regex-fail/pages/index.vue create mode 100644 test/fixtures/custom-regex/locales/de-de.json create mode 100644 test/fixtures/custom-regex/locales/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity-locale/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity-locale/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity-locale/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/activity/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/articles-id/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/articles-id/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/articles-id/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/index/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/index/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/index/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-conf/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-conf/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-conf/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-test/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-test/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/locale-test/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/news-id/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/news-id/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/news-id/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/page/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/page/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/page/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/page2/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/page2/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/page2/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/pages/unlocalized/de-de.json create mode 100644 test/fixtures/custom-regex/locales/pages/unlocalized/en-us.json create mode 100644 test/fixtures/custom-regex/locales/pages/unlocalized/ru-ru.json create mode 100644 test/fixtures/custom-regex/locales/ru-ru.json create mode 100644 test/fixtures/custom-regex/nuxt.config.ts create mode 100644 test/fixtures/custom-regex/package.json create mode 100755 test/fixtures/custom-regex/pages/activity-locale.vue create mode 100755 test/fixtures/custom-regex/pages/activity-locale/hiking.vue create mode 100755 test/fixtures/custom-regex/pages/activity-locale/skiing.vue create mode 100755 test/fixtures/custom-regex/pages/activity.vue create mode 100755 test/fixtures/custom-regex/pages/activity/hiking-locale.vue create mode 100755 test/fixtures/custom-regex/pages/activity/hiking.vue create mode 100755 test/fixtures/custom-regex/pages/activity/skiing-locale.vue create mode 100755 test/fixtures/custom-regex/pages/activity/skiing.vue create mode 100644 test/fixtures/custom-regex/pages/articles/[id].vue create mode 100644 test/fixtures/custom-regex/pages/dir1/[slug].vue create mode 100644 test/fixtures/custom-regex/pages/index.vue create mode 100644 test/fixtures/custom-regex/pages/locale-conf.vue create mode 100644 test/fixtures/custom-regex/pages/locale-test.vue create mode 100644 test/fixtures/custom-regex/pages/news/[id].vue create mode 100644 test/fixtures/custom-regex/pages/page.vue create mode 100644 test/fixtures/custom-regex/pages/page2.vue create mode 100644 test/fixtures/custom-regex/pages/unlocalized.vue create mode 100644 test/fixtures/custom-regex/server/api/getArticles.js create mode 100644 test/fixtures/custom-regex/server/api/getNews.js create mode 100644 test/fixtures/custom-regex/server/tsconfig.json diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 3d55748d..6a5cd5d9 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -215,6 +215,20 @@ Automatically redirects routes without a locale prefix to the default locale. includeDefaultLocaleRoute: true // Ensure consistency across routes by redirecting to the default locale ``` +### 🚦 `customRegexMatcher` + +I18n-micro meticulously checks each locale via vue-router route regex. +If you have **a lot** of locales, you can improve pattern matching performances via a custom regex matcher. + +**Type**: `string | RegExp` +**Default**: `false` + +**Example**: + +```typescript +customRegexMatcher: '[a-z]-[A-Z]'// This matches locales in isoCode (e.g: '/en-US', 'de-DE' etc) +``` + ### 🔗 `routesLocaleLinks` Creates links between different pages' locale files to share translations, reducing duplication. diff --git a/package-lock.json b/package-lock.json index aa1465a9..02c54806 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nuxt-i18n-micro", - "version": "1.21.5", + "version": "1.23.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nuxt-i18n-micro", - "version": "1.21.5", + "version": "1.23.1", "license": "MIT", "workspaces": [ "client", diff --git a/playground/locales/de-de.json b/playground/locales/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/en-us.json b/playground/locales/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/articles-id/de-de.json b/playground/locales/pages/articles-id/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/articles-id/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/articles-id/en-us.json b/playground/locales/pages/articles-id/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/articles-id/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/articles-id/ru-ru.json b/playground/locales/pages/articles-id/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/articles-id/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1-subdir/de-de.json b/playground/locales/pages/dir1-subdir/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1-subdir/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1-subdir/en-us.json b/playground/locales/pages/dir1-subdir/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1-subdir/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1-subdir/ru-ru.json b/playground/locales/pages/dir1-subdir/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1-subdir/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1/de-de.json b/playground/locales/pages/dir1/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1/en-us.json b/playground/locales/pages/dir1/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir1/ru-ru.json b/playground/locales/pages/dir1/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir1/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir2-slug/de-de.json b/playground/locales/pages/dir2-slug/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir2-slug/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir2-slug/en-us.json b/playground/locales/pages/dir2-slug/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir2-slug/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/dir2-slug/ru-ru.json b/playground/locales/pages/dir2-slug/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/dir2-slug/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/index/de-de.json b/playground/locales/pages/index/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/index/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/index/en-us.json b/playground/locales/pages/index/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/index/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/index/ru-ru.json b/playground/locales/pages/index/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/index/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/news-id/de-de.json b/playground/locales/pages/news-id/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/news-id/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/news-id/en-us.json b/playground/locales/pages/news-id/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/news-id/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/news-id/ru-ru.json b/playground/locales/pages/news-id/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/news-id/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/page/de-de.json b/playground/locales/pages/page/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/page/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/page/en-us.json b/playground/locales/pages/page/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/page/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/page/ru-ru.json b/playground/locales/pages/page/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/page/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/subpage/de-de.json b/playground/locales/pages/subpage/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/subpage/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/subpage/en-us.json b/playground/locales/pages/subpage/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/subpage/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/subpage/ru-ru.json b/playground/locales/pages/subpage/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/subpage/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/unlocalized/de-de.json b/playground/locales/pages/unlocalized/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/unlocalized/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/unlocalized/en-us.json b/playground/locales/pages/unlocalized/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/unlocalized/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/pages/unlocalized/ru-ru.json b/playground/locales/pages/unlocalized/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/pages/unlocalized/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/playground/locales/ru-ru.json b/playground/locales/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/playground/locales/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/module.ts b/src/module.ts index 88dbdac1..62f14a75 100644 --- a/src/module.ts +++ b/src/module.ts @@ -68,6 +68,7 @@ export default defineNuxtModule({ } return (forms.length > 2 ? forms[2].trim() : forms[forms.length - 1].trim()).replace('{count}', count.toString()) }, + customRegexMatcher: undefined, }, async setup(options, nuxt) { const isCloudflarePages = nuxt.options.nitro.preset === 'cloudflare_pages' || process.env.NITRO_PRESET === 'cloudflare-pages' @@ -104,6 +105,15 @@ export default defineNuxtModule({ hashMode: nuxt.options?.router?.options?.hashMode ?? false, globalLocaleRoutes: undefined, apiBaseUrl: apiBaseUrl, + customRegexMatcher: options.customRegexMatcher, + } + + // if there is a customRegexMatcher set and all locales don't match the custom matcher, throw error + if (typeof options.customRegexMatcher !== 'undefined') { + const localeCodes = localeManager.locales.map(l => l.code) + if (!localeCodes.every(code => code.match(options.customRegexMatcher as string | RegExp))) { + throw new Error('Nuxt-18n-micro: Some locale codes does not match customRegexMatcher') + } } nuxt.options.runtimeConfig.i18nConfig = { rootDir: nuxt.options.rootDir, @@ -159,7 +169,7 @@ export default defineNuxtModule({ localeManager.ensureTranslationFilesExist(pagesNames, options.translationDir!, nuxt.options.rootDir) } - pageManager.extendPages(pages, nuxt.options.rootDir) + pageManager.extendPages(pages, nuxt.options.rootDir, options.customRegexMatcher) nuxt.options.generate.routes = Array.isArray(nuxt.options.generate.routes) ? nuxt.options.generate.routes : [] diff --git a/src/page-manager.ts b/src/page-manager.ts index 7381d666..032a4786 100644 --- a/src/page-manager.ts +++ b/src/page-manager.ts @@ -41,7 +41,7 @@ export class PageManager { .map(locale => locale.code) } - public extendPages(pages: NuxtPage[], rootDir: string) { + public extendPages(pages: NuxtPage[], rootDir: string, customRegex?: string | RegExp) { this.localizedPaths = this.extractLocalizedPaths(pages, rootDir) const additionalRoutes: NuxtPage[] = [] @@ -56,11 +56,11 @@ export class PageManager { // Check if the page has custom routes in globalLocaleRoutes if (customRoute && typeof customRoute === 'object') { // Add routes based on custom globalLocaleRoutes - this.addCustomGlobalLocalizedRoutes(page, customRoute, additionalRoutes) + this.addCustomGlobalLocalizedRoutes(page, customRoute, additionalRoutes, customRegex) } else { // Default behavior: localize the page as usual - this.localizePage(page, additionalRoutes) + this.localizePage(page, additionalRoutes, customRegex) } }) @@ -110,6 +110,7 @@ export class PageManager { page: NuxtPage, customRoutePaths: Record, additionalRoutes: NuxtPage[], + customRegex?: string | RegExp, ) { this.locales.forEach((locale) => { const customPath = customRoutePaths[locale.code] @@ -122,7 +123,7 @@ export class PageManager { } else { // Create a new localized route for this locale - additionalRoutes.push(this.createLocalizedRoute(page, [locale.code], page.children ?? [], true, customPath)) + additionalRoutes.push(this.createLocalizedRoute(page, [locale.code], page.children ?? [], true, customPath, customRegex)) } }) } @@ -130,6 +131,7 @@ export class PageManager { private localizePage( page: NuxtPage, additionalRoutes: NuxtPage[], + customRegex?: string | RegExp, ) { if (isPageRedirectOnly(page)) return @@ -138,7 +140,7 @@ export class PageManager { const localeCodesWithoutCustomPaths = this.filterLocaleCodesWithoutCustomPaths(normalizedFullPath) if (localeCodesWithoutCustomPaths.length) { - additionalRoutes.push(this.createLocalizedRoute(page, localeCodesWithoutCustomPaths, originalChildren, false)) + additionalRoutes.push(this.createLocalizedRoute(page, localeCodesWithoutCustomPaths, originalChildren, false, '', customRegex)) } this.addCustomLocalizedRoutes(page, normalizedFullPath, originalChildren, additionalRoutes) @@ -198,6 +200,7 @@ export class PageManager { fullPath: string, originalChildren: NuxtPage[], additionalRoutes: NuxtPage[], + customRegex?: string | RegExp, ) { this.locales.forEach((locale) => { const customPath = this.localizedPaths[fullPath]?.[locale.code] @@ -208,7 +211,7 @@ export class PageManager { page.children = this.createLocalizedChildren(originalChildren, '', [locale.code], false) } else { - additionalRoutes.push(this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath)) + additionalRoutes.push(this.createLocalizedRoute(page, [locale.code], originalChildren, true, customPath, customRegex)) } }) } @@ -244,8 +247,9 @@ export class PageManager { originalChildren: NuxtPage[], isCustom: boolean, customPath: string = '', + customRegex?: string | RegExp, ): NuxtPage { - const routePath = this.buildRoutePath(localeCodes, page.path, customPath, isCustom) + const routePath = this.buildRoutePath(localeCodes, page.path, customPath, isCustom, customRegex) const routeName = buildRouteName(page.name ?? '', localeCodes[0], isCustom) return { @@ -299,12 +303,13 @@ export class PageManager { originalPath: string, customPath: string, isCustom: boolean, + customRegex?: string | RegExp, ): string { if (isCustom) { return (this.includeDefaultLocaleRoute || !localeCodes.includes(this.defaultLocale.code)) - ? buildFullPath(localeCodes, customPath) + ? buildFullPath(localeCodes, customPath, customRegex) : normalizePath(customPath) } - return buildFullPath(localeCodes, originalPath) + return buildFullPath(localeCodes, originalPath, customRegex) } } diff --git a/src/runtime/server/middleware/i18n-loader.ts b/src/runtime/server/middleware/i18n-loader.ts index 15a977dd..b2febdc1 100644 --- a/src/runtime/server/middleware/i18n-loader.ts +++ b/src/runtime/server/middleware/i18n-loader.ts @@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises' import { defineEventHandler } from 'h3' import type { ModuleOptionsExtend, ModulePrivateOptionsExtend } from '../../../types' import type { Translations } from '../../plugins/01.plugin' -import { useRuntimeConfig } from '#imports' +import { useRuntimeConfig, createError } from '#imports' // Рекурсивная функция для глубокого слияния объектов function deepMerge(target: Translations, source: Translations): Translations { @@ -34,8 +34,14 @@ export default defineEventHandler(async (event) => { const { page, locale } = event.context.params as { page: string, locale: string } const config = useRuntimeConfig() const { rootDirs } = config.i18nConfig as ModulePrivateOptionsExtend - const { translationDir, fallbackLocale } = config.public.i18nConfig as ModuleOptionsExtend + const { translationDir, fallbackLocale, customRegexMatcher, locales } = config.public.i18nConfig as ModuleOptionsExtend + if (customRegexMatcher && locales && !locales.map(l => l.code).includes(locale)) { + // return 404 if route not matching route + throw createError({ + statusCode: 404, + }) + } const getTranslationPath = (locale: string, page: string) => { return page === 'general' ? `${locale}.json` : `pages/${page}/${locale}.json` } diff --git a/src/types.ts b/src/types.ts index 948fb678..5778a504 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,7 @@ export interface ModuleOptions { fallbackLocale?: string localeCookie?: string globalLocaleRoutes?: GlobalLocaleRoutes + customRegexMatcher?: string | RegExp } export interface ModuleOptionsExtend extends ModuleOptions { diff --git a/src/utils.ts b/src/utils.ts index 2b15941b..38b1cc57 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -58,7 +58,13 @@ export const isLocaleDefault = (locale: string | Locale, defaultLocale: Locale, return localeCode === defaultLocale.code && !includeDefaultLocaleRoute } -export const buildFullPath = (locale: string | string[], basePath: string): string => { - const localeParam = Array.isArray(locale) ? locale.join('|') : locale +export const buildFullPath = (locale: string | string[], basePath: string, customRegex?: string | RegExp): string => { + const regexString = normalizeRegex(customRegex?.toString()) + const localeParam = Array.isArray(locale) ? regexString ? regexString : locale.join('|') : locale return normalizePath(path.posix.join('/', `:locale(${localeParam})`, basePath)) } + +const normalizeRegex = (toNorm?: string): string | undefined => { + if (typeof toNorm === 'undefined') return undefined + return toNorm.startsWith('/') && toNorm.endsWith('/') ? toNorm?.slice(1, -1) : toNorm +} diff --git a/test/custom-regex.test.ts b/test/custom-regex.test.ts new file mode 100644 index 00000000..ad9e00e4 --- /dev/null +++ b/test/custom-regex.test.ts @@ -0,0 +1,386 @@ +import { fileURLToPath } from 'node:url' +import { expect, test } from '@nuxt/test-utils/playwright' + +test.use({ + nuxt: { + rootDir: fileURLToPath(new URL('./fixtures/custom-regex', import.meta.url)), + }, + // launchOptions: { + // headless: false, // Показывать браузер + // slowMo: 500, // Замедлить выполнение шагов (в миллисекундах) для лучшей видимости + // }, +}) + +test('test 404 on unknown locale', async ({ goto }) => { + const response = await goto('/un-kn', { waitUntil: 'networkidle' }) + expect(response?.status()).toBe(404) +}) + +test('test index', async ({ page, goto }) => { + await goto('/', { waitUntil: 'hydration' }) + await expect(page.locator('#locale')).toHaveText('en') + + await goto('/de-de', { waitUntil: 'hydration' }) + await expect(page.locator('#locale')).toHaveText('de') +}) + +test('test text escaping', async ({ page, goto }) => { + await goto('/', { waitUntil: 'hydration' }) + await expect(page.locator('.text_escaping')).toHaveText('test {text_escaping} } { { ') +}) + +test('test head', async ({ page, goto, baseURL }) => { + // Test for the default locale (English) + await goto('/', { waitUntil: 'hydration' }) + + const normalizedBaseURL = (baseURL || 'http://localhost:3000').replace(/\/$/, '') + + // Test meta tags for the default locale (English) + await expect(page.locator('meta#i18n-og')).toHaveAttribute('content', 'en_EN') + await expect(page.locator('meta#i18n-og-url')).toHaveAttribute('content', `${normalizedBaseURL}/`) + await expect(page.locator('meta#i18n-og-alt-de_DE')).toHaveAttribute('content', 'de_DE') + await expect(page.locator('meta#i18n-og-alt-ru_RU')).toHaveAttribute('content', 'ru_RU') + + await expect(page.locator('link#i18n-can')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-en-us')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-en_EN')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-de-de')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-de_DE')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-ru-ru')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + await expect(page.locator('link#i18n-alternate-ru_RU')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + + // Test for German locale + await goto('/de-de', { waitUntil: 'hydration' }) + + // Test meta tags for the German locale + await expect(page.locator('meta#i18n-og')).toHaveAttribute('content', 'de_DE') + await expect(page.locator('meta#i18n-og-url')).toHaveAttribute('content', `${normalizedBaseURL}/de-de`) + await expect(page.locator('meta#i18n-og-alt-en_EN')).toHaveAttribute('content', 'en_EN') + await expect(page.locator('meta#i18n-og-alt-ru_RU')).toHaveAttribute('content', 'ru_RU') + + await expect(page.locator('link#i18n-can')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-en-us')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-en_EN')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-de-de')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-de_DE')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-ru-ru')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + await expect(page.locator('link#i18n-alternate-ru_RU')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + + // Test for Russian locale + await goto('/ru-ru', { waitUntil: 'hydration' }) + + // Test meta tags for the Russian locale + await expect(page.locator('meta#i18n-og')).toHaveAttribute('content', 'ru_RU') + await expect(page.locator('meta#i18n-og-url')).toHaveAttribute('content', `${normalizedBaseURL}/ru-ru`) + await expect(page.locator('meta#i18n-og-alt-en_EN')).toHaveAttribute('content', 'en_EN') + await expect(page.locator('meta#i18n-og-alt-de_DE')).toHaveAttribute('content', 'de_DE') + + await expect(page.locator('link#i18n-can')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + await expect(page.locator('link#i18n-alternate-en-us')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-en_EN')).toHaveAttribute('href', `${normalizedBaseURL}/`) + await expect(page.locator('link#i18n-alternate-de-de')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-de_DE')).toHaveAttribute('href', `${normalizedBaseURL}/de-de`) + await expect(page.locator('link#i18n-alternate-ru-ru')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) + await expect(page.locator('link#i18n-alternate-ru_RU')).toHaveAttribute('href', `${normalizedBaseURL}/ru-ru`) +}) + +test('test links', async ({ page, goto }) => { + await goto('/dir1/test', { waitUntil: 'hydration' }) + await expect(page.locator('#test_link')).toHaveText('link in en') + + await goto('/de-de/dir1/test', { waitUntil: 'hydration' }) + await expect(page.locator('#test_link')).toHaveText('link in de') +}) + +test('test plugin methods output on page', async ({ page, goto }) => { + // Navigate to the /page route + await goto('/page', { waitUntil: 'hydration' }) + + // Verify the locale + await expect(page.locator('#locale')).toHaveText('Current Locale: en-us') + + // Verify the list of locales + await expect(page.locator('#locales')).toHaveText('en-us, de-de, ru-ru') + + // Verify the translation for a key + await expect(page.locator('#translation')).toHaveText('Page example in en') // Replace with actual expected content + + // Verify the pluralization for items + await expect(page.locator('#plural')).toHaveText('2 apples') // Replace with actual pluralization result + + await expect(page.locator('#plural-component')).toHaveText('5 apples') // Replace with actual pluralization result + await expect(page.locator('#plural-component-custom')).toHaveText('5 apples') // Replace with actual pluralization result + await expect(page.locator('#plural-component-custom-zero')).toHaveText('no apples') // Replace with actual pluralization result + + // Verify the localized route generation + await expect(page.locator('#localized-route')).toHaveText('/de-de/page') +}) + +test('test locale switching on page', async ({ page, goto }) => { + // Navigate to the /page route in English + await goto('/page', { waitUntil: 'hydration' }) + + await expect(page).toHaveURL('/page') + + await expect(page.locator('#locale')).toHaveText('Current Locale: en-us') + + // Verify the translation for a key after switching locale + await expect(page.locator('#translation')).toHaveText('Page example in en') // Replace with actual expected content + + // Verify the pluralization for items after switching locale + await expect(page.locator('#plural')).toHaveText('2 apples') // Replace with actual pluralization result in German + + // Verify the localized route generation after switching locale + await expect(page.locator('#localized-route')).toHaveText('/de-de/page') + + // Click the link to switch to the German locale + await page.click('#link-de') + + // Verify that the URL has changed + await expect(page).toHaveURL('/de-de/page') + + // Verify the locale after switching + await expect(page.locator('#locale')).toHaveText('Current Locale: de-de') + + // Verify the translation for a key after switching locale + await expect(page.locator('#translation')).toHaveText('Page example in de') // Replace with actual expected content + + // Verify the pluralization for items after switching locale + await expect(page.locator('#plural')).toHaveText('2 Äpfel') // Replace with actual pluralization result in German + + // Verify the localized route generation after switching locale + await expect(page.locator('#localized-route')).toHaveText('/de-de/page') +}) + +test('test locale switching on locale-test page', async ({ page }) => { + // Navigate to the /locale-test route in English + await page.goto('/locale-test', { waitUntil: 'networkidle' }) + + // Verify the URL and content in English + await expect(page).toHaveURL('/locale-test') + await expect(page.locator('h1')).toHaveText('Locale Test Page') + await expect(page.locator('#content')).toHaveText('This is a content area.') + await expect(page.locator('#username')).toHaveText('Hello, John!') + await expect(page.locator('#plural')).toHaveText('You have 2 items.') + await expect(page.locator('#html-content')).toHaveText('Bold Text with HTML content.') + + const linkDe = page.locator('#link-de') + await expect(linkDe).toHaveAttribute('href', '/de-de/locale-page-modify') + + // Switch to German locale + await linkDe.click() + + // Verify the URL and content in German + await expect(page).toHaveURL('/de-de/locale-page-modify') + await expect(page.locator('h1')).toHaveText('Sprachtestseite') + await expect(page.locator('#content')).toHaveText('Dies ist ein Inhaltsbereich.') + await expect(page.locator('#username')).toHaveText('Hallo, John!') + await expect(page.locator('#plural')).toHaveText('Sie haben 2 Artikel.') + await expect(page.locator('#html-content')).toHaveText('Fetter Text mit HTML-Inhalt.') +}) + +test('test locale switching via links', async ({ page, goto }) => { + await goto('/page', { waitUntil: 'hydration' }) + + await expect(page.locator('#locale')).toHaveText('Current Locale: en-us') + + await page.click('#link-de') + await expect(page).toHaveURL('/de-de/page') + await expect(page.locator('#locale')).toHaveText('Current Locale: de-de') + + await page.click('#link-en') + await expect(page).toHaveURL('/page') + await expect(page.locator('#locale')).toHaveText('Current Locale: en-us') +}) + +test('test localized content changes on navigation', async ({ page, goto }) => { + await goto('/locale-test', { waitUntil: 'hydration' }) + + await expect(page.locator('h1')).toHaveText('Locale Test Page') + await expect(page.locator('#content')).toHaveText('This is a content area.') + + await page.click('#link-de') + await expect(page).toHaveURL('/de-de/locale-page-modify') + await expect(page.locator('h1')).toHaveText('Sprachtestseite') + await expect(page.locator('#content')).toHaveText('Dies ist ein Inhaltsbereich.') +}) + +test('test translation features: pluralization and parameters', async ({ page, goto }) => { + await goto('/', { waitUntil: 'hydration' }) + await goto('/page', { waitUntil: 'hydration' }) + + await expect(page.locator('#plural')).toHaveText('2 apples') + + await page.click('#link-de') + await expect(page.locator('#plural')).toHaveText('2 Äpfel') + + await goto('/locale-test', { waitUntil: 'hydration' }) + await expect(page.locator('#username')).toHaveText('Hello, John!') + await page.click('#link-de') + await expect(page.locator('#username')).toHaveText('Hallo, John!') +}) + +test('test handling of missing locale data', async ({ page, goto }) => { + await goto('/', { waitUntil: 'hydration' }) + await goto('/ru-ru/page', { waitUntil: 'hydration' }) + + await expect(page.locator('#translation')).toHaveText('page.example') +}) + +test('Test globalLocaleRoutes for page2 and unlocalized', async ({ page, goto }) => { + // Test custom locale route for 'page2' in English + await goto('/custom-page2-en', { waitUntil: 'hydration' }) + + // Check that the custom route for English was applied and the content is correct + await expect(page).toHaveURL('/custom-page2-en') + + // Test custom locale route for 'page2' in German + await goto('/de-de/custom-page2-de', { waitUntil: 'hydration' }) + + // Check that the custom route for German was applied and the content is correct + await expect(page).toHaveURL('/de-de/custom-page2-de') + + // Test custom locale route for 'page2' in Russian + await goto('/ru-ru/custom-page2-ru', { waitUntil: 'hydration' }) + + // Check that the custom route for Russian was applied and the content is correct + await expect(page).toHaveURL('/ru-ru/custom-page2-ru') + + // Test that the 'unlocalized' page is not affected by localization + await goto('/unlocalized', { waitUntil: 'hydration' }) + + // Check that the unlocalized page remains the same and isn't localized + await expect(page).toHaveURL('/unlocalized') + + const response = await page.goto('/de-de/unlocalized', { waitUntil: 'networkidle' }) + expect(response?.status()).toBe(404) +}) + +test('test navigation and locale switching on news page', async ({ page, goto }) => { + // Переход на страницу /news/1 + await goto('/news/1', { waitUntil: 'hydration' }) + + // Проверяем наличие id и данных news + await expect(page.locator('.news-id')).toHaveText('id: 1') + await expect(page.locator('.news-data')).toBeVisible() + + // Проверяем переходы по ссылкам + await page.click('.link-article-1') + await expect(page).toHaveURL('/articles/1') + + await goto('/news/1', { waitUntil: 'hydration' }) // Возвращаемся на страницу /news/1 + await page.click('.link-news-4') + await expect(page).toHaveURL('/news/4') + + // Проверяем переключение локалей + await page.click('.locale-en') + await expect(page).toHaveURL('/news/4') + + await page.click('.locale-ru') + await expect(page).toHaveURL('/ru-ru/news/4') + + await page.click('.locale-de') + await expect(page).toHaveURL('/de-de/news/4') +}) + +test('test query parameters and hash on news page', async ({ page, goto }) => { + await goto('/news/2?a=b#tada', { waitUntil: 'hydration' }) + + // Проверяем, что id и query параметры корректно отображаются + await expect(page.locator('.news-id')).toHaveText('id: 2') + await expect(page).toHaveURL('/news/2?a=b#tada') + + // Проверяем, что localeRoute корректно работает с query и hash + await page.click('.link-news-2') + await expect(page).toHaveURL('/news/2?a=b') +}) + +test('test navigation and locale switching on articles page', async ({ page, goto }) => { + // Navigate to the /articles/1 page + await goto('/articles/1', { waitUntil: 'hydration' }) + + // Check the presence of the id and article data + await expect(page.locator('.article-id')).toHaveText('id: 1') + await expect(page.locator('.article-data')).toBeVisible() + + // Check the link transition to the news + await page.click('.link-news-1') + await expect(page).toHaveURL('/news/1') + + // Check locale switching + await goto('/articles/1', { waitUntil: 'hydration' }) // Return to /articles/1 + await page.click('.locale-en') + await expect(page).toHaveURL('/articles/1') + + await page.click('.locale-ru') + await expect(page).toHaveURL('/ru-ru/articles/1') + + await page.click('.locale-de') + await expect(page).toHaveURL('/de-de/articles/1') +}) + +test('test locale switching and content on locale-conf page', async ({ page, goto }) => { + await goto('/', { waitUntil: 'hydration' }) + await goto('/locale-conf', { waitUntil: 'hydration' }) + + await page.waitForTimeout(500) + + // Check the page title in English + const titleEn = await page.locator('h1').textContent() + expect(titleEn).toBe('Locale Test Page') + + // Check the page content in English + const contentEn = await page.locator('#content').textContent() + expect(contentEn).toBe('This is a content area.') + + const greetingEn = await page.locator('#username').textContent() + expect(greetingEn).toBe('Hello, John!') + + const pluralEn = await page.locator('#plural').textContent() + expect(pluralEn).toBe('You have 2 items.') + + const htmlContentEn = await page.locator('#html-content').innerHTML() + expect(htmlContentEn).toContain('Bold Text with HTML content.') + + const localeRouteEn = await page.locator('.locale-route-data:nth-of-type(1)').textContent() + expect(localeRouteEn).toContain('"fullPath": "/locale-conf"') + expect(localeRouteEn).toContain('"name": "locale-conf"') + expect(localeRouteEn).toContain('"href": "/locale-conf"') + + // Check the first $switchLocaleRoute link in English + const switchLocaleRouteEn = await page.locator('#locale-en').getAttribute('href') + expect(switchLocaleRouteEn).toContain('/locale-conf') + + // Check the second $switchLocaleRoute link in German + const switchLocaleRouteDe = await page.locator('#locale-de').getAttribute('href') + expect(switchLocaleRouteDe).toContain('/de-de/locale-conf-modif') + + // Click on the element + await page.click('#locale-de') + + await expect(page).toHaveURL('/de-de/locale-conf-modify') + + // Check the page title in German + const titleDe = await page.locator('h1').textContent() + expect(titleDe).toBe('Sprachtestseite') + + // Check the page content in German + const contentDe = await page.locator('#content').textContent() + expect(contentDe).toBe('Dies ist ein Inhaltsbereich.') + + const greetingDe = await page.locator('#username').textContent() + expect(greetingDe).toBe('Hallo, John!') + + const pluralDe = await page.locator('#plural').textContent() + expect(pluralDe).toBe('Sie haben 2 Artikel.') + + const htmlContentDe = await page.locator('#html-content').innerHTML() + expect(htmlContentDe).toContain('Fetter Text mit HTML-Inhalt.') + + const switchLocaleRouteEnN = await page.locator('#locale-en').getAttribute('href') + expect(switchLocaleRouteEnN).toContain('/locale-conf') + + // Check the second $switchLocaleRoute link in German + const switchLocaleRouteDeN = await page.locator('#locale-de').getAttribute('href') + expect(switchLocaleRouteDeN).toContain('/de-de/locale-conf-modif') +}) diff --git a/test/fixtures/custom-regex-fail/app.vue b/test/fixtures/custom-regex-fail/app.vue new file mode 100755 index 00000000..943dd95e --- /dev/null +++ b/test/fixtures/custom-regex-fail/app.vue @@ -0,0 +1,12 @@ + + + diff --git a/test/fixtures/custom-regex-fail/locales/de.json b/test/fixtures/custom-regex-fail/locales/de.json new file mode 100755 index 00000000..8f73b055 --- /dev/null +++ b/test/fixtures/custom-regex-fail/locales/de.json @@ -0,0 +1,5 @@ +{ + "generic": { + "welcome": "Willkommen auf der Seite!" + } +} diff --git a/test/fixtures/custom-regex-fail/locales/en.json b/test/fixtures/custom-regex-fail/locales/en.json new file mode 100755 index 00000000..6a3d383b --- /dev/null +++ b/test/fixtures/custom-regex-fail/locales/en.json @@ -0,0 +1,6 @@ +{ + "generic": { + "welcome": "Welcome to the page!", + "title": "Page Title!" + } +} diff --git a/test/fixtures/custom-regex-fail/nuxt.config.ts b/test/fixtures/custom-regex-fail/nuxt.config.ts new file mode 100644 index 00000000..771df9a0 --- /dev/null +++ b/test/fixtures/custom-regex-fail/nuxt.config.ts @@ -0,0 +1,18 @@ +import MyModule from '../../../src/module' + +export default defineNuxtConfig({ + modules: [ + MyModule, + ], + + i18n: { + locales: [{ code: 'de' }, { code: 'en' }], + defaultLocale: 'en', + // localeCookie: 'user-change-coockie', + disablePageLocales: true, + includeDefaultLocaleRoute: false, + customRegexMatcher: '[a-z]{2}-[a-z]{2}', + }, + + compatibilityDate: '2024-08-16', +}) diff --git a/test/fixtures/custom-regex-fail/package.json b/test/fixtures/custom-regex-fail/package.json new file mode 100644 index 00000000..8d22eaa4 --- /dev/null +++ b/test/fixtures/custom-regex-fail/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "name": "cookie", + "type": "module", + "scripts": { + "dev": "nuxi dev", + "build": "nuxi build", + "generate": "nuxi generate" + } +} diff --git a/test/fixtures/custom-regex-fail/pages/index.vue b/test/fixtures/custom-regex-fail/pages/index.vue new file mode 100755 index 00000000..6a292d65 --- /dev/null +++ b/test/fixtures/custom-regex-fail/pages/index.vue @@ -0,0 +1,18 @@ + + + diff --git a/test/fixtures/custom-regex/locales/de-de.json b/test/fixtures/custom-regex/locales/de-de.json new file mode 100644 index 00000000..0d9df939 --- /dev/null +++ b/test/fixtures/custom-regex/locales/de-de.json @@ -0,0 +1,6 @@ +{ + "key0": "de", + "generic": { + "welcome": "Willkommen auf der Seite!" + } +} diff --git a/test/fixtures/custom-regex/locales/en-us.json b/test/fixtures/custom-regex/locales/en-us.json new file mode 100644 index 00000000..d897a496 --- /dev/null +++ b/test/fixtures/custom-regex/locales/en-us.json @@ -0,0 +1,7 @@ +{ + "key0": "en", + "basic": "basic text", + "generic": { + "welcome": "Welcome to the page!" + } +} diff --git a/test/fixtures/custom-regex/locales/pages/activity-locale/de-de.json b/test/fixtures/custom-regex/locales/pages/activity-locale/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity-locale/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/activity-locale/en-us.json b/test/fixtures/custom-regex/locales/pages/activity-locale/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity-locale/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/activity-locale/ru-ru.json b/test/fixtures/custom-regex/locales/pages/activity-locale/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity-locale/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/activity/de-de.json b/test/fixtures/custom-regex/locales/pages/activity/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/activity/en-us.json b/test/fixtures/custom-regex/locales/pages/activity/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/activity/ru-ru.json b/test/fixtures/custom-regex/locales/pages/activity/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/activity/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/articles-id/de-de.json b/test/fixtures/custom-regex/locales/pages/articles-id/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/articles-id/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/articles-id/en-us.json b/test/fixtures/custom-regex/locales/pages/articles-id/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/articles-id/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/articles-id/ru-ru.json b/test/fixtures/custom-regex/locales/pages/articles-id/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/articles-id/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/index/de-de.json b/test/fixtures/custom-regex/locales/pages/index/de-de.json new file mode 100644 index 00000000..688fdf32 --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/index/de-de.json @@ -0,0 +1,4 @@ +{ + "key1": "text in de", + "test_link": "link in de" +} diff --git a/test/fixtures/custom-regex/locales/pages/index/en-us.json b/test/fixtures/custom-regex/locales/pages/index/en-us.json new file mode 100644 index 00000000..552bc1c0 --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/index/en-us.json @@ -0,0 +1,6 @@ +{ + "key1": "text in en", + "test_link": "link in en", + "basic_layer": "page basic text", + "text_escaping": "test {text_escaping} } { { " +} diff --git a/test/fixtures/custom-regex/locales/pages/index/ru-ru.json b/test/fixtures/custom-regex/locales/pages/index/ru-ru.json new file mode 100644 index 00000000..9b7d9e47 --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/index/ru-ru.json @@ -0,0 +1,4 @@ +{ + "key1": "text in ru", + "test_link": "link in ru" +} diff --git a/test/fixtures/custom-regex/locales/pages/locale-conf/de-de.json b/test/fixtures/custom-regex/locales/pages/locale-conf/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-conf/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/locale-conf/en-us.json b/test/fixtures/custom-regex/locales/pages/locale-conf/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-conf/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/locale-conf/ru-ru.json b/test/fixtures/custom-regex/locales/pages/locale-conf/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-conf/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/locale-test/de-de.json b/test/fixtures/custom-regex/locales/pages/locale-test/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-test/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/locale-test/en-us.json b/test/fixtures/custom-regex/locales/pages/locale-test/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-test/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/locale-test/ru-ru.json b/test/fixtures/custom-regex/locales/pages/locale-test/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/locale-test/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/news-id/de-de.json b/test/fixtures/custom-regex/locales/pages/news-id/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/news-id/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/news-id/en-us.json b/test/fixtures/custom-regex/locales/pages/news-id/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/news-id/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/news-id/ru-ru.json b/test/fixtures/custom-regex/locales/pages/news-id/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/news-id/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/page/de-de.json b/test/fixtures/custom-regex/locales/pages/page/de-de.json new file mode 100644 index 00000000..9ad9e23f --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page/de-de.json @@ -0,0 +1,8 @@ +{ + "page":{ + "content": "Page content in de", + "example": "Page example in de", + "items": "{count} Artikel", + "apples": "keine Äpfel | ein Apfel | {count} Äpfel" + } +} diff --git a/test/fixtures/custom-regex/locales/pages/page/en-us.json b/test/fixtures/custom-regex/locales/pages/page/en-us.json new file mode 100644 index 00000000..db6c40cd --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page/en-us.json @@ -0,0 +1,8 @@ +{ + "page": { + "content": "Page content in en", + "example": "Page example in en", + "items": "{count} items", + "apples": "no apples | one apple | {count} apples" + } +} diff --git a/test/fixtures/custom-regex/locales/pages/page/ru-ru.json b/test/fixtures/custom-regex/locales/pages/page/ru-ru.json new file mode 100644 index 00000000..f19fbaf6 --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page/ru-ru.json @@ -0,0 +1,7 @@ +{ + "page": { + "content": "Page content in ru", + "items": "{count} items", + "apples": "нет яблок | одно яблоко | {count} яблок(а)" + } +} diff --git a/test/fixtures/custom-regex/locales/pages/page2/de-de.json b/test/fixtures/custom-regex/locales/pages/page2/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page2/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/page2/en-us.json b/test/fixtures/custom-regex/locales/pages/page2/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page2/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/page2/ru-ru.json b/test/fixtures/custom-regex/locales/pages/page2/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/page2/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/unlocalized/de-de.json b/test/fixtures/custom-regex/locales/pages/unlocalized/de-de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/unlocalized/de-de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/unlocalized/en-us.json b/test/fixtures/custom-regex/locales/pages/unlocalized/en-us.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/unlocalized/en-us.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/pages/unlocalized/ru-ru.json b/test/fixtures/custom-regex/locales/pages/unlocalized/ru-ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/fixtures/custom-regex/locales/pages/unlocalized/ru-ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/custom-regex/locales/ru-ru.json b/test/fixtures/custom-regex/locales/ru-ru.json new file mode 100644 index 00000000..e90470e6 --- /dev/null +++ b/test/fixtures/custom-regex/locales/ru-ru.json @@ -0,0 +1,3 @@ +{ + "key0": "ru" +} diff --git a/test/fixtures/custom-regex/nuxt.config.ts b/test/fixtures/custom-regex/nuxt.config.ts new file mode 100644 index 00000000..4aea8644 --- /dev/null +++ b/test/fixtures/custom-regex/nuxt.config.ts @@ -0,0 +1,33 @@ +import MyModule from '../../../src/module' + +export default defineNuxtConfig({ + modules: [ + MyModule, + ], + + i18n: { + locales: [ + { code: 'en-us', iso: 'en_EN' }, + { code: 'de-de', iso: 'de_DE' }, + { code: 'ru-ru', iso: 'ru_RU' }, + ], + meta: true, + defaultLocale: 'en-us', + translationDir: 'locales', + autoDetectLanguage: false, + routesLocaleLinks: { + 'dir1-slug': 'index', + }, + globalLocaleRoutes: { + page2: { + ['en-us']: '/custom-page2-en', + ['de-de']: '/custom-page2-de', + ['ru-ru']: '/custom-page2-ru', + }, + unlocalized: false, // Unlocalized page should not be localized + }, + customRegexMatcher: '[a-z]{2}-[a-z]{2}', + }, + + compatibilityDate: '2024-08-16', +}) diff --git a/test/fixtures/custom-regex/package.json b/test/fixtures/custom-regex/package.json new file mode 100644 index 00000000..763bba6b --- /dev/null +++ b/test/fixtures/custom-regex/package.json @@ -0,0 +1,10 @@ +{ + "private": true, + "name": "basic", + "type": "module", + "scripts": { + "dev": "nuxi dev", + "build": "nuxi build", + "generate": "nuxi generate" + } +} diff --git a/test/fixtures/custom-regex/pages/activity-locale.vue b/test/fixtures/custom-regex/pages/activity-locale.vue new file mode 100755 index 00000000..a1bd643f --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity-locale.vue @@ -0,0 +1,35 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity-locale/hiking.vue b/test/fixtures/custom-regex/pages/activity-locale/hiking.vue new file mode 100755 index 00000000..f7831a83 --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity-locale/hiking.vue @@ -0,0 +1,14 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity-locale/skiing.vue b/test/fixtures/custom-regex/pages/activity-locale/skiing.vue new file mode 100755 index 00000000..cda62ee5 --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity-locale/skiing.vue @@ -0,0 +1,21 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity.vue b/test/fixtures/custom-regex/pages/activity.vue new file mode 100755 index 00000000..0b3fb3dc --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity.vue @@ -0,0 +1,48 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity/hiking-locale.vue b/test/fixtures/custom-regex/pages/activity/hiking-locale.vue new file mode 100755 index 00000000..08ccb3e1 --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity/hiking-locale.vue @@ -0,0 +1,21 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity/hiking.vue b/test/fixtures/custom-regex/pages/activity/hiking.vue new file mode 100755 index 00000000..9449ffce --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity/hiking.vue @@ -0,0 +1,11 @@ + diff --git a/test/fixtures/custom-regex/pages/activity/skiing-locale.vue b/test/fixtures/custom-regex/pages/activity/skiing-locale.vue new file mode 100755 index 00000000..7ef44830 --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity/skiing-locale.vue @@ -0,0 +1,21 @@ + + + diff --git a/test/fixtures/custom-regex/pages/activity/skiing.vue b/test/fixtures/custom-regex/pages/activity/skiing.vue new file mode 100755 index 00000000..3304dd3a --- /dev/null +++ b/test/fixtures/custom-regex/pages/activity/skiing.vue @@ -0,0 +1,14 @@ + + + diff --git a/test/fixtures/custom-regex/pages/articles/[id].vue b/test/fixtures/custom-regex/pages/articles/[id].vue new file mode 100644 index 00000000..d59592d7 --- /dev/null +++ b/test/fixtures/custom-regex/pages/articles/[id].vue @@ -0,0 +1,56 @@ + + + + + diff --git a/test/fixtures/custom-regex/pages/dir1/[slug].vue b/test/fixtures/custom-regex/pages/dir1/[slug].vue new file mode 100644 index 00000000..fff9e87c --- /dev/null +++ b/test/fixtures/custom-regex/pages/dir1/[slug].vue @@ -0,0 +1,11 @@ + + + diff --git a/test/fixtures/custom-regex/pages/index.vue b/test/fixtures/custom-regex/pages/index.vue new file mode 100644 index 00000000..a4bb6c44 --- /dev/null +++ b/test/fixtures/custom-regex/pages/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/test/fixtures/custom-regex/pages/locale-conf.vue b/test/fixtures/custom-regex/pages/locale-conf.vue new file mode 100644 index 00000000..7cabc74f --- /dev/null +++ b/test/fixtures/custom-regex/pages/locale-conf.vue @@ -0,0 +1,83 @@ + + + diff --git a/test/fixtures/custom-regex/pages/locale-test.vue b/test/fixtures/custom-regex/pages/locale-test.vue new file mode 100644 index 00000000..32f600cd --- /dev/null +++ b/test/fixtures/custom-regex/pages/locale-test.vue @@ -0,0 +1,76 @@ + + + diff --git a/test/fixtures/custom-regex/pages/news/[id].vue b/test/fixtures/custom-regex/pages/news/[id].vue new file mode 100644 index 00000000..bd8a7336 --- /dev/null +++ b/test/fixtures/custom-regex/pages/news/[id].vue @@ -0,0 +1,95 @@ + + + diff --git a/test/fixtures/custom-regex/pages/page.vue b/test/fixtures/custom-regex/pages/page.vue new file mode 100644 index 00000000..b4d94387 --- /dev/null +++ b/test/fixtures/custom-regex/pages/page.vue @@ -0,0 +1,81 @@ + + + diff --git a/test/fixtures/custom-regex/pages/page2.vue b/test/fixtures/custom-regex/pages/page2.vue new file mode 100644 index 00000000..b4d94387 --- /dev/null +++ b/test/fixtures/custom-regex/pages/page2.vue @@ -0,0 +1,81 @@ + + + diff --git a/test/fixtures/custom-regex/pages/unlocalized.vue b/test/fixtures/custom-regex/pages/unlocalized.vue new file mode 100644 index 00000000..5c97891d --- /dev/null +++ b/test/fixtures/custom-regex/pages/unlocalized.vue @@ -0,0 +1,81 @@ + + + diff --git a/test/fixtures/custom-regex/server/api/getArticles.js b/test/fixtures/custom-regex/server/api/getArticles.js new file mode 100644 index 00000000..e4bbc5ed --- /dev/null +++ b/test/fixtures/custom-regex/server/api/getArticles.js @@ -0,0 +1,8 @@ +import { defineEventHandler, getQuery } from 'h3' + +export default defineEventHandler((event) => { + const { id } = getQuery(event) + return { + id: id, + } +}) diff --git a/test/fixtures/custom-regex/server/api/getNews.js b/test/fixtures/custom-regex/server/api/getNews.js new file mode 100644 index 00000000..5f189c15 --- /dev/null +++ b/test/fixtures/custom-regex/server/api/getNews.js @@ -0,0 +1,25 @@ +import { defineEventHandler, getQuery } from 'h3' + +export default defineEventHandler((event) => { + const { id } = getQuery(event) + let metadata = null + + if (id === '1' || id.startsWith('1-')) { + metadata = { + ['en-us']: { id: '1-one' }, + ['de-de']: { id: '1-eins' }, + ['ru-ru']: { id: '1-odin' }, + } + } + if (id === '2' || id.startsWith('2-')) { + metadata = { + ['en-us']: { id: '2-two' }, + ['de-de']: { id: '2-zwei' }, + ['ru-ru']: { id: '2-dva' }, + } + } + return { + id: id, + metadata, + } +}) diff --git a/test/fixtures/custom-regex/server/tsconfig.json b/test/fixtures/custom-regex/server/tsconfig.json new file mode 100644 index 00000000..b9ed69c1 --- /dev/null +++ b/test/fixtures/custom-regex/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +}