feat(i18n): translate the log levels, access events and calendar labels (#6226)

* feat(i18n): translate the log levels, access events and calendar labels

The log-level selector, the access-log event tags, the Sub Formats sidebar
entry and the calendar choices were hardcoded English, so a fully translated
locale still showed them in English on core screens.

Add eleven keys across the 13 locales and reference them. Russian and
Ukrainian are translated; the remaining locales carry the English string, the
same convention the existing files already use for untranslated entries.

Two module-level constants had to move: the calendar list and the access-event
map were built outside the component, where t is not in scope. The event map
now stores keys and resolves them at render.

* fix(i18n): keep the log export language-independent and fit the translations

Three follow-ups from review. The downloaded x-ui.log had started carrying the
translated event text, so its contents depended on the panel language and the
Russian value for PROXY contains a space in a field format whose other values
are single tokens. The export keeps DIRECT/BLOCKED/PROXY; only the on-screen
tag is translated.

The log-level select had a fixed 95px width sized for "Warning", which clips
"Предупреждение"; it now grows with its content.

The three access filters stayed English while the tags they filter became
translated, so they use the same keys.

---------

Co-authored-by: n0ctal <n0ctal@users.noreply.github.com>
This commit is contained in:
n0ctal
2026-08-18 14:43:59 +05:00
committed by GitHub
parent 2b1fe1fd02
commit 5c9268c431
19 changed files with 234 additions and 66 deletions

View File

@@ -7,8 +7,8 @@
* - Please do NOT modify this file.
*/
const PACKAGE_VERSION = '2.14.7'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const PACKAGE_VERSION = '2.15.0'
const INTEGRITY_CHECKSUM = '03cb67ac84128e63d7cd722a6e5b7f1e'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
@@ -137,8 +137,18 @@ async function handleRequest(event, requestId, requestInterceptedAt) {
if (client && activeClientIds.has(client.id)) {
const serializedRequest = await serializeRequest(requestCloneForEvents)
// Omit the body of server-sent event stream responses.
// Cloning such responses would prevent client-side stream cancelations
// from reaching the original stream (a teed stream only cancels its
// source once both of its branches cancel) and would buffer the
// entire stream into the unconsumed clone indefinitely.
const isEventStreamResponse = response.headers
.get('content-type')
?.toLowerCase()
.startsWith('text/event-stream')
// Clone the response so both the client and the library could consume it.
const responseClone = response.clone()
const responseClone = isEventStreamResponse ? null : response.clone()
sendToClient(
client,
@@ -151,15 +161,17 @@ async function handleRequest(event, requestId, requestInterceptedAt) {
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
type: response.type,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
body: responseClone ? responseClone.body : null,
},
},
},
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
responseClone && responseClone.body
? [serializedRequest.body, responseClone.body]
: [],
)
}

View File

@@ -219,7 +219,7 @@ export default function AppSidebar() {
{ key: '/settings#subscription', icon: <CloudServerOutlined />, label: t('pages.settings.subSettings') },
];
if (showSubFormats) {
children.push({ key: '/settings#subscription-formats', icon: <CodeOutlined />, label: 'Sub Formats' });
children.push({ key: '/settings#subscription-formats', icon: <CodeOutlined />, label: t('menu.subFormats') });
}
return children;
}, [t, showSubFormats]);

View File

