Skip to content

Commit

Permalink
Merge pull request #41 from Rigo-m/feat/route-regex-simplification
Browse files Browse the repository at this point in the history
feat: custom locale pattern matcher
  • Loading branch information
s00d authored Oct 17, 2024
2 parents 7e5921f + 1ab8688 commit 0f5b37f
Show file tree
Hide file tree
Showing 98 changed files with 1,462 additions and 14 deletions.
14 changes: 14 additions & 0 deletions docs/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions playground/locales/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/articles-id/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/articles-id/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/articles-id/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1-subdir/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1-subdir/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1-subdir/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir1/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir2-slug/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir2-slug/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/dir2-slug/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/index/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/index/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/index/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/news-id/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/news-id/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/news-id/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/page/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/page/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/page/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/subpage/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/subpage/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/subpage/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/unlocalized/de-de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/unlocalized/en-us.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/pages/unlocalized/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions playground/locales/ru-ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
12 changes: 11 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default defineNuxtModule<ModuleOptions>({
}
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'
Expand Down Expand Up @@ -104,6 +105,15 @@ export default defineNuxtModule<ModuleOptions>({
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,
Expand Down Expand Up @@ -159,7 +169,7 @@ export default defineNuxtModule<ModuleOptions>({
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 : []

Expand Down
23 changes: 14 additions & 9 deletions src/page-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = []
Expand All @@ -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)
}
})

Expand Down Expand Up @@ -110,6 +110,7 @@ export class PageManager {
page: NuxtPage,
customRoutePaths: Record<string, string>,
additionalRoutes: NuxtPage[],
customRegex?: string | RegExp,
) {
this.locales.forEach((locale) => {
const customPath = customRoutePaths[locale.code]
Expand All @@ -122,14 +123,15 @@ 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))
}
})
}

private localizePage(
page: NuxtPage,
additionalRoutes: NuxtPage[],
customRegex?: string | RegExp,
) {
if (isPageRedirectOnly(page)) return

Expand All @@ -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)
Expand Down Expand Up @@ -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]
Expand All @@ -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))
}
})
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
10 changes: 8 additions & 2 deletions src/runtime/server/middleware/i18n-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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`
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface ModuleOptions {
fallbackLocale?: string
localeCookie?: string
globalLocaleRoutes?: GlobalLocaleRoutes
customRegexMatcher?: string | RegExp
}

export interface ModuleOptionsExtend extends ModuleOptions {
Expand Down
10 changes: 8 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 0f5b37f

Please sign in to comment.