next-intl
Version:
Internationalization (i18n) for Next.js
144 lines (137 loc) • 4.81 kB
JavaScript
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 };