UNPKG

next-export-i18n

Version:

Internationalize next.js with true support for next export

329 lines (316 loc) 13.4 kB
Object.defineProperty(exports, '__esModule', { value: true }); var router = require('next/router'); var React = require('react'); var I18N = require('./../../i18n/index.js'); var Mustache = require('mustache'); var Link = require('next/link'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var Mustache__default = /*#__PURE__*/_interopDefaultLegacy(Mustache); var Link__default = /*#__PURE__*/_interopDefaultLegacy(Link); var LanguageDataStore; (function (LanguageDataStore) { LanguageDataStore["QUERY"] = "query"; LanguageDataStore["LOCAL_STORAGE"] = "localStorage"; })(LanguageDataStore || (LanguageDataStore = {})); /** * The entry files for the separated hooks */ /** * Calculates the default language from the user's language setting and the i18n object. * In case there is a language set in the browser which is also available as translation, * override the default language setting in the config file. * @returns string indicating the default language to use */ const getDefaultLanguage = (userI18n) => { let browserLang = ""; if (userI18n.useBrowserDefault && typeof window !== "undefined" && window && window.navigator && (window.navigator.languages || window.navigator.language)) { browserLang = ((window.navigator.languages && window.navigator.languages[0]) || window.navigator.language) .split("-")[0] .toLowerCase(); } if (userI18n.useBrowserDefault && browserLang && userI18n.translations[browserLang]) { return browserLang; } return userI18n.defaultLang; }; /** * Imports the translations addes by the user in "i18n/index", * veryfies the tranlsations and exposes them * to the custom hooks * @returns the translations and the default language as defined in "i18n/index" */ const i18n = () => { // cast to be typsafe const userI18n = I18N; // Set "query" as default if (!userI18n.languageDataStore) { userI18n.languageDataStore = LanguageDataStore.QUERY; } if (Object.keys(userI18n.translations).length < 1) { throw new Error(`Missing translations. Did you import and add the tranlations in 'i18n/index.js'?`); } if (userI18n.defaultLang.length === 0) { throw new Error(`Missing default language. Did you set 'defaultLang' in 'i18n/index.js'?`); } if (!userI18n.translations[userI18n.defaultLang]) { throw new Error(`Invalid default language '${userI18n.defaultLang}'. Check your 'defaultLang' in 'i18n/index.js'?`); } // if (!Object.values(LanguageDataStore).includes(userI18n.languageDataStore)) { // throw new Error( // `Invalid language detection '${userI18n.languageDataStore}'. Check your 'languageDataStore' in 'i18n/index.js'?` // ); // } userI18n.defaultLang = getDefaultLanguage(userI18n); return userI18n; }; /** * Returns a react-state containing the currently selected language. * @returns [lang as string, setLang as SetStateAction] a react-state containing the currently selected language */ function useSelectedLanguage() { const i18nObj = i18n(); const defaultLang = i18nObj.defaultLang; const translations = i18nObj.translations; const languageDataStore = i18nObj.languageDataStore; const router$1 = router.useRouter(); const [lang, setLang] = React.useState(defaultLang); // set the language if the localStorage value has changed const handleLocalStorageUpdate = () => { const storedLang = window.localStorage.getItem("next-export-i18n-lang"); if (languageDataStore === LanguageDataStore.LOCAL_STORAGE && storedLang && storedLang !== lang && translations && translations[storedLang]) { setLang(storedLang); } }; // Listen for local-storage changes React.useEffect(() => { handleLocalStorageUpdate(); document.addEventListener("localStorageLangChange", () => { handleLocalStorageUpdate(); }); return () => { document.removeEventListener("localStorageLangChange", handleLocalStorageUpdate); }; }, [handleLocalStorageUpdate]); // set the language if the query parameter changes React.useEffect(() => { const storedLang = router$1.query?.lang; if (languageDataStore === LanguageDataStore.QUERY && storedLang && storedLang !== lang && translations && translations[storedLang]) { setLang(storedLang); } }, [lang, router$1.query.lang, translations, setLang]); return { lang, setLang }; } /** * Returns a react-state which is a queryObject containing an exsisting query and a query string with the current selected * language (or the passed forced language). * Reason: We want to preserve an existing query string. * Usage: LanguageSwitcher with forceLang param and all links without forceLang param * @param [forceLang] string to override the selected language * @returns queryObject react-state as ParsedUrlQueryInput */ function useLanguageQuery(forceLang) { const { lang } = useSelectedLanguage(); const router$1 = router.useRouter(); const [value, setValue] = React.useState(); // keep passed parameters const passedQuery = React.useMemo(() => { if (!router$1.query) { return {}; } return { ...router$1.query }; }, [router$1.query]); // set lang if one of the dependencies is changing React.useEffect(() => { setValue({ ...passedQuery, lang: forceLang || lang || passedQuery['lang'], }); }, [forceLang, lang, passedQuery]); return [value]; } /** * Returns a boolean react-state indicating if the current selected language equals the one passed to the hook. * @param currentLang string the language to check. Needs to equal the key in i18n/index. * @returns boolean react-state */ function useLanguageSwitcherIsActive(currentLang) { const [isActive, setIsActive] = React.useState(false); const i18nObj = i18n(); const router$1 = router.useRouter(); const defaultLang = i18nObj.defaultLang; const languageDataStore = i18nObj.languageDataStore; React.useEffect(() => { if (languageDataStore === LanguageDataStore.QUERY) { let current; if (!router$1.query || !router$1.query.lang) { current = defaultLang === currentLang; } else { current = router$1.query?.lang === currentLang; } setIsActive(current); } }, [currentLang, defaultLang, router$1.query]); const handleLocalStorageUpdate = React.useCallback(() => { if (languageDataStore === LanguageDataStore.LOCAL_STORAGE) { let current; const localStorageLanguage = window.localStorage.getItem("next-export-i18n-lang"); current = defaultLang === currentLang; if (localStorageLanguage) { current = localStorageLanguage === currentLang; } setIsActive(current); } }, [languageDataStore, currentLang, defaultLang]); // Listen for local-storage changes React.useEffect(() => { handleLocalStorageUpdate(); document.addEventListener("localStorageLangChange", () => { handleLocalStorageUpdate(); }); return () => { document.removeEventListener("localStorageLangChange", handleLocalStorageUpdate); }; }, [handleLocalStorageUpdate]); return { isActive }; } /** * Provides the t() function which returns the value stored for this given key (e.g. "i18n.ui.headline") * in the translation file. * The return value can be a string, a number, an array or an object. * In case there is no entry for this key, it returns the key. * @returns t(key: string): any function */ const useTranslation = () => { let i18nObj; i18nObj = i18n(); const translations = i18nObj.translations; const { lang } = useSelectedLanguage(); return { /** * Returns the value stored for this given key (e.g. "i18n.ui.headline") in the translation file. * The return value can be a string, a number, an array or an object. * In case there is no entry for this key, it returns the key. * @param key the key for looking up the translation * @param view the mustache view for interpolating the template string * @returns the value stored for this key, could be a string, a number, an array or an object */ t: (key, view) => { let value = key .split(".") .reduce((previous, current) => (previous && previous[current]) || null, translations[lang]); let translation = value || key; try { return Mustache__default["default"].render(translation, view); } catch (e) { return translation; } }, }; }; /** * Simple component for switching the language. * Set the "lang" query parameter on click while preserving the current query parameters * Style it using the * - [data-language-switcher] * - [data-is-current="true"] * attribute selectors or create your own component. * @param lang string the language to switch to. Needs to equal the key in i18n/index. * @param [children] React.nodes * @param [shallow] enable or disable shallow routing, @see https://nextjs.org/docs/routing/shallow-routing */ const LanguageSwitcher = ({ lang, children, shallow = false, }) => { // state indicating if this component's target language matches the currently selected const { isActive: languageSwitcherIsActive } = useLanguageSwitcherIsActive(lang); // necessary for updating the router's query parameter inside the click handler const router$1 = router.useRouter(); const [query] = useLanguageQuery(lang); const i18nObj = i18n(); const languageDataStore = i18nObj.languageDataStore; /** * Updates the router with the currently selected language */ const handleLanguageChange = () => { if (languageDataStore === LanguageDataStore.QUERY) { router$1.push({ pathname: router$1.pathname, query: query, }, undefined, { shallow: shallow }); } if (languageDataStore === LanguageDataStore.LOCAL_STORAGE) { window.localStorage.setItem("next-export-i18n-lang", lang); const event = new Event("localStorageLangChange"); document.dispatchEvent(event); } }; // use React.cloneElement to manipulate properties if (React__default["default"].isValidElement(children)) { const childElement = children; return React__default["default"].cloneElement(childElement, { onClick: () => { if (childElement.props && typeof childElement.props.onClick === "function") { childElement.props.onClick(); } // set the language handleLanguageChange(); }, "data-language-switcher": "true", // set the current status "data-is-current": languageSwitcherIsActive, role: "button", "aria-label": `set language to ${lang}`, }); } else { return (React__default["default"].createElement("span", { role: "button", "aria-label": `set language to ${lang}`, "data-language-switcher": "true", "data-is-current": languageSwitcherIsActive, onClick: () => { // set the language handleLanguageChange(); } }, children)); } }; function LinkWithLocale(props) { const { lang } = useSelectedLanguage(); const { href, ...rest } = props; const link = React.useMemo(() => { const i18nObj = i18n(); const languageDataStore = i18nObj.languageDataStore; const inputHref = href.toString(); if (inputHref.includes("?lang=") || inputHref.includes("&lang=") || languageDataStore === LanguageDataStore.LOCAL_STORAGE) { return inputHref; } if (inputHref.includes("?")) { return `${inputHref}&lang=${lang}`; } else { return `${inputHref}?lang=${lang}`; } }, [href, lang]); return React__default["default"].createElement(Link__default["default"], { href: link, ...rest }); } exports.LanguageSwitcher = LanguageSwitcher; exports.LinkWithLocale = LinkWithLocale; exports["default"] = i18n; exports.useLanguageQuery = useLanguageQuery; exports.useLanguageSwitcherIsActive = useLanguageSwitcherIsActive; exports.useSelectedLanguage = useSelectedLanguage; exports.useTranslation = useTranslation;