UNPKG

next-intl

Version:

Internationalization (i18n) for Next.js

144 lines (137 loc) 4.81 kB
import { getSortedPathnames, matchesPathname, isLocalizableHref, prefixPathname, getLocalizedTemplate, normalizeTrailingSlash, getLocalePrefix } from '../../shared/utils.js'; // Minor false positive: A route that has both optional and // required params will allow optional params. // For `Link` // For `getPathname` (hence also its consumers: `redirect`, `useRouter`, …) function normalizeNameOrNameWithParams(href) { return typeof href === 'string' ? { pathname: href } : href; } function serializeSearchParams(searchParams) { function serializeValue(value) { return String(value); } const urlSearchParams = new URLSearchParams(); for (const [key, value] of Object.entries(searchParams)) { if (Array.isArray(value)) { value.forEach(cur => { urlSearchParams.append(key, serializeValue(cur)); }); } else { urlSearchParams.set(key, serializeValue(value)); } } return '?' + urlSearchParams.toString(); } function compileLocalizedPathname({ pathname, locale, params, pathnames, query }) { function getNamedPath(value) { let namedPath = pathnames[value]; if (!namedPath) { // Unknown pathnames namedPath = value; } return namedPath; } function compilePath(namedPath, internalPathname) { const template = getLocalizedTemplate(namedPath, locale, internalPathname); let compiled = template; if (params) { Object.entries(params).forEach(([key, value]) => { let regexp, replacer; if (Array.isArray(value)) { regexp = `(\\[)?\\[...${key}\\](\\])?`; replacer = value.map(v => String(v)).join('/'); } else { regexp = `\\[${key}\\]`; replacer = String(value); } compiled = compiled.replace(new RegExp(regexp, 'g'), replacer); }); } // Clean up optional catch-all segments that were not replaced compiled = compiled.replace(/\[\[\.\.\..+\]\]/g, ''); compiled = normalizeTrailingSlash(compiled); if (compiled.includes('[')) { // Next.js throws anyway, therefore better provide a more helpful error message throw new Error(`Insufficient params provided for localized pathname.\nTemplate: ${template}\nParams: ${JSON.stringify(params)}`); } if (query) { compiled += serializeSearchParams(query); } return compiled; } if (typeof pathname === 'string') { const namedPath = getNamedPath(pathname); const compiled = compilePath(namedPath, pathname); return compiled; } else { const { pathname: internalPathname, ...rest } = pathname; const namedPath = getNamedPath(internalPathname); const compiled = compilePath(namedPath, internalPathname); const result = { ...rest, pathname: compiled }; return result; } } function getRoute(locale, pathname, pathnames) { const sortedPathnames = getSortedPathnames(Object.keys(pathnames)); const decoded = decodeURI(pathname); for (const internalPathname of sortedPathnames) { const localizedPathnamesOrPathname = pathnames[internalPathname]; if (typeof localizedPathnamesOrPathname === 'string') { const localizedPathname = localizedPathnamesOrPathname; if (matchesPathname(localizedPathname, decoded)) { return internalPathname; } } else { if (matchesPathname(getLocalizedTemplate(localizedPathnamesOrPathname, locale, internalPathname), decoded)) { return internalPathname; } } } return pathname; } function getBasePath(pathname, windowPathname = window.location.pathname) { if (pathname === '/') { return windowPathname; } else { return windowPathname.replace(pathname, ''); } } function applyPathnamePrefix(pathname, locale, routing, force) { const { mode } = routing.localePrefix; let shouldPrefix; if (force !== undefined) { shouldPrefix = force; } else if (isLocalizableHref(pathname)) { if (mode === 'always') { shouldPrefix = true; } else if (mode === 'as-needed') { shouldPrefix = routing.domains ? // Since locales are unique per domain, any locale that is a // default locale of a domain doesn't require a prefix !routing.domains.some(cur => cur.defaultLocale === locale) : locale !== routing.defaultLocale; } } return shouldPrefix ? prefixPathname(getLocalePrefix(locale, routing.localePrefix), pathname) : pathname; } function validateReceivedConfig(config) { if (config.localePrefix?.mode === 'as-needed' && !('defaultLocale' in config)) { throw new Error("`localePrefix: 'as-needed' requires a `defaultLocale`."); } } export { applyPathnamePrefix, compileLocalizedPathname, getBasePath, getRoute, normalizeNameOrNameWithParams, serializeSearchParams, validateReceivedConfig };