diff --git a/src/client/app/router.ts b/src/client/app/router.ts index 3a7d8cb5..a9829f59 100644 --- a/src/client/app/router.ts +++ b/src/client/app/router.ts @@ -74,7 +74,14 @@ export function createRouter( let latestPendingPath: string | null = null - async function loadPage(href: string, scrollPosition = 0, isRetry = false) { + async function loadPage( + href: string, + scrollPosition = 0, + isRetry = false, + alreadyTriedLoadingRootFallback = false + ) { + let fallbackLoaded = false + if ((await router.onBeforePageLoad?.(href)) === false) return const targetLoc = new URL(href, fakeHost) const pendingPath = (latestPendingPath = targetLoc.pathname) @@ -149,13 +156,98 @@ export function createRouter( } catch (e) {} } - if (latestPendingPath === pendingPath) { + if (!alreadyTriedLoadingRootFallback) { + fallbackLoaded = await loadPageFallback() + } + + if (!fallbackLoaded && latestPendingPath === pendingPath) { latestPendingPath = null route.path = inBrowser ? pendingPath : withBase(pendingPath) route.component = fallbackComponent ? markRaw(fallbackComponent) : null route.data = notFoundPageData } } + + // If failed to find the page, maybe it's not translated yet! if so, please fallback :) + async function loadPageFallback() { + const locales = siteDataRef.value.locales + if (!locales) return fallbackLoaded + const namedLocales = Object.fromEntries( + Object.entries(siteDataRef.value.locales).filter( + ([name]) => name !== 'root' + ) + ) + if (!Object.entries(namedLocales).length) return fallbackLoaded + + const langNames = Object.keys(namedLocales) + + const failedLang = langNames.find( + (lang) => + pendingPath === `/${lang}` || pendingPath.startsWith(`/${lang}/`) + ) + + if (failedLang) { + const fallbackLang = + getFailedLangFallbackLang() ?? getCustomFallbackLang() + + if (fallbackLang) { + await loadPage( + pendingPath.replace(`/${failedLang}`, `/${fallbackLang}`) + ) + return fallbackLoaded + } else { + await loadPage(pendingPath.replace(`/${failedLang}`, '')) + return fallbackLoaded + } + } else { + const rootRouteFallbackPath = getRootRouteFallbackPath() + + if (rootRouteFallbackPath) { + await loadPage(rootRouteFallbackPath, 0, true, true) + } + return fallbackLoaded + } + + function getFailedLangFallbackLang() { + const failedLangFallbackLang = locales[failedLang!]?.fallback + if (!failedLangFallbackLang) return + if (!langNames.includes(failedLangFallbackLang)) { + console.warn( + `No keys in 'locales' with the name of 'locales.${failedLang}.fallback'! RECIVED '${failedLangFallbackLang}'` + ) + return + } + + return failedLangFallbackLang + } + + function getCustomFallbackLang() { + const customFallbackLang = Object.entries(namedLocales).filter( + ([_, values]) => values.useAsFallback + )?.[0]?.[0] + if (customFallbackLang && customFallbackLang !== failedLang) { + return customFallbackLang + } + } + + function getRootRouteFallbackPath() { + const fallbackLang = locales['root']?.fallback + + if (!fallbackLang) return + if (!langNames.includes(fallbackLang)) { + console.warn( + `No keys in 'locales' with the name of 'locales.root.fallback'! RECIVED '${fallbackLang}'` + ) + return + } + + return pendingPath === '/' + ? `/${fallbackLang}` + : `/${fallbackLang}${ + pendingPath.startsWith('/') ? pendingPath : `/${pendingPath}` + }` + } + } } if (inBrowser) { diff --git a/types/shared.d.ts b/types/shared.d.ts index 6fdf919b..d350f5e9 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -156,7 +156,12 @@ export interface LocaleSpecificConfig { export type LocaleConfig = Record< string, - LocaleSpecificConfig & { label: string; link?: string } + LocaleSpecificConfig & { + label: string + link?: string + fallback?: string + useAsFallback?: boolean + } > // Manually declaring all properties as rollup-plugin-dts