@@ -127,7 +127,7 @@ export default function IndexPage() {
async function copyConfig() {
const ok = await ClipboardManager.copyText(configText || '');
if (ok) messageApi.success('Copied');
if (ok) messageApi.success(t('copied'));
}
function downloadConfig() {

View File

@@ -105,14 +105,14 @@ export default function LogModal({ open, onClose }: LogModalProps) {
<Select
value={level}
size="small"
style={{ width: 95 }}
style={{ minWidth: 95 }}
onChange={setLevel}
options={[
{ value: 'debug', label: 'Debug' },
{ value: 'info', label: 'Info' },
{ value: 'notice', label: 'Notice' },
{ value: 'warning', label: 'Warning' },
{ value: 'err', label: 'Error' },
{ value: 'debug', label: t('pages.index.logLevelDebug') },
{ value: 'info', label: t('pages.index.logLevelInfo') },
{ value: 'notice', label: t('pages.index.logLevelNotice') },
{ value: 'warning', label: t('pages.index.logLevelWarning') },
{ value: 'err', label: t('pages.index.logLevelError') },
]}
/>
</Space.Compact>

View File

@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { TFunction } from 'i18next';
import { useTranslation } from 'react-i18next';
import { Button, Checkbox, Form, Input, Modal, Select, Tag } from 'antd';
import { DownloadOutlined, SyncOutlined } from '@ant-design/icons';
@@ -24,11 +25,24 @@ interface XrayLogEntry {
Event?: number;
}
const EVENT_LABELS: Record<number, string> = { 0: 'DIRECT', 1: 'BLOCKED', 2: 'PROXY' };
// The downloaded log is a data format people grep, so it keeps the stable
// tokens; only what is rendered on screen follows the panel language.
const EVENT_TOKENS: Record<number, string> = { 0: 'DIRECT', 1: 'BLOCKED', 2: 'PROXY' };
const EVENT_KEYS: Record<number, string> = {
0: 'pages.index.accessDirect',
1: 'pages.index.accessBlocked',
2: 'pages.index.accessProxy',
};
const EVENT_COLORS: Record<number, string> = { 0: 'green', 1: 'red', 2: 'blue' };
function eventLabel(ev?: number): string {
return EVENT_LABELS[ev ?? -1] ?? String(ev ?? '');
function eventToken(ev?: number): string {
return EVENT_TOKENS[ev ?? -1] ?? String(ev ?? '');
}
function eventLabel(t: TFunction, ev?: number): string {
const key = EVENT_KEYS[ev ?? -1];
return key ? t(key) : String(ev ?? '');
}
function eventColor(ev?: number): string {
@@ -112,7 +126,7 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) {
try {
const dt = l.DateTime ? new Date(l.DateTime) : null;
const dateStr = dt && !isNaN(dt.getTime()) ? dt.toISOString() : '';
const eventText = eventLabel(l.Event);
const eventText = eventToken(l.Event);
const emailPart = l.Email ? ` Email=${l.Email}` : '';
return `${dateStr} FROM=${l.FromAddress || ''} TO=${l.ToAddress || ''} INBOUND=${l.Inbound || ''} OUTBOUND=${l.Outbound || ''}${emailPart} EVENT=${eventText}`.trim();
} catch {
@@ -193,7 +207,7 @@ export default function XrayLogModal({ open, onClose }: XrayLogModalProps) {
{shortTime(log.DateTime)}
</span>
<Tag color={eventColor(log.Event)} className="log-event-tag">
{eventLabel(log.Event)}
{eventLabel(t, log.Event)}
</Tag>
</div>
<div className="log-route">

View File

@@ -34,10 +34,6 @@ interface GeneralTabProps {
updateSetting: (patch: Partial<AllSetting>) => void;
}
const DATEPICKER_LIST: { name: string; value: 'gregorian' | 'jalalian' }[] = [
{ name: 'Gregorian (Standard)', value: 'gregorian' },
{ name: 'Jalalian (شمسی)', value: 'jalalian' },
];
export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProps) {
const { t } = useTranslation();
@@ -290,7 +286,10 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
value={allSetting.datepicker || 'gregorian'}
onChange={(v) => updateSetting({ datepicker: v as 'gregorian' | 'jalalian' })}
style={{ width: '100%' }}
options={DATEPICKER_LIST.map((d) => ({ value: d.value, label: d.name }))}
options={[
{ value: 'gregorian', label: t('pages.settings.calendarGregorian') },
{ value: 'jalalian', label: t('pages.settings.calendarJalalian') },
]}
/>
</SettingListItem>
</>

View File

@@ -112,7 +112,8 @@
"docs": "التوثيق",
"openMenu": "فتح القائمة",
"pinSidebar": "تثبيت الشريط الجانبي",
"unpinSidebar": "إلغاء تثبيت الشريط الجانبي"
"unpinSidebar": "إلغاء تثبيت الشريط الجانبي",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — حرج",
"panel": "اللوحة",
"threads": "الخيوط",
"uptime": "مدة التشغيل"
"uptime": "مدة التشغيل",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "إجمالي المرسل/المستقبل",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "يجب أن يبدأ المسار بالرمز /"
},
"secretClear": "مسح",
"secretClearUndo": "تراجع عن المسح"
"secretClearUndo": "تراجع عن المسح",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "احفظ",

View File

@@ -112,7 +112,8 @@
"docs": "Documentation",
"openMenu": "Open menu",
"pinSidebar": "Pin sidebar",
"unpinSidebar": "Unpin sidebar"
"unpinSidebar": "Unpin sidebar",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — critical",
"panel": "Panel",
"threads": "Threads",
"uptime": "Uptime"
"uptime": "Uptime",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Total Sent/Received",
@@ -1469,7 +1478,9 @@
"pathLeadingSlash": "Path must start with /"
},
"secretClear": "Clear",
"secretClearUndo": "Undo clear"
"secretClearUndo": "Undo clear",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "Save",

View File

@@ -112,7 +112,8 @@
"docs": "Documentación",
"openMenu": "Abrir menú",
"pinSidebar": "Fijar barra lateral",
"unpinSidebar": "Desfijar barra lateral"
"unpinSidebar": "Desfijar barra lateral",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — crítico",
"panel": "Panel",
"threads": "Hilos",
"uptime": "Tiempo activo"
"uptime": "Tiempo activo",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Subidas/Descargas Totales",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "La ruta debe comenzar con /"
},
"secretClear": "Borrar",
"secretClearUndo": "Deshacer borrado"
"secretClearUndo": "Deshacer borrado",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "Guardar configuración",

View File

@@ -112,7 +112,8 @@
"docs": "مستندات",
"openMenu": "باز کردن منو",
"pinSidebar": "ثابت کردن نوار کناری",
"unpinSidebar": "برداشتن تثبیت نوار کناری"
"unpinSidebar": "برداشتن تثبیت نوار کناری",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — بحرانی",
"panel": "پنل",
"threads": "نخ‌ها",
"uptime": "مدت کارکرد"
"uptime": "مدت کارکرد",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "دریافت/ارسال کل",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "مسیر باید با / شروع شود"
},
"secretClear": "پاک کردن",
"secretClearUndo": "لغو پاک کردن"
"secretClearUndo": "لغو پاک کردن",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "ذخیره",

View File

@@ -112,7 +112,8 @@
"docs": "Dokumentasi",
"openMenu": "Buka menu",
"pinSidebar": "Sematkan bilah sisi",
"unpinSidebar": "Lepas sematan bilah sisi"
"unpinSidebar": "Lepas sematan bilah sisi",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — kritis",
"panel": "Panel",
"threads": "Thread",
"uptime": "Waktu aktif"
"uptime": "Waktu aktif",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Total Terkirim/Diterima",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "Path harus diawali dengan /"
},
"secretClear": "Hapus",
"secretClearUndo": "Batalkan hapus"
"secretClearUndo": "Batalkan hapus",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "Simpan",

View File

@@ -112,7 +112,8 @@
"docs": "ドキュメント",
"openMenu": "メニューを開く",
"pinSidebar": "サイドバーを固定",
"unpinSidebar": "サイドバーの固定を解除"
"unpinSidebar": "サイドバーの固定を解除",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — 危険水準",
"panel": "パネル",
"threads": "スレッド",
"uptime": "稼働時間"
"uptime": "稼働時間",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "総アップロード / ダウンロード",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "パスは / で始まる必要があります"
},
"secretClear": "クリア",
"secretClearUndo": "クリアを取り消す"
"secretClearUndo": "クリアを取り消す",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"importRules": "ルールをインポート",

View File

@@ -112,7 +112,8 @@
"docs": "Documentação",
"openMenu": "Abrir menu",
"pinSidebar": "Fixar barra lateral",
"unpinSidebar": "Desafixar barra lateral"
"unpinSidebar": "Desafixar barra lateral",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — crítico",
"panel": "Painel",
"threads": "Threads",
"uptime": "Tempo ativo"
"uptime": "Tempo ativo",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Total Enviado/Recebido",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "O caminho deve começar com /"
},
"secretClear": "Limpar",
"secretClearUndo": "Desfazer limpeza"
"secretClearUndo": "Desfazer limpeza",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"importRules": "Importar regras",

