mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-19 14:07:36 +02:00
feat: add Russian UI locale (#341)
Add a ru-RU language pack and a Русский entry in the language switcher so the console can be used in Russian. Dates follow the selected locale. Strings that are not translated yet fall back to English.
This commit is contained in:
1 parent
470eb5ead1
commit
78973e844e
12 files changed
+4965
-25
No files matched your search
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"lang": {
|
||||
"zhCN": "中文",
|
||||
"enUS": "English"
|
||||
"enUS": "English",
|
||||
"ruRU": "Русский"
|
||||
},
|
||||
"common": {
|
||||
"ok": "OK",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"lang": {
|
||||
"zhCN": "中文",
|
||||
"enUS": "English"
|
||||
"enUS": "English",
|
||||
"ruRU": "Русский"
|
||||
},
|
||||
"common": {
|
||||
"ok": "确定",
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
|
||||
function pickerLocale() {
|
||||
if (typeof auditLocale === 'function') return auditLocale();
|
||||
if (typeof window.uiLocale === 'function') return window.uiLocale();
|
||||
if (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) return 'zh-CN';
|
||||
if (typeof window.__locale === 'string' && window.__locale.startsWith('ru')) return 'ru-RU';
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
@@ -87,6 +89,9 @@
|
||||
if (loc.startsWith('zh')) {
|
||||
return ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];
|
||||
}
|
||||
if (loc.startsWith('ru')) {
|
||||
return ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'];
|
||||
}
|
||||
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,11 @@ function auditResultLabel(result) {
|
||||
}
|
||||
|
||||
function auditLocale() {
|
||||
if (typeof window.uiLocale === 'function') return window.uiLocale();
|
||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||
return window.__locale.startsWith('zh') ? 'zh-CN' : 'en-US';
|
||||
if (window.__locale.startsWith('zh')) return 'zh-CN';
|
||||
if (window.__locale.startsWith('ru')) return 'ru-RU';
|
||||
return 'en-US';
|
||||
}
|
||||
return (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : 'en-US';
|
||||
}
|
||||
|
||||
@@ -3648,9 +3648,9 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
|
||||
} else {
|
||||
messageTime = new Date();
|
||||
}
|
||||
const msgTimeLocale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const msgTimeLocale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const msgTimeOpts = { hour: '2-digit', minute: '2-digit' };
|
||||
if (msgTimeLocale === 'zh-CN') msgTimeOpts.hour12 = false;
|
||||
if (msgTimeLocale === 'zh-CN' || msgTimeLocale === 'ru-RU') msgTimeOpts.hour12 = false;
|
||||
timeDiv.textContent = messageTime.toLocaleTimeString(msgTimeLocale, msgTimeOpts);
|
||||
try {
|
||||
timeDiv.dataset.messageTime = messageTime.toISOString();
|
||||
@@ -5597,7 +5597,7 @@ function renderMCPDetailModal(exec) {
|
||||
try {
|
||||
statusEl.dataset.detailStatus = normalizedStatus;
|
||||
} catch (e) { /* ignore */ }
|
||||
const detailTimeLocale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const detailTimeLocale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const detailTimeEl = document.getElementById('detail-time');
|
||||
if (detailTimeEl) {
|
||||
detailTimeEl.textContent = exec.startTime
|
||||
@@ -6257,13 +6257,13 @@ function formatConversationTimestamp(dateObj, todayStart, yesterdayStart) {
|
||||
const referenceToday = todayStart || new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const referenceYesterday = yesterdayStart || new Date(referenceToday.getTime() - 24 * 60 * 60 * 1000);
|
||||
const messageDate = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate());
|
||||
const fmtLocale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const fmtLocale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const yesterdayLabel = typeof window.t === 'function' ? window.t('chat.yesterday') : '昨天';
|
||||
|
||||
const timeOnlyOpts = { hour: '2-digit', minute: '2-digit' };
|
||||
const dateTimeOpts = { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
||||
const fullDateOpts = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
||||
if (fmtLocale === 'zh-CN') {
|
||||
if (fmtLocale === 'zh-CN' || fmtLocale === 'ru-RU') {
|
||||
timeOnlyOpts.hour12 = false;
|
||||
dateTimeOpts.hour12 = false;
|
||||
fullDateOpts.hour12 = false;
|
||||
@@ -10565,7 +10565,7 @@ function formatConversationDateForMarkdown(value) {
|
||||
if (!value) return '';
|
||||
const d = new Date(value);
|
||||
if (isNaN(d.getTime())) return '';
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
return d.toLocaleString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
@@ -11066,9 +11066,9 @@ function closeBatchManageModal() {
|
||||
|
||||
// 语言切换时刷新当前聊天页内的时间与动态文案(消息时间、执行流程时间由 monitor 的 refreshProgressAndTimelineI18n 处理)
|
||||
function refreshChatPanelI18n() {
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const timeOpts = { hour: '2-digit', minute: '2-digit' };
|
||||
if (locale === 'zh-CN') timeOpts.hour12 = false;
|
||||
if (locale === 'zh-CN' || locale === 'ru-RU') timeOpts.hour12 = false;
|
||||
const t = typeof window.t === 'function' ? window.t : function (k) { return k; };
|
||||
|
||||
const messagesEl = document.getElementById('chat-messages');
|
||||
|
||||
@@ -116,8 +116,11 @@ function hitlPaginationT(key, opts, fallback) {
|
||||
}
|
||||
|
||||
function hitlLocale() {
|
||||
if (typeof window.uiLocale === 'function') return window.uiLocale();
|
||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||
return window.__locale.startsWith('zh') ? 'zh-CN' : 'en-US';
|
||||
if (window.__locale.startsWith('zh')) return 'zh-CN';
|
||||
if (window.__locale.startsWith('ru')) return 'ru-RU';
|
||||
return 'en-US';
|
||||
}
|
||||
return (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : 'en-US';
|
||||
}
|
||||
|
||||
+21
-1
@@ -26,6 +26,9 @@
|
||||
if (navLang.startsWith('zh')) {
|
||||
return 'zh-CN';
|
||||
}
|
||||
if (navLang.startsWith('ru')) {
|
||||
return 'ru-RU';
|
||||
}
|
||||
if (navLang.startsWith('en')) {
|
||||
return 'en-US';
|
||||
}
|
||||
@@ -124,6 +127,8 @@
|
||||
const lang = (i18next.language || DEFAULT_LANG).toLowerCase();
|
||||
if (lang.indexOf('zh') === 0) {
|
||||
label.textContent = i18next.t('lang.zhCN');
|
||||
} else if (lang.indexOf('ru') === 0) {
|
||||
label.textContent = i18next.t('lang.ruRU');
|
||||
} else {
|
||||
label.textContent = i18next.t('lang.enUS');
|
||||
}
|
||||
@@ -154,6 +159,9 @@
|
||||
const current = i18next.language || DEFAULT_LANG;
|
||||
if (lang === current) return;
|
||||
await loadLanguageResources(lang);
|
||||
if (lang === 'ru-RU') {
|
||||
await loadLanguageResources('en-US');
|
||||
}
|
||||
await i18next.changeLanguage(lang);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, lang);
|
||||
@@ -183,12 +191,18 @@
|
||||
const initialLang = detectInitialLang();
|
||||
await i18next.init({
|
||||
lng: initialLang,
|
||||
fallbackLng: DEFAULT_LANG,
|
||||
fallbackLng: {
|
||||
'ru-RU': ['en-US', 'zh-CN'],
|
||||
'default': [DEFAULT_LANG]
|
||||
},
|
||||
debug: false,
|
||||
resources: {}
|
||||
});
|
||||
|
||||
await loadLanguageResources(initialLang);
|
||||
if (initialLang === 'ru-RU') {
|
||||
await loadLanguageResources('en-US');
|
||||
}
|
||||
applyTranslations(document);
|
||||
updateLangLabel();
|
||||
if (typeof window.refreshThemeToggleLabel === 'function') {
|
||||
@@ -203,6 +217,12 @@
|
||||
if (typeof i18next === 'undefined') return key;
|
||||
return i18next.t(key, opts);
|
||||
};
|
||||
window.uiLocale = function () {
|
||||
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';
|
||||
return 'en-US';
|
||||
};
|
||||
window.changeLanguage = changeLanguage;
|
||||
window.applyTranslations = applyTranslations;
|
||||
|
||||
|
||||
@@ -77,11 +77,17 @@ if (typeof window !== 'undefined') {
|
||||
|
||||
// 当前界面语言对应的 BCP 47 标签(与时间格式化一致)
|
||||
function getCurrentTimeLocale() {
|
||||
if (typeof window.uiLocale === 'function') return window.uiLocale();
|
||||
if (typeof window.__locale === 'string' && window.__locale.length) {
|
||||
return window.__locale.startsWith('zh') ? 'zh-CN' : 'en-US';
|
||||
if (window.__locale.startsWith('zh')) return 'zh-CN';
|
||||
if (window.__locale.startsWith('ru')) return 'ru-RU';
|
||||
return 'en-US';
|
||||
}
|
||||
if (typeof i18next !== 'undefined' && i18next.language) {
|
||||
return (i18next.language || '').startsWith('zh') ? 'zh-CN' : 'en-US';
|
||||
const lang = String(i18next.language || '');
|
||||
if (lang.startsWith('zh')) return 'zh-CN';
|
||||
if (lang.startsWith('ru')) return 'ru-RU';
|
||||
return 'en-US';
|
||||
}
|
||||
return 'zh-CN';
|
||||
}
|
||||
@@ -90,7 +96,7 @@ function getCurrentTimeLocale() {
|
||||
function getTimeFormatOptions() {
|
||||
const loc = getCurrentTimeLocale();
|
||||
const base = { hour: '2-digit', minute: '2-digit', second: '2-digit' };
|
||||
if (loc === 'zh-CN') {
|
||||
if (loc === 'zh-CN' || loc === 'ru-RU') {
|
||||
base.hour12 = false;
|
||||
}
|
||||
return base;
|
||||
@@ -7812,7 +7818,7 @@ function buildMcpTimelineSvg(points, rangeKey) {
|
||||
const maxVal = Math.max(1, ...points.map((p) => p.total || 0));
|
||||
const hasFailed = points.some((p) => (p.failed || 0) > 0);
|
||||
const hasBlocked = points.some((p) => (p.blocked || 0) > 0);
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const barGap = points.length > 48 ? 1 : 2;
|
||||
const barW = Math.max(1.6, Math.min(8, (plotW / Math.max(1, points.length)) - barGap));
|
||||
|
||||
@@ -8030,7 +8036,7 @@ function buildTimelineSparseHint(points, timeline) {
|
||||
if (nonZeroRatio > 0.3 && !peakNearEnd) return '';
|
||||
|
||||
const rangeKey = timeline.range || getMcpMonitorTimelineRange();
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const peakTime = timeline.summary.peakAt
|
||||
? formatMcpTimelineLabel(timeline.summary.peakAt, rangeKey, locale)
|
||||
: formatMcpTimelineLabel(points[peakIdx].t, rangeKey, locale);
|
||||
@@ -8040,7 +8046,7 @@ function buildTimelineSparseHint(points, timeline) {
|
||||
|
||||
function renderMcpTimelineActiveMoments(points, rangeKey) {
|
||||
if (!Array.isArray(points) || points.length === 0) return '';
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const active = points
|
||||
.map((p, i) => ({ ...p, i }))
|
||||
.filter((p) => (p.total || 0) > 0)
|
||||
@@ -8590,7 +8596,7 @@ function renderMcpStatsStackedBar(success, failed) {
|
||||
function updateMonitorStatsSubtitle(lastFetchedAt, toolCount, retentionDays) {
|
||||
const subtitle = document.getElementById('monitor-stats-subtitle');
|
||||
if (!subtitle) return;
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const timeText = lastFetchedAt
|
||||
? (lastFetchedAt.toLocaleString ? lastFetchedAt.toLocaleString(locale) : String(lastFetchedAt))
|
||||
: '—';
|
||||
@@ -8987,7 +8993,7 @@ function renderMonitorStats(summary = null, topTools = [], lastFetchedAt = null)
|
||||
const hasCalls = effectiveTotal > 0;
|
||||
const successRateNum = hasCalls ? (totals.success / effectiveTotal) * 100 : 0;
|
||||
const successRate = hasCalls ? successRateNum.toFixed(1) : '-';
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : 'en-US';
|
||||
const locale = (typeof window.uiLocale === 'function' ? window.uiLocale() : 'en-US');
|
||||
const noCallsYet = mcpMonitorT('noCallsYet') || monitorFallback('暂无调用', 'No calls yet');
|
||||
const noCompletedYet = mcpMonitorT('noCompletedYet') || monitorFallback('暂无有效完成', 'No completed outcomes yet');
|
||||
const lastCallText = totals.lastCallTime
|
||||
@@ -9081,7 +9087,7 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
|
||||
hard_timeout: 'statusHardTimeout',
|
||||
orphaned: 'statusOrphaned'
|
||||
};
|
||||
const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : undefined;
|
||||
const locale = (typeof window.uiLocale === 'function') ? window.uiLocale() : undefined;
|
||||
const rowEntries = executions
|
||||
.map(exec => {
|
||||
const status = getToolExecutionDisplayStatus(exec);
|
||||
|
||||
@@ -8,11 +8,11 @@ function vulnT(key, opts) {
|
||||
}
|
||||
|
||||
function vulnDateLocale() {
|
||||
if (typeof window.uiLocale === 'function') return window.uiLocale();
|
||||
try {
|
||||
const lang = (window.__locale || '').toLowerCase();
|
||||
if (lang.indexOf('zh') === 0) {
|
||||
return 'zh-CN';
|
||||
}
|
||||
if (lang.indexOf('zh') === 0) return 'zh-CN';
|
||||
if (lang.indexOf('ru') === 0) return 'ru-RU';
|
||||
} catch (e) { /* ignore */ }
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
@@ -984,6 +984,7 @@
|
||||
<div id="lang-dropdown" class="lang-dropdown" style="display: none;">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
<div id="lang-dropdown" class="lang-dropdown" style="display: none;">
|
||||
<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>
|
||||
</div>
|
||||
<div class="notification-menu-container">
|
||||
|
||||
Reference in new issue
Block a user