Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class I18nManager {
constructor() {
this.currentLang = this.getLanguageFromURL() || 'zh';
this.currentLang = this.getInitialLanguage();
this.translations = {
zh: {
title: 'AFX · 支付宝体验技术部 · 引领体验科技,驱动数字生活',
Expand Down Expand Up @@ -35,6 +35,59 @@ class I18nManager {
return urlParams.get('lang');
}

/**
* 规范化语言值
*/
normalizeLanguage(lang) {
if (!lang) {
return null;
}

const normalizedLang = lang.toLowerCase();

if (normalizedLang.startsWith('zh')) {
return 'zh';
}

if (normalizedLang.startsWith('en')) {
return 'en';
}

return null;
}

/**
* 检测浏览器语言
*/
detectBrowserLanguage() {
const browserLanguages = navigator.languages?.length
? navigator.languages
: [navigator.language];

for (const lang of browserLanguages) {
const normalizedLang = this.normalizeLanguage(lang);

if (normalizedLang) {
return normalizedLang;
}
}

return 'en';
}

/**
* 获取初始化语言
*/
getInitialLanguage() {
const urlLanguage = this.getLanguageFromURL();

if (urlLanguage !== null) {
return this.normalizeLanguage(urlLanguage) || 'en';
}
Comment on lines +81 to +86

return this.detectBrowserLanguage();
}

/**
* 更新URL参数
*/
Expand Down
85 changes: 85 additions & 0 deletions tests/aria-label-i18n.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import vm from 'node:vm';

const html = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
const i18nScript = readFileSync(
Expand All @@ -27,6 +28,54 @@ function getExternalLinks() {
.filter(({ attributes }) => attributes.target === '_blank');
}

function createI18nManager({ search = '', language = 'zh-CN', languages } = {}) {
const context = {
window: {
location: { search },
history: { replaceState() {} },
},
document: {
addEventListener() {},
getElementById() {
return null;
},
querySelector() {
return null;
},
querySelectorAll() {
return [];
},
dispatchEvent() {},
documentElement: { lang: 'zh-CN' },
},
navigator: {
language,
languages: languages ?? [language],
},
URL,
URLSearchParams,
CustomEvent: class {
constructor(type, init) {
this.type = type;
this.detail = init?.detail;
}
},
};

vm.createContext(context);

const i18nScriptWithoutInit = i18nScript.replace(
/\n?\s*new I18nManager\(\);\s*$/,
''
);
vm.runInContext(
`${i18nScriptWithoutInit}\nglobalThis.I18nManager = I18nManager;`,
context
);

return new context.I18nManager();
Comment thread
Copilot marked this conversation as resolved.
}

test('external links provide localized aria-label values', () => {
const externalLinks = getExternalLinks();

Expand Down Expand Up @@ -62,3 +111,39 @@ test('language switch updates localized aria-label values', () => {
assert.match(i18nScript, /\[data-aria-zh\]\[data-aria-en\]/);
assert.match(i18nScript, /setAttribute\(\s*['"]aria-label['"]/);
});

test('defaults to browser English when no lang query is provided', () => {
const manager = createI18nManager({ language: 'en-US' });

assert.equal(manager.currentLang, 'en');
});

test('defaults to browser Chinese when no lang query is provided', () => {
const manager = createI18nManager({ language: 'zh-TW' });

assert.equal(manager.currentLang, 'zh');
});

test('valid lang query takes precedence over browser language detection', () => {
const manager = createI18nManager({
search: '?lang=en',
language: 'zh-CN',
});

assert.equal(manager.currentLang, 'en');
});

test('invalid lang query ignores browser locale and falls back to English', () => {
const manager = createI18nManager({
search: '?lang=fr',
language: 'zh-CN',
});

assert.equal(manager.currentLang, 'en');
});
Comment on lines +136 to +143

test('unrecognized browser language falls back to English', () => {
const manager = createI18nManager({ language: 'fr-FR', languages: ['fr-FR'] });

assert.equal(manager.currentLang, 'en');
});
Loading