next-translate-routes-multi-domain
Version:
Flexible and translated routes for Next.js without custom server
136 lines (117 loc) • 4.44 kB
text/typescript
import { normalizePathTrailingSlash } from 'next/dist/client/normalize-trailing-slash'
import { parse as parsePathPattern, compile as compilePath } from 'path-to-regexp'
import { format as formatUrl, UrlObject } from 'url'
import { ignoreSegmentPathRegex } from '../shared/regex'
import { ntrMessagePrefix } from '../shared/withNtrPrefix'
import type { TRouteBranch } from '../types'
import { getNtrData } from './ntrData'
import { urlToUrlObject } from './urlToUrlObject'
const getPatternFromRoutePaths = (routeBranch: TRouteBranch, locale: string) => {
const pattern = routeBranch.paths[locale] || routeBranch.paths.default
return ignoreSegmentPathRegex.test(pattern) ? '' : pattern
}
/** Add `/` prefix only if `pattern` is not an empty string and does not already have it */
const addSlashPrefix = (path: string) => (path === '' || path.startsWith('/') ? path : `/${path}`)
const getPathPatternPart = ({
routeBranch,
locale,
isLastPathPart,
}: {
routeBranch: TRouteBranch
locale: string
isLastPathPart: boolean
}) => {
let pattern = getPatternFromRoutePaths(routeBranch, locale)
if (isLastPathPart) {
if (routeBranch.children?.length) {
const indexChild = routeBranch.children.find((child) => child.name === 'index' && !child.children?.length)
if (!indexChild) {
throw new Error(`No index file found in "${routeBranch.name}" folder.`)
}
const indexChildPattern = getPatternFromRoutePaths(indexChild, locale)
if (indexChildPattern && indexChildPattern !== 'index') {
pattern = pattern + addSlashPrefix(indexChildPattern)
}
} else if (pattern === 'index' && routeBranch.name === 'index') {
pattern = ''
}
}
return addSlashPrefix(pattern)
}
/**
* Get path pattern (path-to-regexp synthax) from file path parts
*
* Ex: `/[dynamic]/path` => `/:dynamic/path`
*/
export const getTranslatedPathPattern = ({
routeBranch,
pathParts,
locale,
}: {
routeBranch: TRouteBranch
/** Remaining path parts after the `routeBranch` path parts */
pathParts: string[]
locale: string
}): string => {
const isLastPathPart = pathParts.length === 0
const currentPathPatternPart = getPathPatternPart({ routeBranch, locale, isLastPathPart })
if (isLastPathPart) {
return currentPathPatternPart
}
const nextPathPart = pathParts[0]
const remainingPathParts = pathParts.slice(1)
for (const child of routeBranch.children || []) {
if (
// child name must match nextPathPart
child.name === nextPathPart &&
// child.children must be coherent with remaining path parts is case a file and and folder share the same name
(remainingPathParts.length === 0 || child.children?.length)
) {
return (
currentPathPatternPart +
getTranslatedPathPattern({
routeBranch: child,
pathParts: remainingPathParts,
locale,
})
)
}
}
throw new Error(`No "/${pathParts.join('/')}" page found in /${routeBranch.name} folder.`)
}
/**
* Translate Next file url into translated string url
*
* @param url Next default file url
* @param locale string
*/
export const fileUrlToUrl = (url: UrlObject | URL | string, locale: string) => {
const { routesTree, defaultLocale } = getNtrData()
const { pathname, query, hash } = urlToUrlObject(url)
if (!routesTree.children) {
throw new Error('No page found. You probably need to add the pageDirectory option in your translateRoutes config.')
}
try {
const pathParts = (pathname || '/')
.replace(/^\/|\/$/g, '')
.split('/')
.filter(Boolean)
// Create a new query object that we can mutate
const newQuery = { ...query }
const pathPattern = getTranslatedPathPattern({ routeBranch: routesTree, pathParts, locale })
const newPathname = normalizePathTrailingSlash(compilePath(pathPattern)(query))
for (const patterToken of parsePathPattern(pathPattern)) {
if (typeof patterToken === 'object' && patterToken.name) {
delete newQuery[patterToken.name]
}
}
const hasLocalePrefix = locale !== defaultLocale && process.env.NEXT_PUBLIC_HAS_LOCALE_PREFIX !== 'false'
return `${hasLocalePrefix ? `/${locale}` : ''}${formatUrl({
pathname: newPathname,
query: newQuery,
hash,
})}`
} catch (error) {
throw new Error(ntrMessagePrefix + `No page found for pathname ${pathname}`, { cause: error })
}
}