mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(ui): avoid noisy i18n report locale warnings
This commit is contained in:
@@ -2,13 +2,10 @@ import { existsSync } from "node:fs";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import type { TranslationMap } from "../ui/src/i18n/lib/types.ts";
|
||||
|
||||
const ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
||||
const I18N_ASSETS_DIR = path.join(ROOT, "ui/src/i18n/.i18n");
|
||||
const LOCALES_DIR = path.join(ROOT, "ui/src/i18n/locales");
|
||||
const RAW_COPY_BASELINE_PATH = path.join(I18N_ASSETS_DIR, "raw-copy-baseline.json");
|
||||
const SOURCE_LOCALE_PATH = path.join(LOCALES_DIR, "en.ts");
|
||||
const DEFAULT_TOP = 10;
|
||||
const LOCALE_LABELS: Record<string, string> = {
|
||||
ar: "Arabic",
|
||||
@@ -91,7 +88,6 @@ export type RawCopySummary = {
|
||||
export type LocaleSummary = {
|
||||
fallbackKeysInScope: string[];
|
||||
meta: LocaleMeta;
|
||||
sameAsEnglishKeys: string[];
|
||||
};
|
||||
|
||||
type ReportInput = {
|
||||
@@ -193,29 +189,6 @@ function normalizeToken(value: string) {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function flattenTranslations(
|
||||
value: TranslationMap,
|
||||
prefix = "",
|
||||
out = new Map<string, string>(),
|
||||
) {
|
||||
for (const [key, nested] of Object.entries(value)) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (typeof nested === "string") {
|
||||
out.set(fullKey, nested);
|
||||
continue;
|
||||
}
|
||||
flattenTranslations(nested, fullKey, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function computeSameAsEnglishKeys(source: Map<string, string>, target: Map<string, string>) {
|
||||
return [...target.entries()]
|
||||
.filter(([key, value]) => source.get(key) === value)
|
||||
.map(([key]) => key)
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
export function filterTranslationKeysBySurface(keys: string[], surface?: string) {
|
||||
if (!surface) {
|
||||
return keys;
|
||||
@@ -275,24 +248,14 @@ function formatIssueLines(input: ReportInput) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (
|
||||
input.locale.fallbackKeysInScope.length === 0 &&
|
||||
input.locale.sameAsEnglishKeys.length === 0
|
||||
) {
|
||||
if (input.locale.fallbackKeysInScope.length === 0) {
|
||||
lines.push(` No ${input.locale.meta.locale} locale problems found in this scope.`);
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (input.locale.fallbackKeysInScope.length > 0) {
|
||||
lines.push(
|
||||
` ${input.locale.meta.locale} has ${formatMissingKeyCount(input.locale.fallbackKeysInScope.length)}.`,
|
||||
);
|
||||
}
|
||||
if (input.locale.sameAsEnglishKeys.length > 0) {
|
||||
lines.push(
|
||||
` ${formatSameAsEnglishIssue(input.locale.sameAsEnglishKeys.length, input.locale.meta.locale)}.`,
|
||||
);
|
||||
}
|
||||
lines.push(
|
||||
` ${input.locale.meta.locale} has ${formatMissingKeyCount(input.locale.fallbackKeysInScope.length)}.`,
|
||||
);
|
||||
return lines;
|
||||
}
|
||||
|
||||
@@ -322,13 +285,6 @@ function formatFallbackCount(count: number) {
|
||||
return `${count} ${count === 1 ? "fallback" : "fallbacks"}`;
|
||||
}
|
||||
|
||||
function formatSameAsEnglishIssue(count: number, locale: string) {
|
||||
if (count === 1) {
|
||||
return `1 ${locale} translation still matches English and needs review`;
|
||||
}
|
||||
return `${count} ${locale} translations still match English and need review`;
|
||||
}
|
||||
|
||||
function formatSurfaceLabel(surface?: string) {
|
||||
if (!surface) {
|
||||
return "all Control UI";
|
||||
@@ -369,20 +325,6 @@ async function loadLocaleMeta(locale: string): Promise<LocaleMeta> {
|
||||
return JSON.parse(await readFile(metaPath, "utf8")) as LocaleMeta;
|
||||
}
|
||||
|
||||
async function loadLocaleMap(locale: string): Promise<TranslationMap> {
|
||||
const filePath = locale === "en" ? SOURCE_LOCALE_PATH : path.join(LOCALES_DIR, `${locale}.ts`);
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error(`unknown locale file: ${locale}`);
|
||||
}
|
||||
const exportName = locale.replaceAll("-", "_");
|
||||
const mod = (await import(pathToFileURL(filePath).href)) as Record<string, TranslationMap>;
|
||||
const map = mod[exportName];
|
||||
if (!map) {
|
||||
throw new Error(`locale ${locale} does not export ${exportName}`);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
async function buildReport(args: ReportArgs) {
|
||||
const baseline = await loadRawCopyBaseline();
|
||||
const entries = filterRawCopyEntries(baseline.entries, args.surface);
|
||||
@@ -392,18 +334,10 @@ async function buildReport(args: ReportArgs) {
|
||||
};
|
||||
|
||||
if (args.locale) {
|
||||
const [meta, source, target] = await Promise.all([
|
||||
loadLocaleMeta(args.locale),
|
||||
loadLocaleMap("en"),
|
||||
loadLocaleMap(args.locale),
|
||||
]);
|
||||
const meta = await loadLocaleMeta(args.locale);
|
||||
input.locale = {
|
||||
fallbackKeysInScope: filterTranslationKeysBySurface(meta.fallbackKeys, args.surface),
|
||||
meta,
|
||||
sameAsEnglishKeys: filterTranslationKeysBySurface(
|
||||
computeSameAsEnglishKeys(flattenTranslations(source), flattenTranslations(target)),
|
||||
args.surface,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
computeSameAsEnglishKeys,
|
||||
filterRawCopyEntries,
|
||||
filterTranslationKeysBySurface,
|
||||
flattenTranslations,
|
||||
formatReport,
|
||||
parseArgs,
|
||||
summarizeRawCopy,
|
||||
type RawCopyBaselineEntry,
|
||||
} from "../../scripts/control-ui-i18n-report.ts";
|
||||
import type { TranslationMap } from "../../ui/src/i18n/lib/types.ts";
|
||||
|
||||
const entries: RawCopyBaselineEntry[] = [
|
||||
{
|
||||
@@ -70,22 +67,7 @@ describe("control-ui-i18n report helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("flattens locale maps and reports same-as-English keys", () => {
|
||||
const source: TranslationMap = {
|
||||
actions: { send: "Send", stop: "Stop" },
|
||||
brand: "OpenClaw",
|
||||
};
|
||||
const target: TranslationMap = {
|
||||
actions: { send: "发送", stop: "Stop" },
|
||||
brand: "OpenClaw",
|
||||
};
|
||||
|
||||
expect(
|
||||
computeSameAsEnglishKeys(flattenTranslations(source), flattenTranslations(target)),
|
||||
).toEqual(["actions.stop", "brand"]);
|
||||
});
|
||||
|
||||
it("filters same-as-English keys by translation key surface token", () => {
|
||||
it("filters translation keys by surface token", () => {
|
||||
expect(
|
||||
filterTranslationKeysBySurface(
|
||||
[
|
||||
@@ -125,7 +107,6 @@ describe("control-ui-i18n report helpers", () => {
|
||||
translatedKeys: 2,
|
||||
workflow: 1,
|
||||
},
|
||||
sameAsEnglishKeys: ["brand"],
|
||||
},
|
||||
rawCopy: summarizeRawCopy(entries, 1),
|
||||
surface: "chat",
|
||||
@@ -144,7 +125,7 @@ describe("control-ui-i18n report helpers", () => {
|
||||
);
|
||||
expect(report).toContain("Chat still has UI text written directly in code.\n");
|
||||
expect(report).toContain("zh-CN has 1 missing key.\n");
|
||||
expect(report).toContain("1 zh-CN translation still matches English and needs review.\n");
|
||||
expect(report).not.toContain("matches English");
|
||||
expect(report).toContain("4 Config form: ui/src/ui/views/config-form.render.ts\n");
|
||||
expect(report).toContain("Move text from the focus modules into translation keys.\n");
|
||||
expect(report).toContain(
|
||||
@@ -169,7 +150,6 @@ describe("control-ui-i18n report helpers", () => {
|
||||
translatedKeys: 2,
|
||||
workflow: 1,
|
||||
},
|
||||
sameAsEnglishKeys: [],
|
||||
},
|
||||
rawCopy: summarizeRawCopy(entries, 1),
|
||||
surface: "chat",
|
||||
|
||||
Reference in New Issue
Block a user