mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 00:52:18 +03:00
* fix(i18n): preserve remaining Vietnamese localization * chore(quality): rebaseline file-size cap for 9 dashboard components (i18n wiring) Restoring the Vietnamese localization on 9 dashboard components (useTranslations wiring + t()/tc() call-site swaps for previously hardcoded strings) grows each file by a small, irreducible amount. Bumps the frozen file-size-baseline.json caps to match, with a justification entry per the project's own ratchet policy. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(i18n): wire weekday localization + add missing qwen CLI description Two gaps left by this PR's own new contract tests, caught while reconciling the branch against the release tip: - CostOverviewTab.tsx added formatWeekdayLabel() but never called it; the Weekly Usage Pattern chart still showed raw English day abbreviations regardless of locale. Now maps weeklyPattern rows through it before handing them to WeeklyPatternCard. - cliTools.toolDescriptions was missing an entry for "qwen" (a baseUrlSupport:"full" tool) in both en.json and vi.json, failing the PR's own cli-catalog-display-contract.test.ts. Covered by the PR's existing tests/unit/dashboard-localization-contract.test.ts and tests/unit/cli-catalog-display-contract.test.ts (both now pass). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * i18n(vi): backfill the 4 proxySubscription keys #7299 added to en.json #7299 (proxy subscriptions) merged while this branch was rebasing, adding settings.proxySubscriptionsTab and settings.proxySubscription.error.{LOCAL_CORE_ENDPOINT_INVALID, NEEDS_CORE_NOT_CONFIGURED,NO_USABLE_NODES} to en.json. This PR's own i18n-vi-completeness contract asserts full en↔vi key parity, so the merge of the current release tip surfaced them as missing. Adds the Vietnamese translations, keeping the SS/VMess/Trojan/VLESS/SOCKS5 technical terms verbatim. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com>
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
|
|
import en from "../../src/i18n/messages/en.json" with { type: "json" };
|
|
import vi from "../../src/i18n/messages/vi.json" with { type: "json" };
|
|
|
|
type MessageEntry = {
|
|
key: string;
|
|
value: string;
|
|
};
|
|
|
|
function flattenMessages(value: unknown, segments: Array<string | number> = []): MessageEntry[] {
|
|
if (typeof value === "string") {
|
|
return [{ key: segments.map(String).join("."), value }];
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.flatMap((child, index) => flattenMessages(child, [...segments, index]));
|
|
}
|
|
if (value && typeof value === "object") {
|
|
return Object.entries(value).flatMap(([key, child]) =>
|
|
flattenMessages(child, [...segments, key])
|
|
);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function placeholderNames(value: string): string[] {
|
|
return [...value.matchAll(/\{\s*([A-Za-z][A-Za-z0-9_]*)\s*(?=[,}])/g)]
|
|
.map((match) => match[1])
|
|
.sort();
|
|
}
|
|
|
|
const englishMessages = flattenMessages(en);
|
|
const vietnameseMessages = flattenMessages(vi);
|
|
const vietnameseByKey = new Map(vietnameseMessages.map((entry) => [entry.key, entry.value]));
|
|
|
|
test("Vietnamese locale has complete key parity with English", () => {
|
|
assert.deepEqual(
|
|
vietnameseMessages.map((entry) => entry.key).sort(),
|
|
englishMessages.map((entry) => entry.key).sort()
|
|
);
|
|
});
|
|
|
|
test("Vietnamese locale has no internal missing markers or empty fallbacks", () => {
|
|
const invalid = vietnameseMessages.filter(
|
|
({ value }) => !value.trim() || /__(?:MISSING|TODO)__:?/i.test(value)
|
|
);
|
|
assert.deepEqual(invalid, []);
|
|
});
|
|
|
|
test("Vietnamese locale preserves every ICU placeholder name", () => {
|
|
const mismatches = englishMessages.flatMap(({ key, value }) => {
|
|
const translated = vietnameseByKey.get(key);
|
|
if (translated === undefined) return [{ key, reason: "missing" }];
|
|
const sourceNames = placeholderNames(value);
|
|
const targetNames = placeholderNames(translated);
|
|
return JSON.stringify(sourceNames) === JSON.stringify(targetNames)
|
|
? []
|
|
: [{ key, sourceNames, targetNames }];
|
|
});
|
|
assert.deepEqual(mismatches, []);
|
|
});
|
|
|
|
test("Vietnamese locale introduces no ICU parse regression", () => {
|
|
const regressions = englishMessages.flatMap(({ key, value }) => {
|
|
try {
|
|
parse(value, { captureLocation: false, shouldParseSkeletons: true });
|
|
} catch {
|
|
return [];
|
|
}
|
|
|
|
const translated = vietnameseByKey.get(key);
|
|
if (translated === undefined) return [{ key, reason: "missing" }];
|
|
try {
|
|
parse(translated, { captureLocation: false, shouldParseSkeletons: true });
|
|
return [];
|
|
} catch (error) {
|
|
return [{ key, reason: error instanceof Error ? error.message : String(error) }];
|
|
}
|
|
});
|
|
assert.deepEqual(regressions, []);
|
|
});
|
|
|
|
test("no-auth provider controls keep locale translators unambiguous", () => {
|
|
const source = readFileSync(
|
|
new URL(
|
|
"../../src/app/(dashboard)/dashboard/providers/[id]/components/NoAuthProviderControls.tsx",
|
|
import.meta.url
|
|
),
|
|
"utf8"
|
|
);
|
|
|
|
assert.equal(source.match(/import \{ useTranslations \} from "next-intl";/g)?.length, 1);
|
|
assert.match(source, /const noAuthT = useTranslations\("noAuthProvider"\);/);
|
|
assert.match(source, /const t = useTranslations\("providers"\);/);
|
|
});
|