feat(i18n): add type-checked translation keys

This commit is contained in:
Slinetrac
2025-11-06 13:40:16 +08:00
Unverified
parent 9fb46fd3e0
commit c715327739
12 changed files with 2215 additions and 9 deletions

View File

@@ -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",

18
pnpm-lock.yaml generated
View File

@@ -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: {}

View File

@@ -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;
});

View File

@@ -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,
});

View File

@@ -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;
}

View File

@@ -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<ClashMode, { label: string; description: string }> = {
const MODE_META: Record<
ClashMode,
{ label: TranslationKey; description: TranslationKey }
> = {
rule: {
label: "home.components.clashMode.labels.rule",
description: "home.components.clashMode.descriptions.rule",

View File

@@ -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<string, string> = {
const PROXY_STRATEGY_LABEL_KEYS: Record<string, TranslationKey> = {
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<string, string> = {
relay: "proxies.components.enums.strategies.relay",
};
const PROXY_POLICY_LABEL_KEYS: Record<string, string> =
const PROXY_POLICY_LABEL_KEYS: Record<string, TranslationKey> =
builtinProxyPolicies.reduce(
(acc, policy) => {
acc[policy] = `proxies.components.enums.policies.${policy}`;
acc[policy] =
`proxies.components.enums.policies.${policy}` as TranslationKey;
return acc;
},
{} as Record<string, string>,
{} as Record<string, TranslationKey>,
);
const normalizeDeleteSeq = (input?: unknown): string[] => {

View File

@@ -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<string, TranslationKey> = {
home: "profiles.components.menu.home",
select: "profiles.components.menu.select",
editInfo: "profiles.components.menu.editInfo",

View File

@@ -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];

File diff suppressed because it is too large Load Diff

11
src/types/i18next.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import "i18next";
import type { TranslationResources } from "./generated/i18n-resources";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "translation";
resources: TranslationResources;
enableSelector: "optimize";
}
}

39
src/types/react-i18next.d.ts vendored Normal file
View File

@@ -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<Key extends string> = string extends Key
? string
: Key extends TranslationKey
? Key
: never;
type BaseTFunction = UseTranslationResponse<Namespace, undefined>[0];
type TypedTFunction = BaseTFunction &
TFunction &
(<Key extends string>(
key: EnforcedTranslationKey<Key>,
options?: TOptions | string,
) => string) &
(<Key extends string>(
key: readonly EnforcedTranslationKey<Key>[],
options?: TOptions | string,
) => string);
declare module "react-i18next" {
function useTranslation<KPrefix extends string = undefined>(
ns?: Namespace | Namespace[],
options?: UseTranslationOptions<KPrefix>,
): [t: TypedTFunction, i18n: i18n, ready: boolean] & {
t: TypedTFunction;
i18n: i18n;
ready: boolean;
};
}