From c715327739882f05dc41bbc932db1001f751642e Mon Sep 17 00:00:00 2001 From: Slinetrac Date: Thu, 6 Nov 2025 13:40:16 +0800 Subject: [PATCH] feat(i18n): add type-checked translation keys --- package.json | 2 + pnpm-lock.yaml | 18 + scripts/generate-i18n-keys.mjs | 98 ++ src/components/base/NoticeManager.tsx | 5 +- src/components/base/base-empty.tsx | 4 +- src/components/home/clash-mode-card.tsx | 6 +- .../profile/groups-editor-viewer.tsx | 10 +- src/components/profile/profile-item.tsx | 3 +- src/types/generated/i18n-keys.ts | 724 +++++++++ src/types/generated/i18n-resources.ts | 1304 +++++++++++++++++ src/types/i18next.d.ts | 11 + src/types/react-i18next.d.ts | 39 + 12 files changed, 2215 insertions(+), 9 deletions(-) create mode 100644 scripts/generate-i18n-keys.mjs create mode 100644 src/types/generated/i18n-keys.ts create mode 100644 src/types/generated/i18n-resources.ts create mode 100644 src/types/i18next.d.ts create mode 100644 src/types/react-i18next.d.ts diff --git a/package.json b/package.json index 9619f5c1..e23c75df 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "format": "prettier --write .", "format:check": "prettier --check .", "format:i18n": "node scripts/cleanup-unused-i18n.mjs --align --apply", + "i18n:types": "node scripts/generate-i18n-keys.mjs", "typecheck": "tsc --noEmit", "test": "vitest run" }, @@ -97,6 +98,7 @@ "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-i18next": "^6.1.3", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react-hooks": "^7.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac86b7bd..dd1558f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,6 +189,9 @@ importers: eslint-import-resolver-typescript: specifier: ^4.4.4 version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-i18next: + specifier: ^6.1.3 + version: 6.1.3 eslint-plugin-import-x: specifier: ^4.16.1 version: 4.16.1(@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) @@ -2566,6 +2569,10 @@ packages: eslint-import-resolver-webpack: optional: true + eslint-plugin-i18next@6.1.3: + resolution: {integrity: sha512-z/h4oBRd9wI1ET60HqcLSU6XPeAh/EPOrBBTyCdkWeMoYrWAaUVA+DOQkWTiNIyCltG4NTmy62SQisVXxoXurw==} + engines: {node: '>=18.10.0'} + eslint-plugin-import-x@4.16.1: resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3724,6 +3731,10 @@ packages: remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + requireindex@1.1.0: + resolution: {integrity: sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==} + engines: {node: '>=0.10.5'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -6853,6 +6864,11 @@ snapshots: - supports-color optional: true + eslint-plugin-i18next@6.1.3: + dependencies: + lodash: 4.17.21 + requireindex: 1.1.0 + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@typescript-eslint/types': 8.46.3 @@ -8310,6 +8326,8 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + requireindex@1.1.0: {} + reselect@5.1.1: {} resize-observer-polyfill@1.5.1: {} diff --git a/scripts/generate-i18n-keys.mjs b/scripts/generate-i18n-keys.mjs new file mode 100644 index 00000000..60cf9385 --- /dev/null +++ b/scripts/generate-i18n-keys.mjs @@ -0,0 +1,98 @@ +#!/usr/bin/env node +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const ROOT_DIR = path.resolve(__dirname, ".."); +const LOCALE_DIR = path.resolve(ROOT_DIR, "src/locales/en"); +const KEY_OUTPUT = path.resolve(ROOT_DIR, "src/types/generated/i18n-keys.ts"); +const RESOURCE_OUTPUT = path.resolve( + ROOT_DIR, + "src/types/generated/i18n-resources.ts", +); + +const isPlainObject = (value) => + typeof value === "object" && value !== null && !Array.isArray(value); + +const flattenKeys = (data, prefix = "") => { + const keys = []; + for (const [key, value] of Object.entries(data)) { + const nextPrefix = prefix ? `${prefix}.${key}` : key; + if (isPlainObject(value)) { + keys.push(...flattenKeys(value, nextPrefix)); + } else { + keys.push(nextPrefix); + } + } + return keys; +}; + +const buildType = (data, indent = 0) => { + if (!isPlainObject(data)) { + return "string"; + } + + const entries = Object.entries(data).sort(([a], [b]) => a.localeCompare(b)); + const pad = " ".repeat(indent); + const inner = entries + .map(([key, value]) => { + const typeStr = buildType(value, indent + 2); + return `${" ".repeat(indent + 2)}${JSON.stringify(key)}: ${typeStr};`; + }) + .join("\n"); + + return entries.length + ? `{ +${inner} +${pad}}` + : "{}"; +}; + +const loadNamespaceJson = async () => { + const dirents = await fs.readdir(LOCALE_DIR, { withFileTypes: true }); + const namespaces = []; + for (const dirent of dirents) { + if (!dirent.isFile() || !dirent.name.endsWith(".json")) continue; + const name = dirent.name.replace(/\.json$/, ""); + const filePath = path.join(LOCALE_DIR, dirent.name); + const raw = await fs.readFile(filePath, "utf8"); + const json = JSON.parse(raw); + namespaces.push({ name, json }); + } + namespaces.sort((a, b) => a.name.localeCompare(b.name)); + return namespaces; +}; + +const buildKeysFile = (keys) => { + const arrayLiteral = keys.map((key) => ` \"${key}\"`).join(",\n"); + return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport const translationKeys = [\n${arrayLiteral}\n] as const;\n\nexport type TranslationKey = typeof translationKeys[number];\n`; +}; + +const buildResourcesFile = (namespaces) => { + const namespaceEntries = namespaces + .map(({ name, json }) => { + const typeStr = buildType(json, 4); + return ` ${JSON.stringify(name)}: ${typeStr};`; + }) + .join("\n"); + + return `// This file is auto-generated by scripts/generate-i18n-keys.mjs\n// Do not edit this file manually.\n\nexport interface TranslationResources {\n translation: {\n${namespaceEntries}\n };\n}\n`; +}; + +const main = async () => { + const namespaces = await loadNamespaceJson(); + const keys = namespaces.flatMap(({ name, json }) => flattenKeys(json, name)); + const keysContent = buildKeysFile(keys); + const resourcesContent = buildResourcesFile(namespaces); + await fs.mkdir(path.dirname(KEY_OUTPUT), { recursive: true }); + await fs.writeFile(KEY_OUTPUT, keysContent, "utf8"); + await fs.writeFile(RESOURCE_OUTPUT, resourcesContent, "utf8"); + console.log(`Generated ${keys.length} translation keys.`); +}; + +main().catch((error) => { + console.error("Failed to generate i18n metadata:", error); + process.exitCode = 1; +}); diff --git a/src/components/base/NoticeManager.tsx b/src/components/base/NoticeManager.tsx index a34f2584..b70c5886 100644 --- a/src/components/base/NoticeManager.tsx +++ b/src/components/base/NoticeManager.tsx @@ -8,6 +8,7 @@ import { hideNotice, getSnapshotNotices, } from "@/services/noticeService"; +import type { TranslationKey } from "@/types/generated/i18n-keys"; export const NoticeManager: React.FC = () => { const { t } = useTranslation(); @@ -85,7 +86,7 @@ export const NoticeManager: React.FC = () => { const resolvedPrefix = typeof prefixKey === "string" - ? t(prefixKey, { + ? t(prefixKey as TranslationKey, { defaultValue: prefixKey, ...(prefixKeyParams ?? {}), }) @@ -110,7 +111,7 @@ export const NoticeManager: React.FC = () => { ? message : undefined; - return t(notice.i18n.key, { + return t(notice.i18n.key as TranslationKey, { defaultValue, ...finalParams, }); diff --git a/src/components/base/base-empty.tsx b/src/components/base/base-empty.tsx index 441ec6e6..acbe36c4 100644 --- a/src/components/base/base-empty.tsx +++ b/src/components/base/base-empty.tsx @@ -3,9 +3,11 @@ import { alpha, Box, Typography } from "@mui/material"; import type { ReactNode } from "react"; import { useTranslation } from "react-i18next"; +import type { TranslationKey } from "@/types/generated/i18n-keys"; + interface Props { text?: ReactNode; - textKey?: string; + textKey?: TranslationKey; extra?: ReactNode; } diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx index b16f4cb5..ea48128c 100644 --- a/src/components/home/clash-mode-card.tsx +++ b/src/components/home/clash-mode-card.tsx @@ -12,6 +12,7 @@ import { closeAllConnections } from "tauri-plugin-mihomo-api"; import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-context"; import { patchClashMode } from "@/services/cmds"; +import type { TranslationKey } from "@/types/generated/i18n-keys"; const CLASH_MODES = ["rule", "global", "direct"] as const; type ClashMode = (typeof CLASH_MODES)[number]; @@ -19,7 +20,10 @@ type ClashMode = (typeof CLASH_MODES)[number]; const isClashMode = (mode: string): mode is ClashMode => (CLASH_MODES as readonly string[]).includes(mode); -const MODE_META: Record = { +const MODE_META: Record< + ClashMode, + { label: TranslationKey; description: TranslationKey } +> = { rule: { label: "home.components.clashMode.labels.rule", description: "home.components.clashMode.descriptions.rule", diff --git a/src/components/profile/groups-editor-viewer.tsx b/src/components/profile/groups-editor-viewer.tsx index 7a2d5e5f..3a3b6a15 100644 --- a/src/components/profile/groups-editor-viewer.tsx +++ b/src/components/profile/groups-editor-viewer.tsx @@ -57,6 +57,7 @@ import { } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import { useThemeMode } from "@/services/states"; +import type { TranslationKey } from "@/types/generated/i18n-keys"; import getSystem from "@/utils/get-system"; import { BaseSearchBox } from "../base/base-search-box"; @@ -73,7 +74,7 @@ interface Props { const builtinProxyPolicies = ["DIRECT", "REJECT", "REJECT-DROP", "PASS"]; -const PROXY_STRATEGY_LABEL_KEYS: Record = { +const PROXY_STRATEGY_LABEL_KEYS: Record = { select: "proxies.components.enums.strategies.select", "url-test": "proxies.components.enums.strategies.url-test", fallback: "proxies.components.enums.strategies.fallback", @@ -81,13 +82,14 @@ const PROXY_STRATEGY_LABEL_KEYS: Record = { relay: "proxies.components.enums.strategies.relay", }; -const PROXY_POLICY_LABEL_KEYS: Record = +const PROXY_POLICY_LABEL_KEYS: Record = builtinProxyPolicies.reduce( (acc, policy) => { - acc[policy] = `proxies.components.enums.policies.${policy}`; + acc[policy] = + `proxies.components.enums.policies.${policy}` as TranslationKey; return acc; }, - {} as Record, + {} as Record, ); const normalizeDeleteSeq = (input?: unknown): string[] => { diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index fc76d2bc..7f928336 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -36,6 +36,7 @@ import { } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import { useLoadingCache, useSetLoadingCache } from "@/services/states"; +import type { TranslationKey } from "@/types/generated/i18n-keys"; import parseTraffic from "@/utils/parse-traffic"; import { ProfileBox } from "./profile-box"; @@ -372,7 +373,7 @@ export const ProfileItem = (props: Props) => { disabled: boolean; }; - const menuLabels = { + const menuLabels: Record = { home: "profiles.components.menu.home", select: "profiles.components.menu.select", editInfo: "profiles.components.menu.editInfo", diff --git a/src/types/generated/i18n-keys.ts b/src/types/generated/i18n-keys.ts new file mode 100644 index 00000000..536d7a8e --- /dev/null +++ b/src/types/generated/i18n-keys.ts @@ -0,0 +1,724 @@ +// This file is auto-generated by scripts/generate-i18n-keys.mjs +// Do not edit this file manually. + +export const translationKeys = [ + "connections.page.title", + "connections.components.fields.host", + "connections.components.fields.dlSpeed", + "connections.components.fields.ulSpeed", + "connections.components.fields.chains", + "connections.components.fields.rule", + "connections.components.fields.process", + "connections.components.fields.time", + "connections.components.fields.source", + "connections.components.fields.destination", + "connections.components.fields.destinationPort", + "connections.components.fields.type", + "connections.components.order.default", + "connections.components.order.uploadSpeed", + "connections.components.order.downloadSpeed", + "connections.components.actions.closeConnection", + "home.page.tooltips.lightweightMode", + "home.page.tooltips.manual", + "home.page.tooltips.settings", + "home.page.cards.trafficStats", + "home.page.cards.networkSettings", + "home.page.cards.proxyMode", + "home.page.settings.cards.profile", + "home.page.settings.cards.currentProxy", + "home.page.settings.cards.network", + "home.page.settings.cards.proxyMode", + "home.page.settings.cards.traffic", + "home.page.settings.cards.tests", + "home.page.settings.cards.ip", + "home.page.settings.cards.clashInfo", + "home.page.settings.cards.systemInfo", + "home.page.settings.title", + "home.page.title", + "home.components.proxyTun.status.systemProxyEnabled", + "home.components.proxyTun.status.systemProxyDisabled", + "home.components.proxyTun.status.tunModeServiceRequired", + "home.components.proxyTun.status.tunModeEnabled", + "home.components.proxyTun.status.tunModeDisabled", + "home.components.proxyTun.tooltips.systemProxy", + "home.components.proxyTun.tooltips.tunMode", + "home.components.clashInfo.title", + "home.components.clashInfo.fields.coreVersion", + "home.components.clashInfo.fields.systemProxyAddress", + "home.components.clashInfo.fields.mixedPort", + "home.components.clashInfo.fields.uptime", + "home.components.clashInfo.fields.rulesCount", + "home.components.systemInfo.title", + "home.components.systemInfo.fields.osInfo", + "home.components.systemInfo.fields.autoLaunch", + "home.components.systemInfo.fields.runningMode", + "home.components.systemInfo.fields.lastCheckUpdate", + "home.components.systemInfo.fields.vergeVersion", + "home.components.systemInfo.actions.settings", + "home.components.systemInfo.tooltips.autoLaunchAdmin", + "home.components.systemInfo.badges.adminMode", + "home.components.systemInfo.badges.serviceMode", + "home.components.systemInfo.badges.sidecarMode", + "home.components.systemInfo.badges.adminServiceMode", + "home.components.ipInfo.title", + "home.components.ipInfo.labels.ip", + "home.components.ipInfo.labels.asn", + "home.components.ipInfo.labels.isp", + "home.components.ipInfo.labels.org", + "home.components.ipInfo.labels.location", + "home.components.ipInfo.labels.timezone", + "home.components.ipInfo.labels.autoRefresh", + "home.components.ipInfo.labels.unknown", + "home.components.ipInfo.errors.load", + "home.components.currentProxy.title", + "home.components.currentProxy.actions.refreshDelay", + "home.components.currentProxy.labels.globalMode", + "home.components.currentProxy.labels.directMode", + "home.components.currentProxy.labels.group", + "home.components.currentProxy.labels.proxy", + "home.components.currentProxy.labels.noActiveNode", + "home.components.tests.title", + "home.components.traffic.metrics.uploadSpeed", + "home.components.traffic.metrics.downloadSpeed", + "home.components.traffic.metrics.activeConnections", + "home.components.traffic.metrics.memoryUsage", + "home.components.traffic.legends.upload", + "home.components.traffic.legends.download", + "home.components.traffic.patterns.minutes", + "home.components.clashMode.errors.communication", + "home.components.clashMode.labels.rule", + "home.components.clashMode.labels.global", + "home.components.clashMode.labels.direct", + "home.components.clashMode.descriptions.rule", + "home.components.clashMode.descriptions.global", + "home.components.clashMode.descriptions.direct", + "layout.components.navigation.tabs.home", + "layout.components.navigation.tabs.proxies", + "layout.components.navigation.tabs.profiles", + "layout.components.navigation.tabs.connections", + "layout.components.navigation.tabs.rules", + "layout.components.navigation.tabs.logs", + "layout.components.navigation.tabs.unlock", + "layout.components.navigation.tabs.settings", + "layout.components.navigation.menu.reorderMode", + "layout.components.navigation.menu.unlock", + "layout.components.navigation.menu.lock", + "logs.page.title", + "profiles.page.actions.updateAll", + "profiles.page.actions.viewRuntimeConfig", + "profiles.page.actions.reactivate", + "profiles.page.actions.import", + "profiles.page.batch.actions.delete", + "profiles.page.batch.actions.selectAll", + "profiles.page.batch.actions.deselectAll", + "profiles.page.batch.actions.done", + "profiles.page.batch.summary.selected", + "profiles.page.batch.summary.items", + "profiles.page.batch.title", + "profiles.page.importForm.placeholder", + "profiles.page.importForm.actions.paste", + "profiles.page.feedback.errors.invalidUrl", + "profiles.page.feedback.errors.onlyYaml", + "profiles.page.feedback.notifications.importRetry", + "profiles.page.feedback.notifications.importFail", + "profiles.page.feedback.notifications.importNeedsRefresh", + "profiles.page.feedback.notifications.importSuccess", + "profiles.page.feedback.notifications.profileSwitched", + "profiles.page.feedback.notifications.profileReactivated", + "profiles.page.feedback.notifications.switchInterrupted", + "profiles.page.feedback.notifications.batchDeleted", + "profiles.page.feedback.notices.forceRefreshCompleted", + "profiles.page.feedback.notices.emergencyRefreshFailed", + "profiles.page.title", + "profiles.components.card.labels.clickToImport", + "profiles.components.fileInput.chooseFile", + "profiles.components.menu.home", + "profiles.components.menu.select", + "profiles.components.menu.editInfo", + "profiles.components.menu.editFile", + "profiles.components.menu.editRules", + "profiles.components.menu.editProxies", + "profiles.components.menu.editGroups", + "profiles.components.menu.extendConfig", + "profiles.components.menu.extendScript", + "profiles.components.menu.openFile", + "profiles.components.menu.update", + "profiles.components.menu.updateViaProxy", + "profiles.components.more.global.merge", + "profiles.components.more.global.script", + "profiles.components.more.chips.merge", + "profiles.components.more.chips.script", + "profiles.components.profileItem.tooltips.showLast", + "profiles.components.profileItem.tooltips.showNext", + "profiles.components.profileItem.status.lastUpdateFailed", + "profiles.components.profileItem.status.nextUp", + "profiles.components.profileItem.status.noSchedule", + "profiles.components.profileItem.status.unknown", + "profiles.components.profileItem.status.autoUpdateDisabled", + "profiles.modals.profileForm.title.create", + "profiles.modals.profileForm.title.edit", + "profiles.modals.profileForm.fields.type", + "profiles.modals.profileForm.fields.description", + "profiles.modals.profileForm.fields.subscriptionUrl", + "profiles.modals.profileForm.fields.httpTimeout", + "profiles.modals.profileForm.fields.updateInterval", + "profiles.modals.profileForm.fields.useSystemProxy", + "profiles.modals.profileForm.fields.useClashProxy", + "profiles.modals.profileForm.fields.acceptInvalidCerts", + "profiles.modals.profileForm.fields.allowAutoUpdate", + "profiles.modals.profileForm.feedback.notifications.creationRetry", + "profiles.modals.profileForm.feedback.notifications.creationSuccess", + "profiles.modals.proxiesEditor.title", + "profiles.modals.proxiesEditor.placeholders.multiUri", + "profiles.modals.proxiesEditor.actions.prepend", + "profiles.modals.proxiesEditor.actions.append", + "profiles.modals.groupsEditor.title", + "profiles.modals.groupsEditor.errors.nameRequired", + "profiles.modals.groupsEditor.errors.nameExists", + "profiles.modals.groupsEditor.fields.type", + "profiles.modals.groupsEditor.fields.name", + "profiles.modals.groupsEditor.fields.icon", + "profiles.modals.groupsEditor.fields.proxies", + "profiles.modals.groupsEditor.fields.provider", + "profiles.modals.groupsEditor.fields.healthCheckUrl", + "profiles.modals.groupsEditor.fields.expectedStatus", + "profiles.modals.groupsEditor.fields.interval", + "profiles.modals.groupsEditor.fields.maxFailedTimes", + "profiles.modals.groupsEditor.fields.interfaceName", + "profiles.modals.groupsEditor.fields.routingMark", + "profiles.modals.groupsEditor.fields.filter", + "profiles.modals.groupsEditor.fields.excludeFilter", + "profiles.modals.groupsEditor.fields.excludeType", + "profiles.modals.groupsEditor.fields.includeAll", + "profiles.modals.groupsEditor.fields.includeAllProxies", + "profiles.modals.groupsEditor.fields.includeAllProviders", + "profiles.modals.groupsEditor.toggles.lazy", + "profiles.modals.groupsEditor.toggles.disableUdp", + "profiles.modals.groupsEditor.toggles.hidden", + "profiles.modals.groupsEditor.actions.prepend", + "profiles.modals.groupsEditor.actions.append", + "profiles.modals.editor.actions.format", + "profiles.modals.editor.messages.readOnly", + "profiles.modals.confirmDelete.title", + "profiles.modals.confirmDelete.message", + "profiles.modals.logViewer.title", + "proxies.page.modes.rule", + "proxies.page.modes.global", + "proxies.page.modes.direct", + "proxies.page.actions.toggleChain", + "proxies.page.actions.connect", + "proxies.page.actions.disconnect", + "proxies.page.actions.connecting", + "proxies.page.actions.clearChainConfig", + "proxies.page.provider.title", + "proxies.page.provider.actions.updateAll", + "proxies.page.provider.actions.update", + "proxies.page.rules.title", + "proxies.page.rules.select", + "proxies.page.labels.proxyCount", + "proxies.page.labels.delayCheckReset", + "proxies.page.tooltips.locate", + "proxies.page.tooltips.delayCheck", + "proxies.page.tooltips.sortDefault", + "proxies.page.tooltips.sortDelay", + "proxies.page.tooltips.sortName", + "proxies.page.tooltips.delayCheckUrl", + "proxies.page.tooltips.showBasic", + "proxies.page.tooltips.showDetail", + "proxies.page.tooltips.filter", + "proxies.page.placeholders.delayCheckUrl", + "proxies.page.chain.header", + "proxies.page.chain.empty", + "proxies.page.chain.instruction", + "proxies.page.chain.minimumNodes", + "proxies.page.chain.minimumNodesHint", + "proxies.page.chain.connectFailed", + "proxies.page.chain.disconnectFailed", + "proxies.page.chain.duplicateNode", + "proxies.page.messages.directMode", + "proxies.page.title.default", + "proxies.page.title.chainMode", + "proxies.feedback.notifications.provider.updateSuccess", + "proxies.feedback.notifications.provider.updateFailed", + "proxies.feedback.notifications.provider.genericError", + "proxies.feedback.notifications.provider.none", + "proxies.feedback.notifications.provider.allUpdated", + "proxies.components.enums.strategies.select", + "proxies.components.enums.strategies.url-test", + "proxies.components.enums.strategies.fallback", + "proxies.components.enums.strategies.load-balance", + "proxies.components.enums.strategies.relay", + "proxies.components.enums.policies.DIRECT", + "proxies.components.enums.policies.REJECT", + "proxies.components.enums.policies.REJECT-DROP", + "proxies.components.enums.policies.PASS", + "rules.page.provider.trigger", + "rules.page.provider.dialogTitle", + "rules.page.provider.actions.updateAll", + "rules.page.provider.actions.update", + "rules.page.title", + "rules.feedback.notifications.provider.updateSuccess", + "rules.feedback.notifications.provider.updateFailed", + "rules.feedback.notifications.provider.genericError", + "rules.feedback.notifications.provider.none", + "rules.feedback.notifications.provider.allUpdated", + "rules.modals.editor.form.labels.type", + "rules.modals.editor.form.labels.content", + "rules.modals.editor.form.labels.proxyPolicy", + "rules.modals.editor.form.toggles.noResolve", + "rules.modals.editor.form.actions.prependRule", + "rules.modals.editor.form.actions.appendRule", + "rules.modals.editor.form.validation.conditionRequired", + "rules.modals.editor.form.validation.invalidRule", + "rules.modals.editor.ruleTypes.DOMAIN", + "rules.modals.editor.ruleTypes.DOMAIN-SUFFIX", + "rules.modals.editor.ruleTypes.DOMAIN-KEYWORD", + "rules.modals.editor.ruleTypes.DOMAIN-REGEX", + "rules.modals.editor.ruleTypes.GEOSITE", + "rules.modals.editor.ruleTypes.GEOIP", + "rules.modals.editor.ruleTypes.SRC-GEOIP", + "rules.modals.editor.ruleTypes.IP-ASN", + "rules.modals.editor.ruleTypes.SRC-IP-ASN", + "rules.modals.editor.ruleTypes.IP-CIDR", + "rules.modals.editor.ruleTypes.IP-CIDR6", + "rules.modals.editor.ruleTypes.SRC-IP-CIDR", + "rules.modals.editor.ruleTypes.IP-SUFFIX", + "rules.modals.editor.ruleTypes.SRC-IP-SUFFIX", + "rules.modals.editor.ruleTypes.SRC-PORT", + "rules.modals.editor.ruleTypes.DST-PORT", + "rules.modals.editor.ruleTypes.IN-PORT", + "rules.modals.editor.ruleTypes.DSCP", + "rules.modals.editor.ruleTypes.PROCESS-NAME", + "rules.modals.editor.ruleTypes.PROCESS-PATH", + "rules.modals.editor.ruleTypes.PROCESS-NAME-REGEX", + "rules.modals.editor.ruleTypes.PROCESS-PATH-REGEX", + "rules.modals.editor.ruleTypes.NETWORK", + "rules.modals.editor.ruleTypes.UID", + "rules.modals.editor.ruleTypes.IN-TYPE", + "rules.modals.editor.ruleTypes.IN-USER", + "rules.modals.editor.ruleTypes.IN-NAME", + "rules.modals.editor.ruleTypes.SUB-RULE", + "rules.modals.editor.ruleTypes.RULE-SET", + "rules.modals.editor.ruleTypes.AND", + "rules.modals.editor.ruleTypes.OR", + "rules.modals.editor.ruleTypes.NOT", + "rules.modals.editor.ruleTypes.MATCH", + "rules.modals.editor.title", + "settings.page.actions.manual", + "settings.page.actions.telegram", + "settings.page.actions.github", + "settings.page.title", + "settings.sections.system.title", + "settings.sections.system.toggles.tunMode", + "settings.sections.system.toggles.systemProxy", + "settings.sections.system.tooltips.autoLaunchAdmin", + "settings.sections.system.tooltips.silentStart", + "settings.sections.system.fields.autoLaunch", + "settings.sections.system.fields.silentStart", + "settings.sections.system.notifications.tunMode.autoDisabled", + "settings.sections.system.notifications.tunMode.autoDisableFailed", + "settings.sections.proxyControl.tooltips.systemProxy", + "settings.sections.proxyControl.tooltips.tunMode", + "settings.sections.proxyControl.tooltips.tunUnavailable", + "settings.sections.proxyControl.actions.installService", + "settings.sections.proxyControl.actions.uninstallService", + "settings.sections.proxyControl.fields.systemProxy", + "settings.sections.proxyControl.fields.tunMode", + "settings.sections.externalController.title", + "settings.sections.externalController.fields.enable", + "settings.sections.externalController.fields.address", + "settings.sections.externalController.fields.secret", + "settings.sections.externalController.placeholders.address", + "settings.sections.externalController.placeholders.secret", + "settings.sections.externalController.tooltips.copy", + "settings.sections.externalController.messages.addressRequired", + "settings.sections.externalController.messages.secretRequired", + "settings.sections.externalController.messages.copyFailed", + "settings.sections.externalController.messages.controllerCopied", + "settings.sections.externalController.messages.secretCopied", + "settings.sections.externalCors.title", + "settings.sections.externalCors.fields.allowPrivateNetwork", + "settings.sections.externalCors.fields.allowedOrigins", + "settings.sections.externalCors.placeholders.origin", + "settings.sections.externalCors.actions.add", + "settings.sections.externalCors.messages.alwaysIncluded", + "settings.sections.externalCors.tooltips.open", + "settings.sections.appearance.light", + "settings.sections.appearance.dark", + "settings.sections.appearance.system", + "settings.sections.clash.title", + "settings.sections.clash.form.fields.allowLan", + "settings.sections.clash.form.fields.dnsOverwrite", + "settings.sections.clash.form.fields.ipv6", + "settings.sections.clash.form.fields.unifiedDelay", + "settings.sections.clash.form.fields.logLevel", + "settings.sections.clash.form.fields.portConfig", + "settings.sections.clash.form.fields.external", + "settings.sections.clash.form.fields.webUI", + "settings.sections.clash.form.fields.clashCore", + "settings.sections.clash.form.fields.openUwpTool", + "settings.sections.clash.form.fields.updateGeoData", + "settings.sections.clash.form.tooltips.networkInterface", + "settings.sections.clash.form.tooltips.unifiedDelay", + "settings.sections.clash.form.tooltips.logLevel", + "settings.sections.clash.form.tooltips.openUwpTool", + "settings.sections.clash.form.options.logLevel.debug", + "settings.sections.clash.form.options.logLevel.info", + "settings.sections.clash.form.options.logLevel.warning", + "settings.sections.clash.form.options.logLevel.error", + "settings.sections.clash.form.options.logLevel.silent", + "settings.components.verge.basic.title", + "settings.components.verge.basic.actions.browse", + "settings.components.verge.basic.trayOptions.showMainWindow", + "settings.components.verge.basic.trayOptions.showTrayMenu", + "settings.components.verge.basic.trayOptions.disable", + "settings.components.verge.basic.fields.language", + "settings.components.verge.basic.fields.themeMode", + "settings.components.verge.basic.fields.trayClickEvent", + "settings.components.verge.basic.fields.copyEnvType", + "settings.components.verge.basic.fields.startPage", + "settings.components.verge.basic.fields.startupScript", + "settings.components.verge.basic.fields.themeSetting", + "settings.components.verge.basic.fields.layoutSetting", + "settings.components.verge.basic.fields.misc", + "settings.components.verge.basic.fields.hotkeySetting", + "settings.components.verge.advanced.title", + "settings.components.verge.advanced.tooltips.backupInfo", + "settings.components.verge.advanced.tooltips.openConfDir", + "settings.components.verge.advanced.tooltips.liteMode", + "settings.components.verge.advanced.actions.copyVersion", + "settings.components.verge.advanced.notifications.latestVersion", + "settings.components.verge.advanced.notifications.versionCopied", + "settings.components.verge.advanced.fields.backupSetting", + "settings.components.verge.advanced.fields.runtimeConfig", + "settings.components.verge.advanced.fields.openConfDir", + "settings.components.verge.advanced.fields.openCoreDir", + "settings.components.verge.advanced.fields.openLogsDir", + "settings.components.verge.advanced.fields.checkUpdates", + "settings.components.verge.advanced.fields.openDevTools", + "settings.components.verge.advanced.fields.liteModeSettings", + "settings.components.verge.advanced.fields.exit", + "settings.components.verge.advanced.fields.exportDiagnostics", + "settings.components.verge.advanced.fields.vergeVersion", + "settings.components.verge.theme.title", + "settings.components.verge.theme.fields.primaryColor", + "settings.components.verge.theme.fields.secondaryColor", + "settings.components.verge.theme.fields.primaryText", + "settings.components.verge.theme.fields.secondaryText", + "settings.components.verge.theme.fields.infoColor", + "settings.components.verge.theme.fields.warningColor", + "settings.components.verge.theme.fields.errorColor", + "settings.components.verge.theme.fields.successColor", + "settings.components.verge.theme.fields.fontFamily", + "settings.components.verge.theme.fields.cssInjection", + "settings.components.verge.theme.actions.editCss", + "settings.components.verge.theme.dialogs.editCssTitle", + "settings.components.verge.layout.title", + "settings.components.verge.layout.fields.preferSystemTitlebar", + "settings.components.verge.layout.fields.trafficGraph", + "settings.components.verge.layout.fields.memoryUsage", + "settings.components.verge.layout.fields.proxyGroupIcon", + "settings.components.verge.layout.fields.hoverNavigator", + "settings.components.verge.layout.fields.hoverNavigatorDelay", + "settings.components.verge.layout.fields.navIcon", + "settings.components.verge.layout.fields.trayIcon", + "settings.components.verge.layout.fields.showProxyGroupsInline", + "settings.components.verge.layout.fields.commonTrayIcon", + "settings.components.verge.layout.fields.systemProxyTrayIcon", + "settings.components.verge.layout.fields.tunTrayIcon", + "settings.components.verge.layout.fields.enableTrayIcon", + "settings.components.verge.layout.fields.enableTraySpeed", + "settings.components.verge.layout.tooltips.hoverNavigator", + "settings.components.verge.layout.tooltips.hoverNavigatorDelay", + "settings.components.verge.layout.options.icon.monochrome", + "settings.components.verge.layout.options.icon.colorful", + "settings.components.verge.layout.options.icon.disable", + "settings.modals.clashPort.title", + "settings.modals.clashPort.fields.mixed", + "settings.modals.clashPort.fields.socks", + "settings.modals.clashPort.fields.http", + "settings.modals.clashPort.fields.redir", + "settings.modals.clashPort.fields.tproxy", + "settings.modals.clashPort.actions.random", + "settings.modals.clashPort.messages.saved", + "settings.modals.clashPort.messages.saveFailed", + "settings.modals.clashCore.variants.release", + "settings.modals.clashCore.variants.alpha", + "settings.modals.liteMode.title", + "settings.modals.liteMode.actions.enterNow", + "settings.modals.liteMode.toggles.autoEnter", + "settings.modals.liteMode.tooltips.autoEnter", + "settings.modals.liteMode.fields.delay", + "settings.modals.liteMode.messages.autoEnterHint", + "settings.modals.backup.title", + "settings.modals.backup.tabs.local", + "settings.modals.backup.tabs.webdav", + "settings.modals.backup.actions.selectTarget", + "settings.modals.backup.actions.backup", + "settings.modals.backup.actions.export", + "settings.modals.backup.actions.exportBackup", + "settings.modals.backup.actions.deleteBackup", + "settings.modals.backup.actions.restore", + "settings.modals.backup.actions.restoreBackup", + "settings.modals.backup.fields.webdavUrl", + "settings.modals.backup.fields.username", + "settings.modals.backup.fields.info", + "settings.modals.backup.messages.webdavUrlRequired", + "settings.modals.backup.messages.invalidWebdavUrl", + "settings.modals.backup.messages.usernameRequired", + "settings.modals.backup.messages.passwordRequired", + "settings.modals.backup.messages.webdavConfigSaved", + "settings.modals.backup.messages.webdavConfigSaveFailed", + "settings.modals.backup.messages.backupCreated", + "settings.modals.backup.messages.backupFailed", + "settings.modals.backup.messages.localBackupCreated", + "settings.modals.backup.messages.localBackupFailed", + "settings.modals.backup.messages.restoreSuccess", + "settings.modals.backup.messages.localBackupExported", + "settings.modals.backup.messages.localBackupExportFailed", + "settings.modals.backup.messages.confirmDelete", + "settings.modals.backup.messages.confirmRestore", + "settings.modals.backup.table.filename", + "settings.modals.backup.table.backupTime", + "settings.modals.backup.table.actions", + "settings.modals.backup.table.noBackups", + "settings.modals.backup.table.rowsPerPage", + "settings.modals.misc.title", + "settings.modals.misc.fields.appLogLevel", + "settings.modals.misc.fields.appLogMaxSize", + "settings.modals.misc.fields.appLogMaxCount", + "settings.modals.misc.fields.autoCloseConnections", + "settings.modals.misc.fields.autoCheckUpdate", + "settings.modals.misc.fields.enableBuiltinEnhanced", + "settings.modals.misc.fields.proxyLayoutColumns", + "settings.modals.misc.fields.autoLogClean", + "settings.modals.misc.fields.autoDelayDetection", + "settings.modals.misc.fields.defaultLatencyTest", + "settings.modals.misc.fields.defaultLatencyTimeout", + "settings.modals.misc.tooltips.autoCloseConnections", + "settings.modals.misc.tooltips.enableBuiltinEnhanced", + "settings.modals.misc.tooltips.autoDelayDetection", + "settings.modals.misc.tooltips.defaultLatencyTest", + "settings.modals.misc.options.proxyLayoutColumns.auto", + "settings.modals.misc.options.autoLogClean.never", + "settings.modals.misc.options.autoLogClean.retainDays", + "settings.modals.update.title", + "settings.modals.update.actions.goToRelease", + "settings.modals.update.actions.update", + "settings.modals.update.messages.portableError", + "settings.modals.update.messages.breakChangeError", + "settings.modals.sysproxy.title", + "settings.modals.sysproxy.fieldsets.currentStatus", + "settings.modals.sysproxy.fields.enableStatus", + "settings.modals.sysproxy.fields.serverAddr", + "settings.modals.sysproxy.fields.pacUrl", + "settings.modals.sysproxy.fields.proxyHost", + "settings.modals.sysproxy.fields.usePacMode", + "settings.modals.sysproxy.fields.proxyGuard", + "settings.modals.sysproxy.fields.guardDuration", + "settings.modals.sysproxy.fields.alwaysUseDefaultBypass", + "settings.modals.sysproxy.fields.proxyBypass", + "settings.modals.sysproxy.fields.bypass", + "settings.modals.sysproxy.fields.pacScriptContent", + "settings.modals.sysproxy.tooltips.proxyGuard", + "settings.modals.sysproxy.messages.durationTooShort", + "settings.modals.sysproxy.messages.invalidBypass", + "settings.modals.sysproxy.messages.invalidProxyHost", + "settings.modals.sysproxy.actions.editPac", + "settings.modals.tun.title", + "settings.modals.tun.fields.stack", + "settings.modals.tun.fields.device", + "settings.modals.tun.fields.autoRoute", + "settings.modals.tun.fields.strictRoute", + "settings.modals.tun.fields.autoDetectInterface", + "settings.modals.tun.fields.dnsHijack", + "settings.modals.tun.fields.mtu", + "settings.modals.tun.tooltips.dnsHijack", + "settings.modals.tun.messages.applied", + "settings.modals.dns.dialog.title", + "settings.modals.dns.dialog.warning", + "settings.modals.dns.sections.general", + "settings.modals.dns.sections.fallbackFilter", + "settings.modals.dns.sections.hosts", + "settings.modals.dns.fields.enable", + "settings.modals.dns.fields.listen", + "settings.modals.dns.fields.enhancedMode", + "settings.modals.dns.fields.fakeIpRange", + "settings.modals.dns.fields.fakeIpFilterMode", + "settings.modals.dns.fields.ipv6.label", + "settings.modals.dns.fields.ipv6.description", + "settings.modals.dns.fields.preferH3.label", + "settings.modals.dns.fields.preferH3.description", + "settings.modals.dns.fields.respectRules.label", + "settings.modals.dns.fields.respectRules.description", + "settings.modals.dns.fields.useHosts.label", + "settings.modals.dns.fields.useHosts.description", + "settings.modals.dns.fields.useSystemHosts.label", + "settings.modals.dns.fields.useSystemHosts.description", + "settings.modals.dns.fields.directPolicy.label", + "settings.modals.dns.fields.directPolicy.description", + "settings.modals.dns.fields.defaultNameserver.label", + "settings.modals.dns.fields.defaultNameserver.description", + "settings.modals.dns.fields.nameserver.label", + "settings.modals.dns.fields.nameserver.description", + "settings.modals.dns.fields.fallback.label", + "settings.modals.dns.fields.fallback.description", + "settings.modals.dns.fields.proxy.label", + "settings.modals.dns.fields.proxy.description", + "settings.modals.dns.fields.directNameserver.label", + "settings.modals.dns.fields.directNameserver.description", + "settings.modals.dns.fields.fakeIpFilter.label", + "settings.modals.dns.fields.fakeIpFilter.description", + "settings.modals.dns.fields.nameserverPolicy.label", + "settings.modals.dns.fields.nameserverPolicy.description", + "settings.modals.dns.fields.geoipFiltering.label", + "settings.modals.dns.fields.geoipFiltering.description", + "settings.modals.dns.fields.geoipCode", + "settings.modals.dns.fields.fallbackIpCidr.label", + "settings.modals.dns.fields.fallbackIpCidr.description", + "settings.modals.dns.fields.fallbackDomain.label", + "settings.modals.dns.fields.fallbackDomain.description", + "settings.modals.dns.fields.hosts.label", + "settings.modals.dns.fields.hosts.description", + "settings.modals.dns.messages.saved", + "settings.modals.dns.messages.configError", + "settings.modals.dns.errors.invalid", + "settings.modals.webUI.actions.openUrl", + "settings.modals.webUI.title", + "settings.modals.webUI.messages.supportedPlaceholders", + "settings.modals.webUI.messages.placeholderInstruction", + "settings.modals.hotkey.toggles.enableGlobal", + "settings.modals.hotkey.title", + "settings.modals.hotkey.functions.rule", + "settings.modals.hotkey.functions.global", + "settings.modals.hotkey.functions.openOrCloseDashboard", + "settings.modals.hotkey.functions.toggleSystemProxy", + "settings.modals.hotkey.functions.toggleTunMode", + "settings.modals.hotkey.functions.entryLightweightMode", + "settings.modals.hotkey.functions.direct", + "settings.modals.password.prompts.enterRoot", + "settings.modals.networkInterface.title", + "settings.modals.networkInterface.fields.ipAddress", + "settings.modals.networkInterface.fields.macAddress", + "settings.feedback.notifications.clash.restartSuccess", + "settings.feedback.notifications.clash.versionUpdated", + "settings.feedback.notifications.clash.changeSuccess", + "settings.feedback.notifications.clash.changeFailed", + "settings.feedback.notifications.clash.geoDataUpdated", + "settings.feedback.notifications.clashService.installSuccess", + "settings.feedback.notifications.clashService.uninstallSuccess", + "settings.feedback.notifications.updater.withClashProxySuccess", + "settings.feedback.notifications.updater.withClashProxyFailed", + "settings.statuses.clash.stopping", + "settings.statuses.clash.restarting", + "settings.statuses.clashService.installing", + "settings.statuses.clashService.uninstalling", + "shared.actions.cancel", + "shared.actions.close", + "shared.actions.confirm", + "shared.actions.save", + "shared.actions.delete", + "shared.actions.edit", + "shared.actions.new", + "shared.actions.enable", + "shared.actions.upgrade", + "shared.actions.restart", + "shared.actions.resetToDefault", + "shared.actions.refresh", + "shared.actions.retry", + "shared.actions.refreshPage", + "shared.actions.showDetails", + "shared.actions.hideDetails", + "shared.actions.listView", + "shared.actions.tableView", + "shared.actions.pause", + "shared.actions.resume", + "shared.actions.closeAll", + "shared.actions.clear", + "shared.labels.updateAt", + "shared.labels.timeout", + "shared.labels.icon", + "shared.labels.name", + "shared.labels.readOnly", + "shared.labels.expireTime", + "shared.labels.updateTime", + "shared.labels.usedTotal", + "shared.labels.from", + "shared.labels.password", + "shared.labels.retryAttempts", + "shared.labels.downloaded", + "shared.labels.uploaded", + "shared.statuses.enabled", + "shared.statuses.disabled", + "shared.statuses.saving", + "shared.statuses.empty", + "shared.units.milliseconds", + "shared.units.seconds", + "shared.units.minutes", + "shared.units.kilobytes", + "shared.units.files", + "shared.placeholders.filter", + "shared.placeholders.matchCase", + "shared.placeholders.matchWholeWord", + "shared.placeholders.useRegex", + "shared.validation.invalidRegex", + "shared.window.maximize", + "shared.window.minimize", + "shared.editorModes.visualization", + "shared.editorModes.advanced", + "shared.feedback.errors.trafficStats", + "shared.feedback.errors.trafficStatsDescription", + "shared.feedback.notices.raw", + "shared.feedback.notices.prefixedRaw", + "shared.feedback.notifications.importSuccess", + "shared.feedback.notifications.importSubscriptionSuccess", + "shared.feedback.notifications.importWithClashProxy", + "shared.feedback.notifications.saved", + "shared.feedback.notifications.common.copySuccess", + "shared.feedback.notifications.common.saveSuccess", + "shared.feedback.notifications.common.saveFailed", + "shared.feedback.validation.config.failed", + "shared.feedback.validation.config.bootFailed", + "shared.feedback.validation.config.coreChangeFailed", + "shared.feedback.validation.config.processTerminated", + "shared.feedback.validation.script.syntaxError", + "shared.feedback.validation.script.missingMain", + "shared.feedback.validation.script.fileNotFound", + "shared.feedback.validation.script.fileError", + "shared.feedback.validation.yaml.syntaxError", + "shared.feedback.validation.yaml.readError", + "shared.feedback.validation.yaml.mappingError", + "shared.feedback.validation.yaml.keyError", + "shared.feedback.validation.yaml.generalError", + "shared.feedback.validation.merge.syntaxError", + "shared.feedback.validation.merge.mappingError", + "shared.feedback.validation.merge.keyError", + "shared.feedback.validation.merge.generalError", + "shared.filters.logLevels.all", + "shared.filters.logLevels.debug", + "shared.filters.logLevels.info", + "shared.filters.logLevels.warn", + "shared.filters.logLevels.error", + "tests.page.actions.testAll", + "tests.page.title", + "tests.components.item.actions.test", + "tests.modals.test.title.create", + "tests.modals.test.title.edit", + "tests.modals.test.fields.url", + "tests.statuses.test.pending", + "tests.statuses.test.yes", + "tests.statuses.test.no", + "tests.statuses.test.failed", + "tests.statuses.test.completed", + "tests.statuses.test.disallowedIsp", + "tests.statuses.test.originalsOnly", + "tests.statuses.test.noDisney", + "tests.statuses.test.unsupportedRegion", + "tests.statuses.test.failedNetwork", + "tests.unlock.page.actions.testing", + "tests.unlock.page.empty", + "tests.unlock.page.messages.detectionTimeout", + "tests.unlock.page.title", +] as const; + +export type TranslationKey = (typeof translationKeys)[number]; diff --git a/src/types/generated/i18n-resources.ts b/src/types/generated/i18n-resources.ts new file mode 100644 index 00000000..2e76791e --- /dev/null +++ b/src/types/generated/i18n-resources.ts @@ -0,0 +1,1304 @@ +// This file is auto-generated by scripts/generate-i18n-keys.mjs +// Do not edit this file manually. + +export interface TranslationResources { + translation: { + connections: { + components: { + actions: { + closeConnection: string; + }; + fields: { + chains: string; + destination: string; + destinationPort: string; + dlSpeed: string; + host: string; + process: string; + rule: string; + source: string; + time: string; + type: string; + ulSpeed: string; + }; + order: { + default: string; + downloadSpeed: string; + uploadSpeed: string; + }; + }; + page: { + title: string; + }; + }; + home: { + components: { + clashInfo: { + fields: { + coreVersion: string; + mixedPort: string; + rulesCount: string; + systemProxyAddress: string; + uptime: string; + }; + title: string; + }; + clashMode: { + descriptions: { + direct: string; + global: string; + rule: string; + }; + errors: { + communication: string; + }; + labels: { + direct: string; + global: string; + rule: string; + }; + }; + currentProxy: { + actions: { + refreshDelay: string; + }; + labels: { + directMode: string; + globalMode: string; + group: string; + noActiveNode: string; + proxy: string; + }; + title: string; + }; + ipInfo: { + errors: { + load: string; + }; + labels: { + asn: string; + autoRefresh: string; + ip: string; + isp: string; + location: string; + org: string; + timezone: string; + unknown: string; + }; + title: string; + }; + proxyTun: { + status: { + systemProxyDisabled: string; + systemProxyEnabled: string; + tunModeDisabled: string; + tunModeEnabled: string; + tunModeServiceRequired: string; + }; + tooltips: { + systemProxy: string; + tunMode: string; + }; + }; + systemInfo: { + actions: { + settings: string; + }; + badges: { + adminMode: string; + adminServiceMode: string; + serviceMode: string; + sidecarMode: string; + }; + fields: { + autoLaunch: string; + lastCheckUpdate: string; + osInfo: string; + runningMode: string; + vergeVersion: string; + }; + title: string; + tooltips: { + autoLaunchAdmin: string; + }; + }; + tests: { + title: string; + }; + traffic: { + legends: { + download: string; + upload: string; + }; + metrics: { + activeConnections: string; + downloadSpeed: string; + memoryUsage: string; + uploadSpeed: string; + }; + patterns: { + minutes: string; + }; + }; + }; + page: { + cards: { + networkSettings: string; + proxyMode: string; + trafficStats: string; + }; + settings: { + cards: { + clashInfo: string; + currentProxy: string; + ip: string; + network: string; + profile: string; + proxyMode: string; + systemInfo: string; + tests: string; + traffic: string; + }; + title: string; + }; + title: string; + tooltips: { + lightweightMode: string; + manual: string; + settings: string; + }; + }; + }; + layout: { + components: { + navigation: { + menu: { + lock: string; + reorderMode: string; + unlock: string; + }; + tabs: { + connections: string; + home: string; + logs: string; + profiles: string; + proxies: string; + rules: string; + settings: string; + unlock: string; + }; + }; + }; + }; + logs: { + page: { + title: string; + }; + }; + profiles: { + components: { + card: { + labels: { + clickToImport: string; + }; + }; + fileInput: { + chooseFile: string; + }; + menu: { + editFile: string; + editGroups: string; + editInfo: string; + editProxies: string; + editRules: string; + extendConfig: string; + extendScript: string; + home: string; + openFile: string; + select: string; + update: string; + updateViaProxy: string; + }; + more: { + chips: { + merge: string; + script: string; + }; + global: { + merge: string; + script: string; + }; + }; + profileItem: { + status: { + autoUpdateDisabled: string; + lastUpdateFailed: string; + nextUp: string; + noSchedule: string; + unknown: string; + }; + tooltips: { + showLast: string; + showNext: string; + }; + }; + }; + modals: { + confirmDelete: { + message: string; + title: string; + }; + editor: { + actions: { + format: string; + }; + messages: { + readOnly: string; + }; + }; + groupsEditor: { + actions: { + append: string; + prepend: string; + }; + errors: { + nameExists: string; + nameRequired: string; + }; + fields: { + excludeFilter: string; + excludeType: string; + expectedStatus: string; + filter: string; + healthCheckUrl: string; + icon: string; + includeAll: string; + includeAllProviders: string; + includeAllProxies: string; + interfaceName: string; + interval: string; + maxFailedTimes: string; + name: string; + provider: string; + proxies: string; + routingMark: string; + type: string; + }; + title: string; + toggles: { + disableUdp: string; + hidden: string; + lazy: string; + }; + }; + logViewer: { + title: string; + }; + profileForm: { + feedback: { + notifications: { + creationRetry: string; + creationSuccess: string; + }; + }; + fields: { + acceptInvalidCerts: string; + allowAutoUpdate: string; + description: string; + httpTimeout: string; + subscriptionUrl: string; + type: string; + updateInterval: string; + useClashProxy: string; + useSystemProxy: string; + }; + title: { + create: string; + edit: string; + }; + }; + proxiesEditor: { + actions: { + append: string; + prepend: string; + }; + placeholders: { + multiUri: string; + }; + title: string; + }; + }; + page: { + actions: { + import: string; + reactivate: string; + updateAll: string; + viewRuntimeConfig: string; + }; + batch: { + actions: { + delete: string; + deselectAll: string; + done: string; + selectAll: string; + }; + summary: { + items: string; + selected: string; + }; + title: string; + }; + feedback: { + errors: { + invalidUrl: string; + onlyYaml: string; + }; + notices: { + emergencyRefreshFailed: string; + forceRefreshCompleted: string; + }; + notifications: { + batchDeleted: string; + importFail: string; + importNeedsRefresh: string; + importRetry: string; + importSuccess: string; + profileReactivated: string; + profileSwitched: string; + switchInterrupted: string; + }; + }; + importForm: { + actions: { + paste: string; + }; + placeholder: string; + }; + title: string; + }; + }; + proxies: { + components: { + enums: { + policies: { + DIRECT: string; + PASS: string; + REJECT: string; + "REJECT-DROP": string; + }; + strategies: { + fallback: string; + "load-balance": string; + relay: string; + select: string; + "url-test": string; + }; + }; + }; + feedback: { + notifications: { + provider: { + allUpdated: string; + genericError: string; + none: string; + updateFailed: string; + updateSuccess: string; + }; + }; + }; + page: { + actions: { + clearChainConfig: string; + connect: string; + connecting: string; + disconnect: string; + toggleChain: string; + }; + chain: { + connectFailed: string; + disconnectFailed: string; + duplicateNode: string; + empty: string; + header: string; + instruction: string; + minimumNodes: string; + minimumNodesHint: string; + }; + labels: { + delayCheckReset: string; + proxyCount: string; + }; + messages: { + directMode: string; + }; + modes: { + direct: string; + global: string; + rule: string; + }; + placeholders: { + delayCheckUrl: string; + }; + provider: { + actions: { + update: string; + updateAll: string; + }; + title: string; + }; + rules: { + select: string; + title: string; + }; + title: { + chainMode: string; + default: string; + }; + tooltips: { + delayCheck: string; + delayCheckUrl: string; + filter: string; + locate: string; + showBasic: string; + showDetail: string; + sortDefault: string; + sortDelay: string; + sortName: string; + }; + }; + }; + rules: { + feedback: { + notifications: { + provider: { + allUpdated: string; + genericError: string; + none: string; + updateFailed: string; + updateSuccess: string; + }; + }; + }; + modals: { + editor: { + form: { + actions: { + appendRule: string; + prependRule: string; + }; + labels: { + content: string; + proxyPolicy: string; + type: string; + }; + toggles: { + noResolve: string; + }; + validation: { + conditionRequired: string; + invalidRule: string; + }; + }; + ruleTypes: { + AND: string; + DOMAIN: string; + "DOMAIN-KEYWORD": string; + "DOMAIN-REGEX": string; + "DOMAIN-SUFFIX": string; + DSCP: string; + "DST-PORT": string; + GEOIP: string; + GEOSITE: string; + "IN-NAME": string; + "IN-PORT": string; + "IN-TYPE": string; + "IN-USER": string; + "IP-ASN": string; + "IP-CIDR": string; + "IP-CIDR6": string; + "IP-SUFFIX": string; + MATCH: string; + NETWORK: string; + NOT: string; + OR: string; + "PROCESS-NAME": string; + "PROCESS-NAME-REGEX": string; + "PROCESS-PATH": string; + "PROCESS-PATH-REGEX": string; + "RULE-SET": string; + "SRC-GEOIP": string; + "SRC-IP-ASN": string; + "SRC-IP-CIDR": string; + "SRC-IP-SUFFIX": string; + "SRC-PORT": string; + "SUB-RULE": string; + UID: string; + }; + title: string; + }; + }; + page: { + provider: { + actions: { + update: string; + updateAll: string; + }; + dialogTitle: string; + trigger: string; + }; + title: string; + }; + }; + settings: { + components: { + verge: { + advanced: { + actions: { + copyVersion: string; + }; + fields: { + backupSetting: string; + checkUpdates: string; + exit: string; + exportDiagnostics: string; + liteModeSettings: string; + openConfDir: string; + openCoreDir: string; + openDevTools: string; + openLogsDir: string; + runtimeConfig: string; + vergeVersion: string; + }; + notifications: { + latestVersion: string; + versionCopied: string; + }; + title: string; + tooltips: { + backupInfo: string; + liteMode: string; + openConfDir: string; + }; + }; + basic: { + actions: { + browse: string; + }; + fields: { + copyEnvType: string; + hotkeySetting: string; + language: string; + layoutSetting: string; + misc: string; + startPage: string; + startupScript: string; + themeMode: string; + themeSetting: string; + trayClickEvent: string; + }; + title: string; + trayOptions: { + disable: string; + showMainWindow: string; + showTrayMenu: string; + }; + }; + layout: { + fields: { + commonTrayIcon: string; + enableTrayIcon: string; + enableTraySpeed: string; + hoverNavigator: string; + hoverNavigatorDelay: string; + memoryUsage: string; + navIcon: string; + preferSystemTitlebar: string; + proxyGroupIcon: string; + showProxyGroupsInline: string; + systemProxyTrayIcon: string; + trafficGraph: string; + trayIcon: string; + tunTrayIcon: string; + }; + options: { + icon: { + colorful: string; + disable: string; + monochrome: string; + }; + }; + title: string; + tooltips: { + hoverNavigator: string; + hoverNavigatorDelay: string; + }; + }; + theme: { + actions: { + editCss: string; + }; + dialogs: { + editCssTitle: string; + }; + fields: { + cssInjection: string; + errorColor: string; + fontFamily: string; + infoColor: string; + primaryColor: string; + primaryText: string; + secondaryColor: string; + secondaryText: string; + successColor: string; + warningColor: string; + }; + title: string; + }; + }; + }; + feedback: { + notifications: { + clash: { + changeFailed: string; + changeSuccess: string; + geoDataUpdated: string; + restartSuccess: string; + versionUpdated: string; + }; + clashService: { + installSuccess: string; + uninstallSuccess: string; + }; + updater: { + withClashProxyFailed: string; + withClashProxySuccess: string; + }; + }; + }; + modals: { + backup: { + actions: { + backup: string; + deleteBackup: string; + export: string; + exportBackup: string; + restore: string; + restoreBackup: string; + selectTarget: string; + }; + fields: { + info: string; + username: string; + webdavUrl: string; + }; + messages: { + backupCreated: string; + backupFailed: string; + confirmDelete: string; + confirmRestore: string; + invalidWebdavUrl: string; + localBackupCreated: string; + localBackupExported: string; + localBackupExportFailed: string; + localBackupFailed: string; + passwordRequired: string; + restoreSuccess: string; + usernameRequired: string; + webdavConfigSaved: string; + webdavConfigSaveFailed: string; + webdavUrlRequired: string; + }; + table: { + actions: string; + backupTime: string; + filename: string; + noBackups: string; + rowsPerPage: string; + }; + tabs: { + local: string; + webdav: string; + }; + title: string; + }; + clashCore: { + variants: { + alpha: string; + release: string; + }; + }; + clashPort: { + actions: { + random: string; + }; + fields: { + http: string; + mixed: string; + redir: string; + socks: string; + tproxy: string; + }; + messages: { + saved: string; + saveFailed: string; + }; + title: string; + }; + dns: { + dialog: { + title: string; + warning: string; + }; + errors: { + invalid: string; + }; + fields: { + defaultNameserver: { + description: string; + label: string; + }; + directNameserver: { + description: string; + label: string; + }; + directPolicy: { + description: string; + label: string; + }; + enable: string; + enhancedMode: string; + fakeIpFilter: { + description: string; + label: string; + }; + fakeIpFilterMode: string; + fakeIpRange: string; + fallback: { + description: string; + label: string; + }; + fallbackDomain: { + description: string; + label: string; + }; + fallbackIpCidr: { + description: string; + label: string; + }; + geoipCode: string; + geoipFiltering: { + description: string; + label: string; + }; + hosts: { + description: string; + label: string; + }; + ipv6: { + description: string; + label: string; + }; + listen: string; + nameserver: { + description: string; + label: string; + }; + nameserverPolicy: { + description: string; + label: string; + }; + preferH3: { + description: string; + label: string; + }; + proxy: { + description: string; + label: string; + }; + respectRules: { + description: string; + label: string; + }; + useHosts: { + description: string; + label: string; + }; + useSystemHosts: { + description: string; + label: string; + }; + }; + messages: { + configError: string; + saved: string; + }; + sections: { + fallbackFilter: string; + general: string; + hosts: string; + }; + }; + hotkey: { + functions: { + direct: string; + entryLightweightMode: string; + global: string; + openOrCloseDashboard: string; + rule: string; + toggleSystemProxy: string; + toggleTunMode: string; + }; + title: string; + toggles: { + enableGlobal: string; + }; + }; + liteMode: { + actions: { + enterNow: string; + }; + fields: { + delay: string; + }; + messages: { + autoEnterHint: string; + }; + title: string; + toggles: { + autoEnter: string; + }; + tooltips: { + autoEnter: string; + }; + }; + misc: { + fields: { + appLogLevel: string; + appLogMaxCount: string; + appLogMaxSize: string; + autoCheckUpdate: string; + autoCloseConnections: string; + autoDelayDetection: string; + autoLogClean: string; + defaultLatencyTest: string; + defaultLatencyTimeout: string; + enableBuiltinEnhanced: string; + proxyLayoutColumns: string; + }; + options: { + autoLogClean: { + never: string; + retainDays: string; + }; + proxyLayoutColumns: { + auto: string; + }; + }; + title: string; + tooltips: { + autoCloseConnections: string; + autoDelayDetection: string; + defaultLatencyTest: string; + enableBuiltinEnhanced: string; + }; + }; + networkInterface: { + fields: { + ipAddress: string; + macAddress: string; + }; + title: string; + }; + password: { + prompts: { + enterRoot: string; + }; + }; + sysproxy: { + actions: { + editPac: string; + }; + fields: { + alwaysUseDefaultBypass: string; + bypass: string; + enableStatus: string; + guardDuration: string; + pacScriptContent: string; + pacUrl: string; + proxyBypass: string; + proxyGuard: string; + proxyHost: string; + serverAddr: string; + usePacMode: string; + }; + fieldsets: { + currentStatus: string; + }; + messages: { + durationTooShort: string; + invalidBypass: string; + invalidProxyHost: string; + }; + title: string; + tooltips: { + proxyGuard: string; + }; + }; + tun: { + fields: { + autoDetectInterface: string; + autoRoute: string; + device: string; + dnsHijack: string; + mtu: string; + stack: string; + strictRoute: string; + }; + messages: { + applied: string; + }; + title: string; + tooltips: { + dnsHijack: string; + }; + }; + update: { + actions: { + goToRelease: string; + update: string; + }; + messages: { + breakChangeError: string; + portableError: string; + }; + title: string; + }; + webUI: { + actions: { + openUrl: string; + }; + messages: { + placeholderInstruction: string; + supportedPlaceholders: string; + }; + title: string; + }; + }; + page: { + actions: { + github: string; + manual: string; + telegram: string; + }; + title: string; + }; + sections: { + appearance: { + dark: string; + light: string; + system: string; + }; + clash: { + form: { + fields: { + allowLan: string; + clashCore: string; + dnsOverwrite: string; + external: string; + ipv6: string; + logLevel: string; + openUwpTool: string; + portConfig: string; + unifiedDelay: string; + updateGeoData: string; + webUI: string; + }; + options: { + logLevel: { + debug: string; + error: string; + info: string; + silent: string; + warning: string; + }; + }; + tooltips: { + logLevel: string; + networkInterface: string; + openUwpTool: string; + unifiedDelay: string; + }; + }; + title: string; + }; + externalController: { + fields: { + address: string; + enable: string; + secret: string; + }; + messages: { + addressRequired: string; + controllerCopied: string; + copyFailed: string; + secretCopied: string; + secretRequired: string; + }; + placeholders: { + address: string; + secret: string; + }; + title: string; + tooltips: { + copy: string; + }; + }; + externalCors: { + actions: { + add: string; + }; + fields: { + allowedOrigins: string; + allowPrivateNetwork: string; + }; + messages: { + alwaysIncluded: string; + }; + placeholders: { + origin: string; + }; + title: string; + tooltips: { + open: string; + }; + }; + proxyControl: { + actions: { + installService: string; + uninstallService: string; + }; + fields: { + systemProxy: string; + tunMode: string; + }; + tooltips: { + systemProxy: string; + tunMode: string; + tunUnavailable: string; + }; + }; + system: { + fields: { + autoLaunch: string; + silentStart: string; + }; + notifications: { + tunMode: { + autoDisabled: string; + autoDisableFailed: string; + }; + }; + title: string; + toggles: { + systemProxy: string; + tunMode: string; + }; + tooltips: { + autoLaunchAdmin: string; + silentStart: string; + }; + }; + }; + statuses: { + clash: { + restarting: string; + stopping: string; + }; + clashService: { + installing: string; + uninstalling: string; + }; + }; + }; + shared: { + actions: { + cancel: string; + clear: string; + close: string; + closeAll: string; + confirm: string; + delete: string; + edit: string; + enable: string; + hideDetails: string; + listView: string; + new: string; + pause: string; + refresh: string; + refreshPage: string; + resetToDefault: string; + restart: string; + resume: string; + retry: string; + save: string; + showDetails: string; + tableView: string; + upgrade: string; + }; + editorModes: { + advanced: string; + visualization: string; + }; + feedback: { + errors: { + trafficStats: string; + trafficStatsDescription: string; + }; + notices: { + prefixedRaw: string; + raw: string; + }; + notifications: { + common: { + copySuccess: string; + saveFailed: string; + saveSuccess: string; + }; + importSubscriptionSuccess: string; + importSuccess: string; + importWithClashProxy: string; + saved: string; + }; + validation: { + config: { + bootFailed: string; + coreChangeFailed: string; + failed: string; + processTerminated: string; + }; + merge: { + generalError: string; + keyError: string; + mappingError: string; + syntaxError: string; + }; + script: { + fileError: string; + fileNotFound: string; + missingMain: string; + syntaxError: string; + }; + yaml: { + generalError: string; + keyError: string; + mappingError: string; + readError: string; + syntaxError: string; + }; + }; + }; + filters: { + logLevels: { + all: string; + debug: string; + error: string; + info: string; + warn: string; + }; + }; + labels: { + downloaded: string; + expireTime: string; + from: string; + icon: string; + name: string; + password: string; + readOnly: string; + retryAttempts: string; + timeout: string; + updateAt: string; + updateTime: string; + uploaded: string; + usedTotal: string; + }; + placeholders: { + filter: string; + matchCase: string; + matchWholeWord: string; + useRegex: string; + }; + statuses: { + disabled: string; + empty: string; + enabled: string; + saving: string; + }; + units: { + files: string; + kilobytes: string; + milliseconds: string; + minutes: string; + seconds: string; + }; + validation: { + invalidRegex: string; + }; + window: { + maximize: string; + minimize: string; + }; + }; + tests: { + components: { + item: { + actions: { + test: string; + }; + }; + }; + modals: { + test: { + fields: { + url: string; + }; + title: { + create: string; + edit: string; + }; + }; + }; + page: { + actions: { + testAll: string; + }; + title: string; + }; + statuses: { + test: { + completed: string; + disallowedIsp: string; + failed: string; + failedNetwork: string; + no: string; + noDisney: string; + originalsOnly: string; + pending: string; + unsupportedRegion: string; + yes: string; + }; + }; + unlock: { + page: { + actions: { + testing: string; + }; + empty: string; + messages: { + detectionTimeout: string; + }; + title: string; + }; + }; + }; + }; +} diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts new file mode 100644 index 00000000..6c9d17ed --- /dev/null +++ b/src/types/i18next.d.ts @@ -0,0 +1,11 @@ +import "i18next"; + +import type { TranslationResources } from "./generated/i18n-resources"; + +declare module "i18next" { + interface CustomTypeOptions { + defaultNS: "translation"; + resources: TranslationResources; + enableSelector: "optimize"; + } +} diff --git a/src/types/react-i18next.d.ts b/src/types/react-i18next.d.ts new file mode 100644 index 00000000..23c9e4a7 --- /dev/null +++ b/src/types/react-i18next.d.ts @@ -0,0 +1,39 @@ +import "react-i18next"; + +import type { i18n, Namespace, TOptions, TFunction } from "i18next"; +import type { + UseTranslationOptions, + UseTranslationResponse, +} from "react-i18next"; + +import type { TranslationKey } from "./generated/i18n-keys"; + +type EnforcedTranslationKey = string extends Key + ? string + : Key extends TranslationKey + ? Key + : never; + +type BaseTFunction = UseTranslationResponse[0]; + +type TypedTFunction = BaseTFunction & + TFunction & + (( + key: EnforcedTranslationKey, + options?: TOptions | string, + ) => string) & + (( + key: readonly EnforcedTranslationKey[], + options?: TOptions | string, + ) => string); + +declare module "react-i18next" { + function useTranslation( + ns?: Namespace | Namespace[], + options?: UseTranslationOptions, + ): [t: TypedTFunction, i18n: i18n, ready: boolean] & { + t: TypedTFunction; + i18n: i18n; + ready: boolean; + }; +}