import { DEFAULT_LOCALE } from "@config"; import { partitionBy } from "@utils"; import type { IPage } from "@interfaces/IPage"; import { MDXInstance } from "astro"; const storageKey = "preferredLanguage"; /** * This function will look for an existing language preference in * localStorage. If there is none, it will guess prefered language * from Browser settings. If this doesn't work it will use a default * value. */ export const getOrGuessInitialLanguageFromBrowser = () => { let preferredLanguage = localStorage.getItem(storageKey)?.toLowerCase(); if (!preferredLanguage) { if (/^en\b/.test(navigator.language.toLowerCase())) { preferredLanguage = "en"; } else { // Use German as default for unknown language browser settings preferredLanguage = DEFAULT_LOCALE; } localStorage.setItem(storageKey, preferredLanguage); } return preferredLanguage; }; export const setLanguage = (newLanguage: string) => { localStorage.setItem(storageKey, newLanguage); }; export const getLanguage = () => { return localStorage.getItem(storageKey); }; export const buildTranslationRoutes = ( allPages: MDXInstance<IPage>[], baseUrl, allLocales ) => { // Split pages in two, just by the existence of a `translated` frontmatter property const [translatedPages, unTranslatedPages] = partitionBy(allPages, (page) => Boolean(page.frontmatter?.translated) ); // The pages w/o the frontmatter property can still be the "translation targets" // of another page that has the `translated` property const unTranslatedAndNoTargetPages = unTranslatedPages.filter( (untranslatedPage) => !translatedPages.some((translatedPage) => untranslatedPage.url.includes(translatedPage.frontmatter.translated) ) ); const translatedRoutes = translatedPages .flatMap((translatedPage) => buildTranslatedRoutesFromPage(translatedPage, baseUrl, allLocales) ) .filter(Boolean); const fallbackRoutes = unTranslatedAndNoTargetPages .flatMap((untranslatedPage) => buildFallbackRouteForPage(untranslatedPage, baseUrl, allLocales) ) .filter(Boolean); return [...translatedRoutes, ...fallbackRoutes]; }; /** * Build a fallback route for a given page (this is a route that redirects to the home page, e.g. if no translated content is available) */ const buildFallbackRouteForPage = ( page: MDXInstance<IPage>, baseUrl: string, allLocales: string[], targetOverride?: string ) => { const urlPartsWithoutBase = page?.url ?.replace(baseUrl, "") .split("/") .filter(Boolean); if (urlPartsWithoutBase && urlPartsWithoutBase.length > 0) { const localeFromUrl = urlPartsWithoutBase && urlPartsWithoutBase[0]; const otherLocale = allLocales.filter( (locale) => locale !== localeFromUrl )[0]; const middleParts = urlPartsWithoutBase?.slice(1, -1); const slugFromUrl = urlPartsWithoutBase.at(-1); const firstRedirect = { from: `${otherLocale}${ Boolean(middleParts.length) ? `/${middleParts?.join("/")}` : "" }/${slugFromUrl}`, to: targetOverride || "/", }; return [ { params: { slug: firstRedirect.from, }, props: { newTarget: firstRedirect.to, }, }, ]; } }; /** * Build translated routes for a given page. This results in two routes (back and forth) */ const buildTranslatedRoutesFromPage = ( page: MDXInstance<IPage>, baseUrl: string, allLocales: string[] ) => { const urlPartsWithoutBase = page?.url ?.replace(baseUrl, "") .split("/") .filter(Boolean); if (urlPartsWithoutBase && urlPartsWithoutBase.length > 0) { const localeFromUrl = urlPartsWithoutBase && urlPartsWithoutBase[0]; const otherLocale = allLocales.filter( (locale) => locale !== localeFromUrl )[0]; const middleParts = urlPartsWithoutBase?.slice(1, -1); const slugFromUrl = urlPartsWithoutBase.at(-1); const otherSlugWithoutLocale = page.frontmatter?.translated ?.split("/") .filter(Boolean) .slice(1) .join("/"); if (otherSlugWithoutLocale) { const firstRedirect = { from: `${localeFromUrl}/${otherSlugWithoutLocale}`, to: page.url, }; const secondRedirect = { from: `${otherLocale}${ Boolean(middleParts.length) ? `/${middleParts?.join("/")}` : "" }/${slugFromUrl}`, to: `${baseUrl}/${otherLocale}/${otherSlugWithoutLocale}`, }; return [ { params: { slug: firstRedirect.from, }, props: { newTarget: firstRedirect.to, }, }, { params: { slug: secondRedirect.from, }, props: { newTarget: secondRedirect.to }, }, ]; } } };