New options, fixes and improvements

pull/3312/head
Babak Farkhoopak 7 months ago
parent 2bd4666e36
commit 19524cd5d4

@ -77,14 +77,7 @@ export function createRouter(
let latestPendingPath: string | null = null let latestPendingPath: string | null = null
async function loadPage( async function loadPage(href: string, scrollPosition = 0, isRetry = false) {
href: string,
scrollPosition = 0,
isRetry = false,
alreadyTriedLoadingRootFallback = false
) {
let fallbackLoaded = false
if ((await router.onBeforePageLoad?.(href)) === false) return if ((await router.onBeforePageLoad?.(href)) === false) return
const targetLoc = new URL(href, fakeHost) const targetLoc = new URL(href, fakeHost)
const pendingPath = (latestPendingPath = targetLoc.pathname) const pendingPath = (latestPendingPath = targetLoc.pathname)
@ -159,11 +152,11 @@ export function createRouter(
} catch (e) {} } catch (e) {}
} }
if (!alreadyTriedLoadingRootFallback) { if (siteDataRef.value.localesFallback) {
fallbackLoaded = await loadPageFallback() await loadFallback()
} }
if (!fallbackLoaded && latestPendingPath === pendingPath) { if (latestPendingPath === pendingPath) {
latestPendingPath = null latestPendingPath = null
route.path = inBrowser ? pendingPath : withBase(pendingPath) route.path = inBrowser ? pendingPath : withBase(pendingPath)
route.component = fallbackComponent ? markRaw(fallbackComponent) : null route.component = fallbackComponent ? markRaw(fallbackComponent) : null
@ -171,84 +164,74 @@ export function createRouter(
} }
} }
// If failed to find the page, maybe it's not translated yet! if so, please fallback :) async function loadFallback() {
async function loadPageFallback() {
const locales = siteDataRef.value.locales 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) for (const [key, value] of Object.entries(locales)) {
if (!value.fallback) continue
const failedLang = langNames.find( if (value.fallback === 'root') {
(lang) => throw new Error(
pendingPath === `/${lang}` || pendingPath.startsWith(`/${lang}/`) `Invalid Vitepress Config: A locale (${key}), cannot fallback to (root).`
)
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 { if (key === value.fallback) {
const rootRouteFallbackPath = getRootRouteFallbackPath() throw new Error(
`Invalid Vitepress Config: A locale (${key}), cannot have a fallback to itself.`
if (rootRouteFallbackPath) { )
await loadPage(rootRouteFallbackPath, 0, true, true)
} }
return fallbackLoaded if (!Object.keys(locales).includes(value.fallback)) {
} throw new Error(
`Invalid Vitepress Config: A locale (${key}), cannot have a fallback to a non existing locale.`
function getFailedLangFallbackLang() {
const failedLangFallbackLang = locales[failedLang!]?.fallback
if (!failedLangFallbackLang) return
if (!langNames.includes(failedLangFallbackLang)) {
console.warn(
`Invalid value received in "VitePress Config" > "locales.${failedLang}.fallback". "${failedLangFallbackLang}" is not a valid value.`
) )
return
} }
}
return failedLangFallbackLang // If the length is less than 2, it means there are no alternative locales to fallback to.
if (!locales || Object.keys(locales).length < 2) {
return
}
const nonRootLocales = Object.fromEntries(
Object.entries(locales).filter(([name]) => name !== 'root')
)
const failedLocaleKey =
Object.keys(nonRootLocales).find(
(lang) =>
pendingPath === `/${lang}` || pendingPath.startsWith(`/${lang}/`)
) || 'root'
if (failedLocaleKey !== 'root') {
const fallbackLang =
locales[failedLocaleKey].fallback ?? getCustomFallbackLang()
await loadPage(
pendingPath.replace(
`/${failedLocaleKey}`,
fallbackLang ? `/${fallbackLang}` : ''
),
scrollPosition,
true
)
} else {
const fallbackPath = getRootLocaleFallbackPath()
if (!fallbackPath) return
await loadPage(fallbackPath, scrollPosition, true)
} }
function getCustomFallbackLang() { function getCustomFallbackLang() {
const customFallbackLang = Object.entries(namedLocales).filter( const customFallbackLang = siteDataRef.value.localesDefaultFallback
([_, values]) => values.useAsFallback if (customFallbackLang && customFallbackLang !== failedLocaleKey) {
)?.[0]?.[0]
if (customFallbackLang && customFallbackLang !== failedLang) {
return customFallbackLang return customFallbackLang
} }
} }
function getRootRouteFallbackPath() { function getRootLocaleFallbackPath() {
const fallbackLang = locales['root']?.fallback const fallbackLang = locales['root'].fallback
if (!fallbackLang) return if (!fallbackLang) return
if (!langNames.includes(fallbackLang)) { if (pendingPath === '/') return `/${fallbackLang}`
console.warn( const pathDivider = pendingPath.startsWith('/') ? '' : '/'
`Invalid value received in "VitePress Config" > "locales.root.fallback". "${fallbackLang}" is not a valid value.` return `/${fallbackLang}${pathDivider}${pendingPath}`
)
return
}
return pendingPath === '/'
? `/${fallbackLang}`
: `/${fallbackLang}${
pendingPath.startsWith('/') ? pendingPath : `/${pendingPath}`
}`
} }
} }
} }