View File

@@ -112,7 +112,8 @@
"docs": "Документация",
"openMenu": "Открыть меню",
"pinSidebar": "Закрепить боковую панель",
"unpinSidebar": "Открепить боковую панель"
"unpinSidebar": "Открепить боковую панель",
"subFormats": "Форматы подписки"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — критический уровень",
"panel": "Панель",
"threads": "Потоки",
"uptime": "Время работы"
"uptime": "Время работы",
"logLevelDebug": "Отладка",
"logLevelInfo": "Информация",
"logLevelNotice": "Уведомление",
"logLevelWarning": "Предупреждение",
"logLevelError": "Ошибка",
"accessDirect": "НАПРЯМУЮ",
"accessBlocked": "ЗАБЛОКИРОВАНО",
"accessProxy": "ЧЕРЕЗ ПРОКСИ"
},
"inbounds": {
"totalDownUp": "Отправлено/получено",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "Путь должен начинаться с /"
},
"secretClear": "Очистить",
"secretClearUndo": "Отменить очистку"
"secretClearUndo": "Отменить очистку",
"calendarGregorian": "Григорианский (обычный)",
"calendarJalalian": "Джалали (شمسی)"
},
"xray": {
"importRules": "Импорт правил",

View File

@@ -112,7 +112,8 @@
"docs": "Belgeler",
"openMenu": "Menüyü aç",
"pinSidebar": "Kenar çubuğunu sabitle",
"unpinSidebar": "Kenar çubuğu sabitlemesini kaldır"
"unpinSidebar": "Kenar çubuğu sabitlemesini kaldır",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — kritik",
"panel": "Panel",
"threads": "İş parçacıkları",
"uptime": "Çalışma süresi"
"uptime": "Çalışma süresi",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Toplam Gönderilen/Alınan",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "Yol / ile başlamalıdır"
},
"secretClear": "Temizle",
"secretClearUndo": "Temizlemeyi geri al"
"secretClearUndo": "Temizlemeyi geri al",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "Kaydet",

