Files
remoteconn-gitea/apps/miniprogram/utils/i18n.js
2026-03-21 18:57:10 +08:00

441 lines
13 KiB
JavaScript

/* global module, require */
const { I18N_CATALOG } = require("./i18nCatalog");
const DEFAULT_UI_LANGUAGE = "zh-Hans";
const UI_LANGUAGE_VALUES = Object.freeze(["zh-Hans", "zh-Hant", "en", "ja", "ko"]);
const UI_LANGUAGE_VALUE_SET = new Set(UI_LANGUAGE_VALUES);
const UI_LANGUAGE_FALLBACKS = Object.freeze({
ja: "en",
ko: "en"
});
/**
* 主题内部值顺序与 shared/Web 保持一致:
* 1. 设置页下拉顺序直接复用这份稳定数组;
* 2. 已重命名的老主题继续保留多语言展示名;
* 3. 新增主题若未提供本地化名,则回退内部值显示,和 Web 保持同口径。
*/
const THEME_PRESET_VALUES = Object.freeze([
"tide",
"暮砂",
"霓潮",
"苔暮",
"焰岩",
"岩陶",
"靛雾",
"绛霓",
"玫蓝",
"珊湾",
"苔荧",
"铜暮",
"炽潮",
"藕夜",
"沙海",
"珀岚",
"炫虹",
"鎏霓",
"珊汐",
"黛苔",
"霜绯"
]);
const THEME_PRESET_LABELS = Object.freeze({
"zh-Hans": Object.freeze({
tide: "潮汐",
暮砂: "沙丘",
霓潮: "棱光",
苔暮: "苔影",
焰岩: "余烬",
岩陶: "陶土",
靛雾: "岚雾",
绛霓: "绛霓",
玫蓝: "玫蓝",
珊湾: "珊湾",
苔荧: "苔荧",
铜暮: "铜暮",
炽潮: "炽潮",
藕夜: "藕夜",
沙海: "沙海",
珀岚: "珀岚",
炫虹: "炫虹",
鎏霓: "鎏霓",
珊汐: "珊汐",
黛苔: "黛苔",
霜绯: "霜绯"
}),
"zh-Hant": Object.freeze({
tide: "潮汐",
暮砂: "沙丘",
霓潮: "稜光",
苔暮: "苔影",
焰岩: "餘燼",
岩陶: "陶土",
靛雾: "嵐霧",
绛霓: "絳霓",
玫蓝: "玫藍",
珊湾: "珊灣",
苔荧: "苔螢",
铜暮: "銅暮",
炽潮: "熾潮",
藕夜: "藕夜",
沙海: "沙海",
珀岚: "珀嵐",
炫虹: "炫虹",
鎏霓: "鎏霓",
珊汐: "珊汐",
黛苔: "黛苔",
霜绯: "霜緋"
}),
en: Object.freeze({
tide: "Tide",
暮砂: "Dune",
霓潮: "Prism",
苔暮: "Moss",
焰岩: "Ember",
岩陶: "Clay",
靛雾: "Haze",
绛霓: "Crimson",
玫蓝: "Rose",
珊湾: "Bay",
苔荧: "Glow",
铜暮: "Copper",
炽潮: "Blaze",
藕夜: "Lotus",
沙海: "Sandsea",
珀岚: "Amber",
炫虹: "Neon",
鎏霓: "Gilded",
珊汐: "Coral",
黛苔: "Slate",
霜绯: "Frost"
}),
ja: Object.freeze({
tide: "潮汐",
暮砂: "砂丘",
霓潮: "プリズム",
苔暮: "苔影",
焰岩: "残り火",
岩陶: "陶土",
靛雾: "藍霧",
绛霓: "深紅",
玫蓝: "薔薇",
珊湾: "珊湾",
苔荧: "苔光",
铜暮: "銅暮",
炽潮: "炎潮",
藕夜: "蓮夜",
沙海: "砂海",
珀岚: "琥珀",
炫虹: "ネオン",
鎏霓: "金彩",
珊汐: "珊汐",
黛苔: "黛苔",
霜绯: "霜緋"
}),
ko: Object.freeze({
tide: "조류",
暮砂: "사구",
霓潮: "프리즘",
苔暮: "이끼 그림자",
焰岩: "잿불",
岩陶: "도토",
靛雾: "남빛 안개",
绛霓: "크림슨",
玫蓝: "로즈",
珊湾: "코럴",
苔荧: "글로우",
铜暮: "코퍼",
炽潮: "블레이즈",
藕夜: "로터스",
沙海: "샌드씨",
珀岚: "앰버",
炫虹: "네온",
鎏霓: "길디드",
珊汐: "산호",
黛苔: "슬레이트",
霜绯: "프로스트"
})
});
function normalizeUiLanguage(value) {
const normalized = String(value || "").trim();
return UI_LANGUAGE_VALUE_SET.has(normalized) ? normalized : DEFAULT_UI_LANGUAGE;
}
/**
* 新增语种先走英文基线兜底:
* 1. 保证所有现有页面能稳定显示,不会回落成简体中文;
* 2. 后续补专门词典时,只需在 catalog 中加同名语言节点即可覆盖。
*/
function resolveCatalogLanguage(language) {
const normalized = normalizeUiLanguage(language);
return I18N_CATALOG[normalized] ? normalized : UI_LANGUAGE_FALLBACKS[normalized] || DEFAULT_UI_LANGUAGE;
}
function resolveCatalog(language) {
return I18N_CATALOG[resolveCatalogLanguage(language)] || I18N_CATALOG[DEFAULT_UI_LANGUAGE];
}
function deepClone(value) {
if (Array.isArray(value)) {
return value.map((item) => deepClone(item));
}
if (value && typeof value === "object") {
const next = {};
Object.keys(value).forEach((key) => {
next[key] = deepClone(value[key]);
});
return next;
}
return value;
}
function readByPath(source, path) {
if (!source || typeof source !== "object") return undefined;
const segments = String(path || "")
.split(".")
.filter((item) => !!item);
let current = source;
for (let i = 0; i < segments.length; i += 1) {
const key = segments[i];
if (!current || typeof current !== "object" || !(key in current)) {
return undefined;
}
current = current[key];
}
return current;
}
function formatTemplate(template, params) {
const source = params && typeof params === "object" ? params : {};
return String(template || "").replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
if (!(key in source)) return "";
return String(source[key] == null ? "" : source[key]);
});
}
function t(language, key, params) {
const value = readByPath(resolveCatalog(language), key);
if (typeof value !== "string") {
return key ? String(key) : "";
}
return params ? formatTemplate(value, params) : value;
}
function buildPageCopy(language, namespace) {
const value = readByPath(resolveCatalog(language), namespace);
return value && typeof value === "object" ? deepClone(value) : {};
}
function getStatusLabel(language, status) {
const normalized = String(status || "").trim();
return t(language, `common.statusLabels.${normalized}`) || normalized;
}
function getRuntimeStateLabel(language, status) {
const normalized = String(status || "").trim();
return t(language, `common.runtimeStateLabels.${normalized}`) || normalized;
}
function getUiLanguageOptions() {
return [
{ label: "简体中文", value: "zh-Hans" },
{ label: "繁體中文", value: "zh-Hant" },
{ label: "English", value: "en" },
{ label: "日本語", value: "ja" },
{ label: "한국어", value: "ko" }
];
}
function buildThemeModeOptions(language) {
return [
{ label: t(language, "settings.options.themeMode.dark"), value: "dark" },
{ label: t(language, "settings.options.themeMode.light"), value: "light" }
];
}
function buildSettingsAuthTypeOptions(language) {
return [
{ label: t(language, "settings.options.authType.password"), value: "password" },
{ label: t(language, "settings.options.authType.key"), value: "key" }
];
}
function buildAiCodexSandboxOptions(language) {
return [
{ label: t(language, "settings.options.aiCodexSandboxMode.readOnly"), value: "read-only" },
{ label: t(language, "settings.options.aiCodexSandboxMode.workspaceWrite"), value: "workspace-write" },
{
label: t(language, "settings.options.aiCodexSandboxMode.dangerFullAccess"),
value: "danger-full-access"
}
];
}
function buildAiProviderOptions(language) {
return [
{ label: t(language, "settings.options.aiDefaultProvider.codex"), value: "codex" },
{ label: t(language, "settings.options.aiDefaultProvider.copilot"), value: "copilot" }
];
}
function buildAiCopilotPermissionOptions(language) {
return [
{ label: t(language, "settings.options.aiCopilotPermissionMode.default"), value: "default" },
{
label: t(language, "settings.options.aiCopilotPermissionMode.experimental"),
value: "experimental"
},
{ label: t(language, "settings.options.aiCopilotPermissionMode.allowAll"), value: "allow-all" }
];
}
function buildServerAuthTypeOptions(language) {
return [
{ label: t(language, "serverSettings.options.authType.password"), value: "password" },
{ label: t(language, "serverSettings.options.authType.privateKey"), value: "privateKey" },
{ label: t(language, "serverSettings.options.authType.certificate"), value: "certificate" }
];
}
function buildSettingsTabs(language) {
return [
{ id: "ui", label: t(language, "settings.tabs.ui") },
{ id: "shell", label: t(language, "settings.tabs.shell") },
{ id: "connection", label: t(language, "settings.tabs.connection") },
{ id: "log", label: t(language, "settings.tabs.log") }
];
}
/**
* 主题预设内部值保持稳定,界面层再按语言映射为用户可读展示名。
*/
function getThemePresetLabel(language, value) {
const normalizedLanguage = normalizeUiLanguage(language);
return THEME_PRESET_LABELS[normalizedLanguage]?.[value] || String(value || "");
}
function buildThemePresetOptions(language) {
return THEME_PRESET_VALUES.map((value) => ({
value,
label: getThemePresetLabel(language, value)
}));
}
function localizeServerValidationMessage(language, message) {
const raw = String(message || "").trim();
const localizedLanguage = resolveCatalogLanguage(language);
if (!raw) return raw;
const map = {
主机不能为空: {
"zh-Hant": "主機不能為空",
en: "Host is required",
ja: "ホストは必須です",
ko: "호스트는 필수입니다"
},
用户名不能为空: {
"zh-Hant": "使用者名稱不能為空",
en: "Username is required",
ja: "ユーザー名は必須です",
ko: "사용자명은 필수입니다"
},
密码不能为空: {
"zh-Hant": "密碼不能為空",
en: "Password is required",
ja: "パスワードは必須です",
ko: "비밀번호는 필수입니다"
},
私钥内容不能为空: {
"zh-Hant": "私鑰內容不能為空",
en: "Private key is required",
ja: "秘密鍵は必須です",
ko: "개인 키는 필수입니다"
},
证书模式下私钥不能为空: {
"zh-Hant": "憑證模式下私鑰不能為空",
en: "Private key is required for certificate mode",
ja: "証明書モードでは秘密鍵が必須です",
ko: "인증서 모드에서는 개인 키가 필요합니다"
},
证书模式下证书不能为空: {
"zh-Hant": "憑證模式下憑證不能為空",
en: "Certificate is required for certificate mode",
ja: "証明書モードでは証明書が必須です",
ko: "인증서 모드에서는 인증서가 필요합니다"
},
跳板机主机不能为空: {
"zh-Hant": "跳板機主機不能為空",
en: "Jump host is required",
ja: "踏み台ホストは必須です",
ko: "점프 호스트는 필수입니다"
},
跳板机用户名不能为空: {
"zh-Hant": "跳板機使用者名稱不能為空",
en: "Jump host username is required",
ja: "踏み台ユーザー名は必須です",
ko: "점프 호스트 사용자명은 필수입니다"
},
跳板机密码不能为空: {
"zh-Hant": "跳板機密碼不能為空",
en: "Jump host password is required",
ja: "踏み台パスワードは必須です",
ko: "점프 호스트 비밀번호는 필수입니다"
},
跳板机私钥不能为空: {
"zh-Hant": "跳板機私鑰不能為空",
en: "Jump host private key is required",
ja: "踏み台秘密鍵は必須です",
ko: "점프 호스트 개인 키는 필수입니다"
},
跳板机证书模式下私钥不能为空: {
"zh-Hant": "跳板機憑證模式下私鑰不能為空",
en: "Jump host private key is required for certificate mode",
ja: "踏み台の証明書モードでは秘密鍵が必須です",
ko: "점프 호스트 인증서 모드에서는 개인 키가 필요합니다"
},
跳板机证书模式下证书不能为空: {
"zh-Hant": "跳板機憑證模式下憑證不能為空",
en: "Jump host certificate is required for certificate mode",
ja: "踏み台の証明書モードでは証明書が必須です",
ko: "점프 호스트 인증서 모드에서는 인증서가 필요합니다"
}
};
if (map[raw] && map[raw][localizedLanguage]) {
return map[raw][localizedLanguage];
}
if (/^SSH 端口需为 1-65535 的整数$/.test(raw)) {
if (localizedLanguage === "zh-Hant") return "SSH 埠需為 1-65535 的整數";
if (localizedLanguage === "en") return "SSH port must be an integer between 1 and 65535";
if (localizedLanguage === "ja") return "SSH ポートは 1〜65535 の整数で入力してください";
if (localizedLanguage === "ko") return "SSH 포트는 1~65535 사이 정수여야 합니다";
}
if (/^跳板机端口需为 1-65535 的整数$/.test(raw)) {
if (localizedLanguage === "zh-Hant") return "跳板機埠需為 1-65535 的整數";
if (localizedLanguage === "en") return "Jump host port must be an integer between 1 and 65535";
if (localizedLanguage === "ja") return "踏み台ポートは 1〜65535 の整数で入力してください";
if (localizedLanguage === "ko") return "점프 호스트 포트는 1~65535 사이 정수여야 합니다";
}
return raw;
}
module.exports = {
DEFAULT_UI_LANGUAGE,
UI_LANGUAGE_VALUES,
buildAiCodexSandboxOptions,
buildAiCopilotPermissionOptions,
buildAiProviderOptions,
buildPageCopy,
buildServerAuthTypeOptions,
buildSettingsAuthTypeOptions,
buildSettingsTabs,
buildThemeModeOptions,
buildThemePresetOptions,
formatTemplate,
getThemePresetLabel,
getRuntimeStateLabel,
getStatusLabel,
getUiLanguageOptions,
localizeServerValidationMessage,
normalizeUiLanguage,
t
};