i18n: add Arabic (ar-SA) UI locale with RTL support

Add Modern Standard Arabic (ar-SA) as a selectable UI language alongside
the existing zh-CN, en-US and ru-RU locales.

- web/static/i18n/ar-SA.json: new Arabic locale (4272 leaf strings,
  full key/placeholder parity with en-US). The `c2` and `webshell`
  sections are intentionally omitted and fall back to English via the
  i18next fallback chain (ar-SA -> en-US -> zh-CN).
- web/static/js/i18n.js: register ar-SA, detect `ar*` navigator language,
  preload en-US as fallback, set document dir="rtl" for Arabic, and add
  the ar-SA language label handling.
- web/static/i18n/{zh-CN,en-US,ru-RU}.json: add the `lang.arSA` label.
- web/templates/{index,api-docs}.html: add the العربية option to the
  language dropdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
muhannadalhaririandClaude Opus 4.8 committed 2026-10-29 10:42:55 +03:00
1 parent 5affb4df1b
commit caa7700345
7 files changed
+4624 -5

No files matched your search

File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -2,7 +2,8 @@
"lang": {
"zhCN": "中文",
"enUS": "English",
"ruRU": "Русский"
"ruRU": "Русский",
"arSA": "العربية"
},
"mobile": {
"openNav": "Open navigation menu",
+2 -1
View File
@@ -2,7 +2,8 @@
"lang": {
"zhCN": "中文",
"enUS": "English",
"ruRU": "Русский"
"ruRU": "Русский",
"arSA": "العربية"
},
"mobile": {
"openNav": "Открыть меню навигации",
+2 -1
View File
@@ -2,7 +2,8 @@
"lang": {
"zhCN": "中文",
"enUS": "English",
"ruRU": "Русский"
"ruRU": "Русский",
"arSA": "العربية"
},
"mobile": {
"openNav": "打开导航菜单",
+24 -2
View File
@@ -29,12 +29,27 @@
if (navLang.startsWith('ru')) {
return 'ru-RU';
}
if (navLang.startsWith('ar')) {
return 'ar-SA';
}
if (navLang.startsWith('en')) {
return 'en-US';
}
return DEFAULT_LANG;
}
// 根据语言设置文档方向(阿拉伯语为从右到左 RTL)
function applyDirection(lang) {
try {
if (document && document.documentElement) {
var l = String(lang || DEFAULT_LANG).toLowerCase();
document.documentElement.dir = l.indexOf('ar') === 0 ? 'rtl' : 'ltr';
}
} catch (e) {
// ignore
}
}
async function loadLanguageResources(lang) {
if (loadedLangs[lang]) {
return;
@@ -114,6 +129,9 @@
// ignore
}
// 更新文档方向(RTL/LTR)
applyDirection(i18next.language || DEFAULT_LANG);
try {
if (typeof window.syncHitlAuditBackendUI === 'function') {
window.syncHitlAuditBackendUI();
@@ -129,6 +147,8 @@
label.textContent = i18next.t('lang.zhCN');
} else if (lang.indexOf('ru') === 0) {
label.textContent = i18next.t('lang.ruRU');
} else if (lang.indexOf('ar') === 0) {
label.textContent = i18next.t('lang.arSA');
} else {
label.textContent = i18next.t('lang.enUS');
}
@@ -159,7 +179,7 @@
const current = i18next.language || DEFAULT_LANG;
if (lang === current) return;
await loadLanguageResources(lang);
if (lang === 'ru-RU') {
if (lang === 'ru-RU' || lang === 'ar-SA') {
await loadLanguageResources('en-US');
}
await i18next.changeLanguage(lang);
@@ -193,6 +213,7 @@
lng: initialLang,
fallbackLng: {
'ru-RU': ['en-US', 'zh-CN'],
'ar-SA': ['en-US', 'zh-CN'],
'default': [DEFAULT_LANG]
},
debug: false,
@@ -200,7 +221,7 @@
});
await loadLanguageResources(initialLang);
if (initialLang === 'ru-RU') {
if (initialLang === 'ru-RU' || initialLang === 'ar-SA') {
await loadLanguageResources('en-US');
}
applyTranslations(document);
@@ -221,6 +242,7 @@
const lang = String((window.__locale || (typeof i18next !== 'undefined' && i18next.language) || '')).toLowerCase();
if (lang.indexOf('zh') === 0) return 'zh-CN';
if (lang.indexOf('ru') === 0) return 'ru-RU';
if (lang.indexOf('ar') === 0) return 'ar-SA';
return 'en-US';
};
window.changeLanguage = changeLanguage;
+1
View File
@@ -989,6 +989,7 @@
<div class="lang-option" data-lang="zh-CN" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('zh-CN')">中文</div>
<div class="lang-option" data-lang="en-US" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('en-US')">English</div>
<div class="lang-option" data-lang="ru-RU" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('ru-RU')">Русский</div>
<div class="lang-option" data-lang="ar-SA" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('ar-SA')">العربية</div>
</div>
</div>
</div>
+1
View File
@@ -137,6 +137,7 @@
<div class="lang-option" data-lang="zh-CN" onclick="onLanguageSelect('zh-CN')">中文</div>
<div class="lang-option" data-lang="en-US" onclick="onLanguageSelect('en-US')">English</div>
<div class="lang-option" data-lang="ru-RU" onclick="onLanguageSelect('ru-RU')">Русский</div>
<div class="lang-option" data-lang="ar-SA" onclick="onLanguageSelect('ar-SA')">العربية</div>
</div>
</div>
<div class="notification-menu-container">