View File

@@ -112,7 +112,8 @@
"docs": "Документація",
"openMenu": "Відкрити меню",
"pinSidebar": "Закріпити бічну панель",
"unpinSidebar": "Відкріпити бічну панель"
"unpinSidebar": "Відкріпити бічну панель",
"subFormats": "Формати підписки"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — критичний рівень",
"panel": "Панель",
"threads": "Потоки",
"uptime": "Час роботи"
"uptime": "Час роботи",
"logLevelDebug": "Налагодження",
"logLevelInfo": "Інформація",
"logLevelNotice": "Сповіщення",
"logLevelWarning": "Попередження",
"logLevelError": "Помилка",
"accessDirect": "НАПРЯМУ",
"accessBlocked": "ЗАБЛОКОВАНО",
"accessProxy": "ЧЕРЕЗ ПРОКСІ"
},
"inbounds": {
"totalDownUp": "Всього надісланих/отриманих",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "Шлях має починатися з /"
},
"secretClear": "Очистити",
"secretClearUndo": "Скасувати очищення"
"secretClearUndo": "Скасувати очищення",
"calendarGregorian": "Григоріанський (звичайний)",
"calendarJalalian": "Джалалі (شمسی)"
},
"xray": {
"save": "Зберегти",

View File

@@ -112,7 +112,8 @@
"docs": "Tài liệu",
"openMenu": "Mở menu",
"pinSidebar": "Ghim thanh bên",
"unpinSidebar": "Bỏ ghim thanh bên"
"unpinSidebar": "Bỏ ghim thanh bên",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — nguy cấp",
"panel": "Panel",
"threads": "Luồng",
"uptime": "Thời gian chạy"
"uptime": "Thời gian chạy",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "Tổng tải lên/tải xuống",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "Đường dẫn phải bắt đầu bằng /"
},
"secretClear": "Xóa",
"secretClearUndo": "Hoàn tác xóa"
"secretClearUndo": "Hoàn tác xóa",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"importRules": "Nhập quy tắc",

View File

@@ -112,7 +112,8 @@
"docs": "文档",
"openMenu": "打开菜单",
"pinSidebar": "固定侧边栏",
"unpinSidebar": "取消固定侧边栏"
"unpinSidebar": "取消固定侧边栏",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — 危险",
"panel": "面板",
"threads": "线程",
"uptime": "运行时间"
"uptime": "运行时间",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "总上传 / 下载",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "路径必须以 / 开头"
},
"secretClear": "清除",
"secretClearUndo": "撤销清除"
"secretClearUndo": "撤销清除",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"importRules": "导入规则",

View File

@@ -112,7 +112,8 @@
"docs": "文件",
"openMenu": "開啟選單",
"pinSidebar": "固定側邊欄",
"unpinSidebar": "取消固定側邊欄"
"unpinSidebar": "取消固定側邊欄",
"subFormats": "Sub Formats"
},
"pages": {
"login": {
@@ -257,7 +258,15 @@
"healthCritical": "{list} — 危險",
"panel": "面板",
"threads": "執行緒",
"uptime": "執行時間"
"uptime": "執行時間",
"logLevelDebug": "Debug",
"logLevelInfo": "Info",
"logLevelNotice": "Notice",
"logLevelWarning": "Warning",
"logLevelError": "Error",
"accessDirect": "DIRECT",
"accessBlocked": "BLOCKED",
"accessProxy": "PROXY"
},
"inbounds": {
"totalDownUp": "總上傳 / 下載",
@@ -1352,7 +1361,9 @@
"pathLeadingSlash": "路徑必須以 / 開頭"
},
"secretClear": "清除",
"secretClearUndo": "復原清除"
"secretClearUndo": "復原清除",
"calendarGregorian": "Gregorian (Standard)",
"calendarJalalian": "Jalalian (شمسی)"
},
"xray": {
"save": "儲存",