Skip to content

Commit bc63d3c

Browse files
committed
fix: Dynamic locale loading in Chinese
Change-Id: I8b40a459055751add6351f35d749fba0f8c9a061
1 parent 976c0ed commit bc63d3c

5 files changed

Lines changed: 155 additions & 11 deletions

File tree

docs/.vitepress/config.mts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,89 @@ const localeRedirects = Object.entries(siteLocales).map(([key, locale]) => ({
66
lang: locale.lang
77
}))
88

9+
function createLocaleRedirectScript() {
10+
return `(() => {
11+
const redirects = ${JSON.stringify(localeRedirects)};
12+
const pathname = window.location.pathname;
13+
14+
if (pathname !== '/' && pathname !== '/index.html') {
15+
return;
16+
}
17+
18+
const params = new URLSearchParams(window.location.search);
19+
const delayValue = Number(params.get('redirectDelay') ?? '0');
20+
const redirectDelay = Number.isFinite(delayValue) && delayValue >= 0
21+
? delayValue
22+
: 0;
23+
24+
const preferred = [];
25+
const seen = new Set();
26+
const push = (language) => {
27+
if (typeof language !== 'string') {
28+
return;
29+
}
30+
31+
const normalized = language.trim().toLowerCase().replace(/_/g, '-');
32+
33+
if (!normalized || seen.has(normalized)) {
34+
return;
35+
}
36+
37+
seen.add(normalized);
38+
preferred.push(normalized);
39+
};
40+
41+
try {
42+
push(Intl.DateTimeFormat().resolvedOptions().locale);
43+
} catch {}
44+
45+
push(navigator.language);
46+
47+
for (const language of navigator.languages ?? []) {
48+
push(language);
49+
}
50+
51+
push(navigator.userLanguage);
52+
push(navigator.browserLanguage);
53+
push(navigator.systemLanguage);
54+
55+
const resolve = () => {
56+
const normalized = preferred.flatMap((language) => [language, language.split('-')[0]]);
57+
58+
for (const candidate of normalized) {
59+
const matched = redirects.find(({ lang }) => {
60+
const normalizedLang = lang.toLowerCase();
61+
return candidate === normalizedLang || candidate === normalizedLang.split('-')[0];
62+
});
63+
64+
if (matched) {
65+
return matched.path;
66+
}
67+
}
68+
69+
return redirects.find(({ lang }) => lang.toLowerCase().startsWith('en'))?.path
70+
?? redirects[0]?.path
71+
?? '/en/';
72+
};
73+
74+
const target = resolve();
75+
76+
if (target && target !== pathname) {
77+
window.setTimeout(() => {
78+
window.location.replace(target);
79+
}, redirectDelay);
80+
}
81+
})();`
82+
}
83+
984
export default defineConfig({
1085
title: 'DataBackup',
1186
description: 'Free and open-source data backup application',
1287
base: '/',
13-
head: [['link', { rel: 'icon', href: '/images/logo.png' }]],
88+
head: [
89+
['link', { rel: 'icon', href: '/images/logo.png' }],
90+
['script', {}, createLocaleRedirectScript()]
91+
],
1492
themeConfig: {
1593
logo: '/images/logo.png',
1694
localeRedirects,

docs/.vitepress/locales.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function readLocaleFile(localePath: string): SiteLocale {
2222
function loadSiteLocales(): Record<string, SiteLocale> {
2323
const localeEntries = readdirSync(docsRoot, { withFileTypes: true })
2424
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
25+
.sort((left, right) => left.name.localeCompare(right.name))
2526
.map((entry) => {
2627
const localeFile = join(docsRoot, entry.name, 'locale.json')
2728
return existsSync(localeFile)

docs/.vitepress/redirect.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,69 @@ export type LocaleRedirect = {
33
lang: string
44
}
55

6+
function normalizeLanguage(language: string): string {
7+
return language.trim().toLowerCase().replace(/_/g, '-')
8+
}
9+
10+
function pushLanguage(
11+
target: string[],
12+
seen: Set<string>,
13+
language: string | undefined
14+
) {
15+
if (!language) {
16+
return
17+
}
18+
19+
const normalized = normalizeLanguage(language)
20+
21+
if (!normalized || seen.has(normalized)) {
22+
return
23+
}
24+
25+
seen.add(normalized)
26+
target.push(normalized)
27+
}
28+
29+
export function getPreferredLanguages(
30+
navigatorLike: Navigator = navigator
31+
): string[] {
32+
const preferred: string[] = []
33+
const seen = new Set<string>()
34+
const legacyNavigator = navigatorLike as Navigator & {
35+
userLanguage?: string
36+
browserLanguage?: string
37+
systemLanguage?: string
38+
}
39+
40+
try {
41+
pushLanguage(
42+
preferred,
43+
seen,
44+
Intl.DateTimeFormat().resolvedOptions().locale
45+
)
46+
} catch {
47+
// Ignore locale detection failures and fall back to navigator hints.
48+
}
49+
50+
pushLanguage(preferred, seen, navigatorLike.language)
51+
52+
for (const language of navigatorLike.languages ?? []) {
53+
pushLanguage(preferred, seen, language)
54+
}
55+
56+
pushLanguage(preferred, seen, legacyNavigator.userLanguage)
57+
pushLanguage(preferred, seen, legacyNavigator.browserLanguage)
58+
pushLanguage(preferred, seen, legacyNavigator.systemLanguage)
59+
60+
return preferred
61+
}
62+
663
export function resolveRedirectPath(
764
redirects: LocaleRedirect[],
865
preferredLanguages: readonly string[]
966
): string {
1067
const normalized = preferredLanguages
11-
.map((language) => language.toLowerCase())
68+
.map((language) => normalizeLanguage(language))
1269
.flatMap((language) => [language, language.split('-')[0]])
1370

1471
for (const preferred of normalized) {
@@ -22,7 +79,9 @@ export function resolveRedirectPath(
2279
}
2380
}
2481

25-
return '/en/'
82+
return redirects.find(({ lang }) => lang.toLowerCase().startsWith('en'))?.path
83+
?? redirects[0]?.path
84+
?? '/en/'
2685
}
2786

2887
export function getRedirectDelay(search: string): number {

docs/.vitepress/theme/RedirectPage.vue

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<script setup lang="ts">
22
import { computed, onMounted, ref } from 'vue'
33
import { useData } from 'vitepress'
4-
import { getRedirectDelay, resolveRedirectPath, type LocaleRedirect } from '../redirect'
4+
import {
5+
getPreferredLanguages,
6+
getRedirectDelay,
7+
resolveRedirectPath,
8+
type LocaleRedirect
9+
} from '../redirect'
510
611
const { site } = useData()
712
@@ -12,9 +17,7 @@ const preferredLanguages = ref<string[]>([])
1217
const targetPath = ref('/en/')
1318
1419
onMounted(() => {
15-
preferredLanguages.value = navigator.languages.length > 0
16-
? [...navigator.languages]
17-
: [navigator.language || 'en']
20+
preferredLanguages.value = getPreferredLanguages()
1821
1922
targetPath.value = resolveRedirectPath(redirects.value, preferredLanguages.value)
2023

docs/.vitepress/theme/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import type { Theme } from 'vitepress'
12
import DefaultTheme from 'vitepress/theme'
3+
24
import RedirectPage from './RedirectPage.vue'
35

46
export default {
57
extends: DefaultTheme,
6-
enhanceApp({ app }) {
7-
app.component('RedirectPage', RedirectPage)
8-
}
9-
}
8+
enhanceApp(ctx) {
9+
DefaultTheme.enhanceApp?.(ctx)
10+
ctx.app.component('RedirectPage', RedirectPage)
11+
},
12+
} satisfies Theme

0 commit comments

Comments
 (0)