diff --git a/src/plugin/createNtrData.ts b/src/plugin/createNtrData.ts index a620ee1..d3db2eb 100644 --- a/src/plugin/createNtrData.ts +++ b/src/plugin/createNtrData.ts @@ -7,7 +7,7 @@ import { parsePages } from './parsePages' export const createNtrData = (nextConfig: NextConfig, customPagesPath?: string): TNtrData => { const { pageExtensions = ['js', 'ts', 'jsx', 'tsx'], - i18n: { defaultLocale, locales = [] }, + i18n, translateRoutes: { debug, routesDataFileName, routesTree: customRoutesTree, pagesDirectory } = {}, } = nextConfig as NextConfigWithNTR const pagesPath = customPagesPath || getPagesPath(pagesDirectory) @@ -17,8 +17,7 @@ export const createNtrData = (nextConfig: NextConfig, customPagesPath?: string): return { debug, - defaultLocale, - locales, + i18n, routesTree, } } diff --git a/src/plugin/getRouteBranchReRoutes.ts b/src/plugin/getRouteBranchReRoutes.ts index f62fa2d..93c7d4d 100644 --- a/src/plugin/getRouteBranchReRoutes.ts +++ b/src/plugin/getRouteBranchReRoutes.ts @@ -1,6 +1,8 @@ import type { Redirect, Rewrite } from 'next/dist/lib/load-custom-routes' import { pathToRegexp } from 'path-to-regexp' +import { getNtrData } from '../react/ntrData' +import { isDefaultLocale } from '../shared/isDefaultLocale' import { ignoreSegmentPathRegex } from '../shared/regex' import type { TReRoutes, TRouteBranch, TRouteSegment } from '../types' import { fileNameToPath } from './fileNameToPaths' @@ -49,22 +51,14 @@ const mergeOrRegex = (existingRegex: string, newPossiblity: string) => { /** * Get redirects and rewrites for a page */ -export const getPageReRoutes = ({ - locales, - routeSegments, - defaultLocale, -}: { - locales: L[] - routeSegments: TRouteSegment[] - defaultLocale?: L -}): TReRoutes => { +export const getPageReRoutes = ({ routeSegments }: { routeSegments: TRouteSegment[] }): TReRoutes => { /** If there is only one path possible: it is common to all locales and to files. No redirection nor rewrite is needed. */ if (!routeSegments.some(({ paths }) => Object.keys(paths).length > 1)) { return { rewrites: [], redirects: [] } } /** Get a translated path or base path */ - const getPath = (locale: L | 'default') => + const getPath = (locale: string) => `/${routeSegments .map(({ paths }) => paths[locale] || paths.default) .filter((pathPart) => pathPart && !ignoreSegmentPathRegex.test(pathPart)) @@ -80,6 +74,8 @@ export const getPageReRoutes = ({ .filter(Boolean) // Filter out falsy values .join('/')}` + const { i18n } = getNtrData() + /** * ``` * [ @@ -90,7 +86,7 @@ export const getPageReRoutes = ({ * ``` * Each locale cannot appear more than once. Item is ignored if its path would be the same as basePath. */ - const sourceList = locales.reduce((acc, locale) => { + const sourceList = i18n.locales.reduce((acc, locale) => { const source = getPath(locale) if (source === basePath) { return acc @@ -100,11 +96,11 @@ export const getPageReRoutes = ({ ...acc.filter((sourceItem) => sourceItem.source !== source), { source, sourceLocales: [...sourceLocales, locale] }, ] - }, [] as { sourceLocales: L[]; source: string }[]) + }, [] as { sourceLocales: string[]; source: string }[]) - const redirects = locales.reduce((acc, locale) => { + const redirects = i18n.locales.reduce((acc, locale) => { const localePath = getPath(locale) - const destination = `${locale === defaultLocale ? '' : `/${locale}`}${sourceToDestination(localePath)}` + const destination = `${isDefaultLocale(locale, i18n) ? '' : `/${locale}`}${sourceToDestination(localePath)}` return [ ...acc, @@ -203,16 +199,12 @@ export const getPageReRoutes = ({ /** * Generate reroutes in route branch to feed the rewrite section of next.config */ -export const getRouteBranchReRoutes = ({ - locales, +export const getRouteBranchReRoutes = ({ routeBranch: { children, ...routeSegment }, previousRouteSegments = [], - defaultLocale, }: { - locales: L[] - routeBranch: TRouteBranch - previousRouteSegments?: TRouteSegment[] - defaultLocale?: L + routeBranch: TRouteBranch + previousRouteSegments?: TRouteSegment[] }): TReRoutes => { const routeSegments = [...previousRouteSegments, routeSegment] @@ -221,13 +213,8 @@ export const getRouteBranchReRoutes = ({ (acc, child) => { const childReRoutes = child.name === 'index' - ? getPageReRoutes({ locales, routeSegments, defaultLocale }) - : getRouteBranchReRoutes({ - locales, - routeBranch: child, - previousRouteSegments: routeSegments, - defaultLocale, - }) + ? getPageReRoutes({ routeSegments }) + : getRouteBranchReRoutes({ routeBranch: child, previousRouteSegments: routeSegments }) return { redirects: [...acc.redirects, ...childReRoutes.redirects], rewrites: [...acc.rewrites, ...childReRoutes.rewrites], @@ -235,5 +222,5 @@ export const getRouteBranchReRoutes = ({ }, { redirects: [], rewrites: [] } as TReRoutes, ) - : getPageReRoutes({ locales, routeSegments, defaultLocale }) + : getPageReRoutes({ routeSegments }) } diff --git a/src/plugin/parsePages.ts b/src/plugin/parsePages.ts index 025bf8c..3a9914e 100644 --- a/src/plugin/parsePages.ts +++ b/src/plugin/parsePages.ts @@ -8,18 +8,14 @@ import { fileNameToPath } from './fileNameToPaths' import { getPagesDir, isRoutesFileName } from './routesFiles' /** Get path and path translations from name and all translations #childrenOrder */ -const getRouteSegment = ( - name: string, - routeSegmentsData: TRouteSegmentsData, - isDirectory?: boolean, -): TRouteSegment => { +const getRouteSegment = (name: string, routeSegmentsData: TRouteSegmentsData, isDirectory?: boolean): TRouteSegment => { const routeSegmentData = routeSegmentsData?.[isDirectory ? '/' : name] const { default: defaultPath = fileNameToPath(name), ...localized } = typeof routeSegmentData === 'object' ? routeSegmentData : { default: routeSegmentData } const paths = { default: defaultPath, ...localized, - } as TRouteSegmentPaths + } as TRouteSegmentPaths return { name, paths, @@ -49,12 +45,12 @@ export type TParsePageTreeProps = { /** * Recursively parse pages directory and build a page tree object */ -export const parsePages = ({ +export const parsePages = ({ directoryPath: propDirectoryPath, pageExtensions, isSubBranch, routesDataFileName, -}: TParsePageTreeProps): TRouteBranch => { +}: TParsePageTreeProps): TRouteBranch => { const directoryPath = propDirectoryPath || getPagesDir() const directoryItems = fs.readdirSync(directoryPath) const routesFileName = directoryItems.find((directoryItem) => isRoutesFileName(directoryItem, routesDataFileName)) @@ -65,7 +61,7 @@ export const parsePages = ({ routeSegmentsFileContent ? (/\.yaml$/.test(routesFileName as string) ? YAML : JSON).parse(routeSegmentsFileContent) : {} - ) as TRouteSegmentsData + ) as TRouteSegmentsData const directoryPathParts = directoryPath.replace(/[\\/]/, '').split(/[\\/]/) const name = isSubBranch ? directoryPathParts[directoryPathParts.length - 1] : '' @@ -93,7 +89,7 @@ export const parsePages = ({ ] } return acc - }, [] as TRouteBranch[]) + }, [] as TRouteBranch[]) .sort((childA, childB) => getOrderWeight(childA) - getOrderWeight(childB)) return { diff --git a/src/plugin/withTranslateRoutes.ts b/src/plugin/withTranslateRoutes.ts index e184e16..be1452e 100644 --- a/src/plugin/withTranslateRoutes.ts +++ b/src/plugin/withTranslateRoutes.ts @@ -2,8 +2,9 @@ import type { Redirect, Rewrite } from 'next/dist/lib/load-custom-routes' import type { NextConfig } from 'next/dist/server/config-shared' import type { Configuration as WebpackConfiguration, FileCacheOptions } from 'webpack' +import { setNtrData } from '../react/ntrData' import { ntrMessagePrefix } from '../shared/withNtrPrefix' -import { NextConfigWithNTR } from '../types' +import type { NextConfigWithNTR } from '../types' import { createNtrData } from './createNtrData' import { getPagesPath } from './getPagesPath' import { getRouteBranchReRoutes } from './getRouteBranchReRoutes' @@ -42,10 +43,10 @@ export const withTranslateRoutes = (userNextConfig: NextConfigWithNTR): NextConf const pagesPath = getPagesPath(pagesDirectory) const ntrData = createNtrData(userNextConfig, pagesPath) + setNtrData(ntrData) - const { routesTree, locales, defaultLocale } = ntrData - - const { redirects, rewrites } = getRouteBranchReRoutes({ locales, routeBranch: routesTree, defaultLocale }) + const { routesTree } = ntrData + const { redirects, rewrites } = getRouteBranchReRoutes({ routeBranch: routesTree }) const sortedRedirects = sortBySpecificity(redirects) const sortedRewrites = sortBySpecificity(rewrites) diff --git a/src/react/enhanceNextRouter.ts b/src/react/enhanceNextRouter.ts index 965ee02..cbb2f0d 100644 --- a/src/react/enhanceNextRouter.ts +++ b/src/react/enhanceNextRouter.ts @@ -43,7 +43,7 @@ const enhancePushReplace = const enhancePrefetch = (router: R) => (inputUrl: string, asPath?: string, options?: PrefetchOptions) => { - const locale = getLocale(router, options?.locale) + const locale = getLocale({ router, locale: options?.locale, url: inputUrl }) const parsedInputUrl = urlToFileUrl(inputUrl, locale) if (getNtrData().debug === 'withPrefetch') { diff --git a/src/react/fileUrlToUrl.ts b/src/react/fileUrlToUrl.ts index 372b06d..7fddce5 100644 --- a/src/react/fileUrlToUrl.ts +++ b/src/react/fileUrlToUrl.ts @@ -2,6 +2,7 @@ import { normalizePathTrailingSlash } from 'next/dist/client/normalize-trailing- import { parse as parsePathPattern, compile as compilePath } from 'path-to-regexp' import { format as formatUrl, UrlObject } from 'url' +import { isDefaultLocale } from '../shared/isDefaultLocale' import { ignoreSegmentPathRegex, optionalMatchAllFilepathPartRegex } from '../shared/regex' import { ntrMessagePrefix } from '../shared/withNtrPrefix' import type { TRouteBranch } from '../types' @@ -117,7 +118,7 @@ export const fileUrlToUrl = (url: UrlObject | URL | string, locale: string, { th try { const { pathname, query, hash } = fileUrlToFileUrlObject(url) - const { routesTree, defaultLocale } = getNtrData() + const { routesTree, i18n } = getNtrData() const pathParts = (pathname || '/') .replace(/^\/|\/$/g, '') @@ -134,7 +135,7 @@ export const fileUrlToUrl = (url: UrlObject | URL | string, locale: string, { th } } - return `${locale !== defaultLocale ? `/${locale}` : ''}${formatUrl({ + return `${!isDefaultLocale(locale, i18n) ? `/${locale}` : ''}${formatUrl({ pathname: newPathname, query, hash, diff --git a/src/react/getLocale.ts b/src/react/getLocale.ts index 37e5927..705ac64 100644 --- a/src/react/getLocale.ts +++ b/src/react/getLocale.ts @@ -1,8 +1,30 @@ import { NextRouter, SingletonRouter } from 'next/router' +import { UrlObject, parse as parseUrl } from 'url' import { getNtrData } from './ntrData' -export const getLocale = ( - { locale, defaultLocale, locales }: NextRouter | SingletonRouter, - explicitLocale?: string | false, -) => explicitLocale || locale || defaultLocale || locales?.[0] || getNtrData().defaultLocale || getNtrData().locales[0] +export const getLocale = ({ + router, + locale: explicitLocale, + url, +}: { + router?: NextRouter | SingletonRouter + locale?: string | false + url?: string | UrlObject | URL +} = {}) => { + if (explicitLocale) { + return explicitLocale + } + const { i18n } = getNtrData() + + // explicitLocale === false if opted-out of automatically handling the locale prefixing + // Cf. https://nextjs.org/docs/advanced-features/i18n-routing#transition-between-locales + if (explicitLocale === false && url) { + const { pathname } = typeof url === 'string' ? parseUrl(url) : url + const localeSegment = pathname?.split('/')[1] + if (localeSegment && i18n.locales.includes(localeSegment)) { + return localeSegment + } + } + return router?.locale || router?.defaultLocale || i18n.defaultLocale || router?.locales?.[0] || i18n.locales[0] +} diff --git a/src/react/removeLangPrefix.ts b/src/react/removeLangPrefix.ts index bb25120..2b47a7d 100644 --- a/src/react/removeLangPrefix.ts +++ b/src/react/removeLangPrefix.ts @@ -1,27 +1,67 @@ +import { isDefaultLocale } from '../shared/isDefaultLocale' import { getNtrData } from './ntrData' -export function removeLangPrefix(pathname: string, toArray?: false, locale?: string): string -export function removeLangPrefix(pathname: string, toArray: true, locale?: string): string[] -export function removeLangPrefix(pathname: string, toArray?: boolean, givenLocale?: string): string | string[] { - const pathParts = pathname.split('/').filter(Boolean) - const { routesTree, defaultLocale, locales } = getNtrData() +/** + * Remove both the lang prefix and the root prefix from a pathname + * + * (The root prefix is a prefix that can be added for a locale between the locale prefix + * and the rest of the pathname. It is defined in the root _routes.json file for the "/" key.) + */ +export function removeLangPrefix( + pathname: string, + /** + * If locale is explicitely given, removeLangPrefix will use it, + * it it is not, removeLangPrefix will try to deduce the locale from the pathname + */ + locale?: string, +): string { + const { + routesTree, + i18n, + i18n: { locales }, + } = getNtrData() + let lang = locale + let root = '' const getLangRoot = (lang: string) => routesTree.paths[lang] || routesTree.paths.default - const defaultLocaleRoot = defaultLocale && getLangRoot(defaultLocale) - const hasLangPrefix = givenLocale ? pathParts[0] === givenLocale : locales.includes(pathParts[0]) - const hasDefaultLocalePrefix = !hasLangPrefix && !!defaultLocaleRoot && pathParts[0] === defaultLocaleRoot - const hasGivenLocalePrefix = givenLocale ? pathParts[hasLangPrefix ? 1 : 0] === getLangRoot(givenLocale) : false + if (locale) { + root = getLangRoot(locale) + } else { + const prefixLocale = locales.find((locale) => new RegExp(`\\/${locale}(\\/|$)`).test(pathname)) + if (prefixLocale) { + lang = prefixLocale + root = getLangRoot(prefixLocale) + } else { + for (const l of locales) { + if (isDefaultLocale(l, i18n)) { + lang = l + root = getLangRoot(l) + break + } + } + } + } + + let remainingPathname: string | undefined = undefined + + if (!lang) { + return pathname + } + + const fullPrefix = `/${lang}/${root}` - if (!hasLangPrefix && !hasDefaultLocalePrefix && !hasGivenLocalePrefix) { - return toArray ? pathParts : pathname + if (root && pathname.startsWith(fullPrefix)) { + remainingPathname = pathname.slice(fullPrefix.length) + } else if (root && pathname.startsWith(`/${root}`)) { + remainingPathname = pathname.slice(root.length + 1) + } else if (pathname.startsWith(`/${lang}`)) { + remainingPathname = pathname.slice(lang.length + 1) } - const locale = givenLocale || hasLangPrefix ? pathParts[0] : defaultLocale - const localeRootParts = (locale || hasGivenLocalePrefix) && getLangRoot(locale)?.split('/') - const nbPathPartsToRemove = - (hasLangPrefix ? 1 : 0) + - (localeRootParts && (!hasLangPrefix || pathParts[1] === localeRootParts[0]) ? localeRootParts.length : 0) + if (typeof remainingPathname === 'string' && /^($|\/)/.test(remainingPathname)) { + return remainingPathname + } - return toArray ? pathParts.slice(nbPathPartsToRemove) : `/${pathParts.slice(nbPathPartsToRemove).join('/')}` + return pathname } diff --git a/src/react/translatePushReplaceArgs.ts b/src/react/translatePushReplaceArgs.ts index 027317b..ed4c50e 100644 --- a/src/react/translatePushReplaceArgs.ts +++ b/src/react/translatePushReplaceArgs.ts @@ -5,7 +5,6 @@ import type { Url } from '../types' import { fileUrlToFileUrlObject } from './fileUrlToFileUrlObject' import { fileUrlToUrl } from './fileUrlToUrl' import { getLocale } from './getLocale' -import { getNtrData } from './ntrData' import { removeLangPrefix } from './removeLangPrefix' import { urlToFileUrl } from './urlToFileUrl' @@ -24,17 +23,10 @@ export const translatePushReplaceArgs = ({ return { url, as, locale } } - let newLocale = getLocale(router, locale) - const locales = router.locales || getNtrData().locales + const newLocale = getLocale({ router, locale, url }) const unprefixedUrl = typeof url === 'string' ? removeLangPrefix(url) : url const urlLocale = typeof url === 'string' && unprefixedUrl !== url ? url.split('/')[1] : undefined - // propLocale === false if opted-out of automatically handling the locale prefixing - // Cf. https://nextjs.org/docs/advanced-features/i18n-routing#transition-between-locales - if (locale === false && urlLocale && locales.includes(urlLocale)) { - newLocale = urlLocale - } - /** * url can be: * - an external url diff --git a/src/react/translateUrl.ts b/src/react/translateUrl.ts index 8e0bd3d..23c61cf 100644 --- a/src/react/translateUrl.ts +++ b/src/react/translateUrl.ts @@ -4,6 +4,7 @@ import { compile as compilePath, parse as parsePath } from 'path-to-regexp' import type { ParsedUrlQuery } from 'querystring' import { format as formatUrl, parse, UrlObject } from 'url' +import { isDefaultLocale } from '../shared/isDefaultLocale' import { getDynamicPathPartKey, getSpreadFilepathPartKey, ignoreSegmentPathRegex } from '../shared/regex' import type { TRouteBranch, Url } from '../types' import { getNtrData } from './ntrData' @@ -150,11 +151,9 @@ export function translatePath(url: Url, locale: string, { format }: Options = {} return returnFormat === 'object' ? url : formatUrl(url) } - const pathParts = removeLangPrefix(pathname, true) - const { translatedPathParts, augmentedQuery = {} } = translatePathParts({ locale, - pathParts, + pathParts: removeLangPrefix(pathname).split('/'), query, routeBranch: routesTree, }) @@ -199,7 +198,7 @@ export type TTranslateUrl = typeof translatePath * same type as url if options.format is not defined */ export const translateUrl: TTranslateUrl = ((url, locale, options) => { - const { defaultLocale } = getNtrData() + const { i18n } = getNtrData() // Handle external urls const parsedUrl: UrlObject = typeof url === 'string' ? parse(url) : url @@ -215,7 +214,7 @@ export const translateUrl: TTranslateUrl = ((url, locale, options) => { return translatedPath } - const prefix = locale === defaultLocale || options?.withoutLangPrefix ? '' : `/${locale}` + const prefix = isDefaultLocale(locale, i18n) || options?.withoutLangPrefix ? '' : `/${locale}` return normalizePathTrailingSlash(prefix + translatedPath) }) as typeof translatePath diff --git a/src/react/urlToFileUrl.ts b/src/react/urlToFileUrl.ts index 88b8776..0e2dfce 100644 --- a/src/react/urlToFileUrl.ts +++ b/src/react/urlToFileUrl.ts @@ -4,6 +4,7 @@ import type { UrlObject } from 'url' import { ignoreSegmentPathRegex, anyDynamicPathPatternPartRegex, anyDynamicFilepathPartsRegex } from '../shared/regex' import type { TRouteBranch } from '../types' +import { getLocale } from './getLocale' import { getNtrData } from './ntrData' import { parseUrl } from './parseUrl' import { removeLangPrefix } from './removeLangPrefix' @@ -243,7 +244,7 @@ export const parsePathParts = ({ * if the url successfully matched a file path, and undefined otherwise */ export const urlToFileUrl = (url: string | URL | UrlObject, locale?: string) => { - const { routesTree, defaultLocale, locales } = getNtrData() + const { routesTree } = getNtrData() const { pathname, query, hash } = parseUrl(url) if (pathname && anyDynamicFilepathPartsRegex.exec(pathname)) { @@ -252,10 +253,14 @@ export const urlToFileUrl = (url: string | URL | UrlObject, locale?: string) => return { pathname, query, hash } } + const pathParts = removeLangPrefix(pathname || '/', locale) + .split('/') + .slice(1) + const result = parsePathParts({ - locale: locale || defaultLocale || locales[0], + locale: getLocale({ locale }), routeBranch: routesTree, - pathParts: removeLangPrefix(pathname || '/', true, locale), + pathParts, }) if (result) { const { parsedPathParts, additionalQuery } = result diff --git a/src/react/withTranslateRoutes.tsx b/src/react/withTranslateRoutes.tsx index 4b7f2d6..1b7ac6c 100644 --- a/src/react/withTranslateRoutes.tsx +++ b/src/react/withTranslateRoutes.tsx @@ -60,7 +60,7 @@ export const withTranslateRoutes = (...args: (TWrappedAppComponent | TNtrData)[] ) if (nextRouter && !nextRouter.locale) { - const fallbackLocale = ntrData.defaultLocale || ntrData.locales[0] + const fallbackLocale = ntrData.i18n.defaultLocale || ntrData.i18n.locales[0] nextRouter.locale = fallbackLocale console.error(ntrMessagePrefix + `No locale prop in Router: fallback to ${fallbackLocale}.`) } diff --git a/src/shared/isDefaultLocale.ts b/src/shared/isDefaultLocale.ts new file mode 100644 index 0000000..2cdf718 --- /dev/null +++ b/src/shared/isDefaultLocale.ts @@ -0,0 +1,4 @@ +import type { I18NConfig } from 'next/dist/server/config-shared' + +export const isDefaultLocale = (locale: string, i18nConfig: I18NConfig) => + i18nConfig.defaultLocale === locale || !!i18nConfig.domains?.some((domain) => domain.defaultLocale === locale) diff --git a/src/types.ts b/src/types.ts index 04d3f9c..a9ac40c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,25 +1,23 @@ import type { Redirect, Rewrite } from 'next/dist/lib/load-custom-routes' -import type { I18NConfig, NextConfig, NextConfigComplete } from 'next/dist/server/config-shared' +import type { I18NConfig, NextConfig } from 'next/dist/server/config-shared' import type { UrlObject } from 'url' export type Url = UrlObject | string -type TAnyLocale = Exclude export type TReRoutes = { redirects: Redirect[]; rewrites: Rewrite[] } -export type TRouteSegmentPaths = { default: string } & Partial> -export type TRouteSegmentData = string | ({ default?: string } & Partial>) -export type TRouteSegmentsData = Record> -export type TRouteSegment = { +export type TRouteSegmentPaths = { default: string } & Partial> +export type TRouteSegmentData = string | ({ default?: string } & Partial>) +export type TRouteSegmentsData = Record +export type TRouteSegment = { name: string - paths: TRouteSegmentPaths + paths: TRouteSegmentPaths } -export type TRouteBranch = TRouteSegment & { - children?: TRouteBranch[] +export type TRouteBranch = TRouteSegment & { + children?: TRouteBranch[] } export type TNtrData = { debug?: boolean | 'withPrefetch' - defaultLocale: string - locales: string[] + i18n: I18NConfig routesTree: TRouteBranch } @@ -31,7 +29,6 @@ export type NTRConfig = { } export type NextConfigWithNTR = NextConfig & { i18n: I18NConfig; translateRoutes?: NTRConfig } -export type NextConfigCompleteWithNTR = NextConfigComplete & { i18n: I18NConfig; translateRoutes: NTRConfig } declare global { // eslint-disable-next-line no-var diff --git a/tests/fixtures/allReRoutes.json b/tests/fixtures/allReRoutes.json index 4948cbb..f520ea3 100644 --- a/tests/fixtures/allReRoutes.json +++ b/tests/fixtures/allReRoutes.json @@ -8,7 +8,7 @@ }, { "source": "/fr/(acerca-de-nosotros|about)", - "destination": "/fr/a-propos", + "destination": "/a-propos", "locale": false, "permanent": false }, @@ -26,7 +26,7 @@ }, { "source": "/fr/faq", - "destination": "/fr/FAQ", + "destination": "/FAQ", "locale": false, "permanent": false }, @@ -44,7 +44,7 @@ }, { "source": "/fr/(todo|catch-all)/:path+", - "destination": "/fr/tout/:path+", + "destination": "/tout/:path+", "locale": false, "permanent": false }, @@ -62,7 +62,7 @@ }, { "source": "/fr/(todo-o-nada|catch-all-or-none)/:path*", - "destination": "/fr/tout-ou-rien/:path*", + "destination": "/tout-ou-rien/:path*", "locale": false, "permanent": false }, @@ -80,7 +80,7 @@ }, { "source": "/fr/(comunidades|communities)/:tagSlug*", - "destination": "/fr/communautes/:tagSlug*", + "destination": "/communautes/:tagSlug*", "locale": false, "permanent": false }, @@ -104,13 +104,13 @@ }, { "source": "/fr/(community|comunidad)/:communityId(\\d+){-:communitySlug}", - "destination": "/fr/communaute/:communityId-:communitySlug", + "destination": "/communaute/:communityId-:communitySlug", "locale": false, "permanent": false }, { "source": "/fr/community/:communityId/:communitySlug", - "destination": "/fr/communaute/:communityId-:communitySlug", + "destination": "/communaute/:communityId-:communitySlug", "locale": false, "permanent": false }, @@ -140,13 +140,13 @@ }, { "source": "/fr/(community|comunidad)/:communityId(\\d+){-:communitySlug}/(statistics|estadisticas)", - "destination": "/fr/communaute/:communityId-:communitySlug/statistiques", + "destination": "/communaute/:communityId-:communitySlug/statistiques", "locale": false, "permanent": false }, { "source": "/fr/community/:communityId/:communitySlug/statistics", - "destination": "/fr/communaute/:communityId-:communitySlug/statistiques", + "destination": "/communaute/:communityId-:communitySlug/statistiques", "locale": false, "permanent": false }, @@ -176,7 +176,7 @@ }, { "source": "/fr/(my-account|mi-cuenta)/(favoris|favorito|favorites)", - "destination": "/fr/mon-compte/favoris", + "destination": "/mon-compte/favoris", "locale": false, "permanent": false }, @@ -193,7 +193,7 @@ "source": "/en/actualites/:newsPathPart+" }, { - "destination": "/fr/actualites/:newsPathPart+", + "destination": "/actualites/:newsPathPart+", "locale": false, "permanent": false, "source": "/fr/news/:newsPathPart+" diff --git a/tests/fixtures/reRoutesData.json b/tests/fixtures/reRoutesData.json index 667b648..36b267d 100644 --- a/tests/fixtures/reRoutesData.json +++ b/tests/fixtures/reRoutesData.json @@ -1,6 +1,8 @@ { - "locales": ["en", "fr"], - "defaultLocale": "en", + "i18n": { + "locales": ["en", "fr"], + "defaultLocale": "en" + }, "routeSegments": [ { "name": "first", diff --git a/tests/plugin.test.ts b/tests/plugin.test.ts index 4626de4..4cc58c0 100644 --- a/tests/plugin.test.ts +++ b/tests/plugin.test.ts @@ -6,6 +6,7 @@ import path from 'path' import { createNtrData } from '../src/plugin/createNtrData' import { getPageReRoutes, getRouteBranchReRoutes } from '../src/plugin/getRouteBranchReRoutes' +import { setNtrData } from '../src/react/ntrData' import allReRoutes from './fixtures/allReRoutes.json' import reRoutesData from './fixtures/reRoutesData.json' import routesTree from './fixtures/routesTree.json' @@ -18,26 +19,27 @@ declare global { } } } + +const pagesPath = path.resolve(process.cwd(), './tests/fixtures/pages') +const i18n = { locales: ['en', 'fr'], defaultLocale: 'en' } +const ntrData = createNtrData({ i18n, translateRoutes: { debug: true } }, pagesPath) + test('createNtrData.', () => { - const pagesPath = path.resolve(process.cwd(), './tests/fixtures/pages') - const i18n = { locales: ['en', 'fr'], defaultLocale: 'en' } - const ntrData = createNtrData({ i18n, translateRoutes: { debug: true } }, pagesPath) expect(ntrData.routesTree).toEqual(routesTree) - expect(ntrData.locales).toEqual(i18n.locales) - expect(ntrData.defaultLocale).toEqual(i18n.defaultLocale) + expect(ntrData.i18n.locales).toEqual(i18n.locales) + expect(ntrData.i18n.defaultLocale).toEqual(i18n.defaultLocale) expect(ntrData.debug).toBe(true) }) test('getPageReRoutes.', () => { + setNtrData(ntrData) const { reRoutes, ...getPageReRoutesProps } = reRoutesData const pageReRoutes = getPageReRoutes(getPageReRoutesProps) expect(pageReRoutes).toEqual(reRoutes) }) test('getRouteBranchReRoutes.', () => { - const reRoutes = getRouteBranchReRoutes({ - locales: ['en', 'fr', 'es'], - routeBranch: { ...routesTree, paths: { default: '' } }, - }) + setNtrData({ ...ntrData, i18n: { locales: ['en', 'fr', 'es'], defaultLocale: 'fr' } }) + const reRoutes = getRouteBranchReRoutes({ routeBranch: { ...routesTree, paths: { default: '' } } }) expect(reRoutes).toEqual(allReRoutes) }) diff --git a/tests/react/removeLangPrefix.test.ts b/tests/react/removeLangPrefix.test.ts index ed4c57d..086add9 100644 --- a/tests/react/removeLangPrefix.test.ts +++ b/tests/react/removeLangPrefix.test.ts @@ -9,21 +9,36 @@ describe('removeLangPrefix', () => { setEnvData() }) - test('with non default locale prefix and root prefix to array', () => { - expect(removeLangPrefix('/en/root/any/path', true)).toEqual(['any', 'path']) - }) test('with root prefix only on default locale', () => { - setEnvData({ defaultLocale: 'en' }) + setEnvData({ i18n: { defaultLocale: 'en' } }) expect(removeLangPrefix('/root/any/path')).toEqual('/any/path') }) test('with default locale prefix and root prefix', () => { - setEnvData({ defaultLocale: 'en' }) + setEnvData({ i18n: { defaultLocale: 'en' } }) expect(removeLangPrefix('/en/root/any/path')).toEqual('/any/path') }) + test('with default locale prefix and omitted root prefix', () => { + setEnvData({ i18n: { defaultLocale: 'en' } }) + expect(removeLangPrefix('/en/any/path')).toEqual('/any/path') + }) + test('with explicit default locale prefix and omitted root prefix', () => { + setEnvData({ i18n: { defaultLocale: 'en' } }) + expect(removeLangPrefix('/en/any/path', 'en')).toEqual('/any/path') + }) + test('with explicit non default locale prefix and root prefix', () => { + expect(removeLangPrefix('/en/root/any/path', 'en')).toEqual('/any/path') + }) + test('with explicit non default omitted prefix and root prefix', () => { + expect(removeLangPrefix('/root/any/path', 'en')).toEqual('/any/path') + }) + test('with explicit default locale prefix', () => { + expect(removeLangPrefix('/fr/any/path', 'fr')).toEqual('/any/path') + }) test('with non default locale prefix that should have a root prefix but that is not here', () => { expect(removeLangPrefix('/en/any/path')).toEqual('/any/path') }) test('with non default locale prefix that has no root prefix', () => { + setEnvData({ i18n: { defaultLocale: 'en' } }) expect(removeLangPrefix('/fr/any/path')).toEqual('/any/path') }) test('with default locale prefix that has no root prefix', () => { @@ -33,7 +48,7 @@ describe('removeLangPrefix', () => { expect(removeLangPrefix('/any/path')).toEqual('/any/path') }) test('without locale prefix when default locale has a root prefix', () => { - setEnvData({ defaultLocale: 'en' }) + setEnvData({ i18n: { defaultLocale: 'en' } }) expect(removeLangPrefix('/any/path')).toEqual('/any/path') }) }) diff --git a/tests/react/setEnvData.ts b/tests/react/setEnvData.ts index 1b90c74..8529fe3 100644 --- a/tests/react/setEnvData.ts +++ b/tests/react/setEnvData.ts @@ -1,9 +1,13 @@ +import { I18NConfig } from 'next/dist/server/config-shared' + import { TNtrData } from '../../src/types' import routesTree from '../fixtures/routesTree.json' const defaultNtrData = { - defaultLocale: 'fr', - locales: ['fr', 'en', 'es', 'pt'], + i18n: { + defaultLocale: 'fr', + locales: ['fr', 'en', 'es', 'pt'], + }, routesTree, } @@ -13,9 +17,13 @@ declare global { } } -export const setEnvData = (ntrData: Partial = {}) => { +export const setEnvData = (ntrData: Omit, 'i18n'> & { i18n?: Partial } = {}) => { window.__NEXT_TRANSLATE_ROUTES_DATA = { ...defaultNtrData, ...ntrData, + i18n: { + ...defaultNtrData.i18n, + ...ntrData?.i18n, + }, } }