@ -252,6 +252,8 @@ export async function resolveSiteData(
appearance: userConfig.appearance ?? true, appearance: userConfig.appearance ?? true,
themeConfig: userConfig.themeConfig || {}, themeConfig: userConfig.themeConfig || {},
locales: userConfig.locales || {}, locales: userConfig.locales || {},
localesFallback: userConfig.localesFallback ?? true,
localesDefaultFallback: userConfig.localesDefaultFallback,
scrollOffset: userConfig.scrollOffset ?? 134, scrollOffset: userConfig.scrollOffset ?? 134,
cleanUrls: !!userConfig.cleanUrls, cleanUrls: !!userConfig.cleanUrls,
contentProps: userConfig.contentProps contentProps: userConfig.contentProps

@ -69,6 +69,16 @@ export interface UserConfig<ThemeConfig = any>
locales?: LocaleConfig<ThemeConfig> locales?: LocaleConfig<ThemeConfig>
/**
* If a page isn't found in the current language, allow switching to another language as a backup.
*/
localesFallback?: boolean
/**
* Use a custom locale key to be used as a default fallback for all locales. Default is root.
*/
localesDefaultFallback?: string
router?: { router?: {
prefetchLinks?: boolean prefetchLinks?: boolean
} }

12
types/shared.d.ts vendored

@ -124,6 +124,14 @@ export interface SiteData<ThemeConfig = any> {
| string[] | string[]
| { selector: string | string[]; padding: number } | { selector: string | string[]; padding: number }
locales: LocaleConfig<ThemeConfig> locales: LocaleConfig<ThemeConfig>
/**
* If a page isn't found in the current language, allow switching to another language as a backup.
*/
localesFallback?: boolean
/**
* Use a custom locale key to be used as a default fallback for all locales. Default is root.
*/
localesDefaultFallback?: string
localeIndex?: string localeIndex?: string
contentProps?: Record<string, any> contentProps?: Record<string, any>
router: { router: {
@ -159,8 +167,10 @@ export type LocaleConfig<ThemeConfig = any> = Record<
LocaleSpecificConfig<ThemeConfig> & { LocaleSpecificConfig<ThemeConfig> & {
label: string label: string
link?: string link?: string
/**
* If the requested page isn't found in this language, switch to the same page in the specified language as a backup.
*/
fallback?: string fallback?: string
useAsFallback?: boolean
} }
> >

Loading…
Cancel
Save