diff --git a/components/use-locales-map.js b/components/use-locales-map.js index cfc01ef5..1bdaf7b9 100644 --- a/components/use-locales-map.js +++ b/components/use-locales-map.js @@ -5,37 +5,64 @@ import { useRouter } from "next/router"; * @typedef {DefaultLocale | "zh-CN" | "es-ES" | "pt-BR" | "ja" | "ko" | "ru"} Locale * @typedef {{locale?: Locale | undefined; locales?: Locale[] | undefined; defaultLocale?: DefaultLocale | undefined}} TypedRouter * @typedef {Omit & TypedRouter} NextRouter + */ + +/** * @template T * @type {(localesMap: Record) => T} */ export default function useLocalesMap(localesMap) { /** @type {NextRouter} */ const router = useRouter(); - const { locale, defaultLocale } = router; + const { locale, locales, defaultLocale } = router; + if (!localesMap) { - throw new Error("Pass a locales map as argument to useLocalesMap"); + throw new Error("Pass a locales map as argument to useLocalesMap hook."); } - if (!isObject(localesMap)) { - throw new Error("Locales map must be an object"); + if (typeof localesMap !== "object") { + localesMapError( + localesMap, + `Locales map must be an object, but you passed ${typeof localesMap}.` + ); + } + + if (Array.isArray(localesMap)) { + localesMapError( + localesMap, + "Locales map must be an object, but you passed an array." + ); } if (!localesMap.hasOwnProperty(defaultLocale)) { - throw new Error( + localesMapError( + localesMap, `Locales map must contain default locale "${defaultLocale}"` ); } - if ( - localesMap.hasOwnProperty(locale) && - typeof localesMap[locale] !== typeof localesMap[defaultLocale] - ) { - throw new Error( - `Invalid locales map: Shape of "${locale}" must be the same as "${defaultLocale}"` - ); + for (const key in localesMap) { + if (!locales.includes(key)) { + const list = locales.map((l) => `"${l}"`).join(", "); + + localesMapError( + localesMap, + `"${key}" is not a valid locale.`, + `Available locales are defined in "next.config.js": ${list}.` + ); + } + + if (typeof localesMap[key] !== typeof localesMap[defaultLocale]) { + localesMapError( + localesMap, + `Shape of "${key}" must be the same as "${defaultLocale}"` + ); + } } - if (["string", "number", "symbol"].includes(typeof localesMap[defaultLocale])) { + if ( + ["string", "number", "symbol"].includes(typeof localesMap[defaultLocale]) + ) { return localesMap[locale] || localesMap[defaultLocale]; } @@ -76,3 +103,17 @@ function mergeDeep(target, ...sources) { return mergeDeep(target, ...sources); } + +/** + * Throw an error with a formatted message. + * @template T + * @param {Record} localesMap + * @param {string[]} args + */ +export function localesMapError(localesMap, ...args) { + throw new Error( + ["Invalid locales map", JSON.stringify(localesMap, null, 2), ...args].join( + "\n" + ) + ); +}