fix(i18n): complete Vietnamese dashboard localization and runtime fixes (#7493)

* fix(i18n): complete Vietnamese locale

* fix(i18n): localize remaining Vietnamese dashboard surfaces

* fix(i18n): localize CLI catalog and shared navigation

* fix(i18n): repair dashboard routes and shared provider UI

* chore(lint): prune resolved CLI guide suppressions

* fix(i18n): finish Vietnamese dashboard runtime copy

* fix(i18n): complete Vietnamese dashboard localization

* fix(i18n): align Vietnamese locale key order

* fix(i18n): localize remaining production surfaces

* fix(env): write repaired settings to data directory

* fix(i18n): sync Vietnamese locale with release

* fix(i18n): keep Vietnamese parity order-independent

* fix(i18n): address locale review regressions

* feat(i18n): sync complete UI translations

* fix(i18n): sync locale keys after rebase

* docs: sync release metadata and environment references

* chore(quality): rebaseline localized UI files

* fix(quality): repair release-base regressions

* chore(test): sync mutation coverage inputs

* fix(quality): keep localized UI within complexity ratchet

* fix(typecheck): repair localized dashboard regressions

* fix(test): clear locale and dashboard CI failures

* chore(ci): retry interrupted DAST run

* fix(ci): keep DAST smoke within probe budget

* fix(quality): drop scope-creep test/baseline changes from vi-locale PR

The merge-conflict resolution in this Vietnamese i18n PR accidentally
carried over reformatting-only changes to tests/unit/db-core-init.test.ts
(Prettier layout + a fixture column) that have nothing to do with
localization. Revert that file to the release/v3.8.49 version and drop
the two rebaseline entries this created in file-size-baseline.json,
restoring the db-core-init.test.ts ceiling to 877. The legitimate i18n
rebaseline entry (_rebaseline_2026_07_19_pr7493_i18n) is untouched.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

* fix(i18n): rescope PR to Vietnamese translation quality only

The bulk-filled en.json in this branch carried +2219 phantom keys from a
stale base, breaking key parity for every other locale, and the code
changes (EndpointPageClient.tsx and others) broke existing dashboard
contract tests. Everything outside src/i18n/messages/vi.json is reverted
to release/v3.8.49; only the Vietnamese translation improvements remain
(659 previously __MISSING__ keys filled, 2573 English-fallback keys
translated, 2179 over-translated technical literals like "POST /a2a"
corrected back to their original form).

Reconciled the vi.json keyset against the current release tip (English
UI strings added by merged PRs since this branch was opened) using the
same plain-English-fallback convention already used elsewhere in the
file, and added a regression test asserting Vietnamese key parity with
English, ICU placeholder parity, and no missing/empty translations.

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: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
nguyenha935
2026-07-20 08:31:04 +07:00
committed by GitHub
parent c7cbd2ade6
commit e744412760
3 changed files with 7227 additions and 5431 deletions

View File

@@ -0,0 +1 @@
- fix(i18n): replace machine-bulk-filled Vietnamese UI strings with human-quality translations — 5411 values improved, technical literals (RPC methods, endpoints) no longer over-translated (#7493)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
import assert from "node:assert/strict";
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, []);
});