mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 12:04:47 +00:00
fix(ui): harden i18n report filters
Signed-off-by: samzong <samzong.lu@gmail.com>
This commit is contained in:
committed by
Peter Steinberger
parent
ee9d471865
commit
4e76d6e427
@@ -30,6 +30,7 @@ const LOCALE_LABELS: Record<string, string> = {
|
||||
"zh-CN": "Simplified Chinese",
|
||||
"zh-TW": "Traditional Chinese",
|
||||
};
|
||||
const REPORT_LOCALES = new Set(Object.keys(LOCALE_LABELS));
|
||||
const PATH_LABELS: Record<string, string> = {
|
||||
"ui/src/ui/chat/chat-queue.ts": "Chat queue",
|
||||
"ui/src/ui/chat/grouped-render.ts": "Chat message groups",
|
||||
@@ -108,13 +109,16 @@ export function parseArgs(argv: string[]): ReportArgs {
|
||||
continue;
|
||||
}
|
||||
if (arg === "--locale") {
|
||||
args.locale = readOptionValue(argv, (index += 1), arg);
|
||||
args.locale = parseLocale(readOptionValue(argv, (index += 1), arg));
|
||||
continue;
|
||||
}
|
||||
if (arg === "--top") {
|
||||
const raw = readOptionValue(argv, (index += 1), arg);
|
||||
if (!/^[1-9][0-9]*$/.test(raw)) {
|
||||
throw new Error(`--top must be a positive integer: ${raw}`);
|
||||
}
|
||||
const top = Number.parseInt(raw, 10);
|
||||
if (!Number.isSafeInteger(top) || top < 1) {
|
||||
if (!Number.isSafeInteger(top)) {
|
||||
throw new Error(`--top must be a positive integer: ${raw}`);
|
||||
}
|
||||
args.top = top;
|
||||
@@ -133,6 +137,13 @@ function readOptionValue(argv: string[], index: number, flag: string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseLocale(locale: string) {
|
||||
if (!REPORT_LOCALES.has(locale)) {
|
||||
throw new Error(`unknown locale: ${locale}`);
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
export function filterRawCopyEntries(entries: RawCopyBaselineEntry[], surface?: string) {
|
||||
if (!surface) {
|
||||
return entries;
|
||||
@@ -167,9 +178,13 @@ function compareCountThenName<T>(nameOf: (value: T) => string) {
|
||||
}
|
||||
|
||||
function pathTokens(repoPath: string) {
|
||||
return repoPath
|
||||
.split("/")
|
||||
.flatMap((part) => part.replace(/\.[^.]+$/, "").split(/[^A-Za-z0-9]+/))
|
||||
return repoPath.split("/").flatMap((part) => surfaceTokens(part.replace(/\.[^.]+$/, "")));
|
||||
}
|
||||
|
||||
function surfaceTokens(value: string) {
|
||||
return value
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.split(/[^A-Za-z0-9]+/)
|
||||
.filter(Boolean)
|
||||
.map(normalizeToken);
|
||||
}
|
||||
@@ -206,7 +221,9 @@ export function filterTranslationKeysBySurface(keys: string[], surface?: string)
|
||||
return keys;
|
||||
}
|
||||
const normalized = normalizeToken(surface);
|
||||
return keys.filter((key) => key.split(".").some((part) => normalizeToken(part) === normalized));
|
||||
return keys.filter((key) =>
|
||||
key.split(".").some((part) => surfaceTokens(part).some((token) => token === normalized)),
|
||||
);
|
||||
}
|
||||
|
||||
export function formatReport(input: ReportInput) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
filterTranslationKeysBySurface,
|
||||
flattenTranslations,
|
||||
formatReport,
|
||||
parseArgs,
|
||||
summarizeRawCopy,
|
||||
type RawCopyBaselineEntry,
|
||||
} from "../../scripts/control-ui-i18n-report.ts";
|
||||
@@ -35,6 +36,23 @@ const entries: RawCopyBaselineEntry[] = [
|
||||
];
|
||||
|
||||
describe("control-ui-i18n report helpers", () => {
|
||||
it("rejects invalid numeric limits", () => {
|
||||
expect(() => parseArgs(["--top", "3abc"])).toThrow("--top must be a positive integer");
|
||||
expect(() => parseArgs(["--top", "1.5"])).toThrow("--top must be a positive integer");
|
||||
expect(() => parseArgs(["--top", "0"])).toThrow("--top must be a positive integer");
|
||||
expect(() => parseArgs(["--top", "999999999999999999999999999"])).toThrow(
|
||||
"--top must be a positive integer",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects locale path traversal before filesystem access", () => {
|
||||
expect(parseArgs(["--locale", "zh-CN"])).toMatchObject({ locale: "zh-CN" });
|
||||
expect(() => parseArgs(["--locale", "../zh-CN"])).toThrow("unknown locale");
|
||||
expect(() => parseArgs(["--locale", "../../../../scripts/control-ui-i18n-report"])).toThrow(
|
||||
"unknown locale",
|
||||
);
|
||||
});
|
||||
|
||||
it("filters raw-copy entries by path surface token", () => {
|
||||
expect(filterRawCopyEntries(entries, "agents")).toEqual([entries[1]]);
|
||||
expect(filterRawCopyEntries(entries, "config")).toEqual([entries[2]]);
|
||||
@@ -70,10 +88,26 @@ describe("control-ui-i18n report helpers", () => {
|
||||
it("filters same-as-English keys by translation key surface token", () => {
|
||||
expect(
|
||||
filterTranslationKeysBySurface(
|
||||
["agents.tabs.cronJobs", "chat.composer.send", "usage.common.emptyValue"],
|
||||
[
|
||||
"agents.tabs.cronJobs",
|
||||
"chat.composer.send",
|
||||
"sessionsView.thinking",
|
||||
"usage.common.emptyValue",
|
||||
],
|
||||
"chat",
|
||||
),
|
||||
).toEqual(["chat.composer.send"]);
|
||||
expect(
|
||||
filterTranslationKeysBySurface(
|
||||
[
|
||||
"agents.tabs.cronJobs",
|
||||
"chat.composer.send",
|
||||
"sessionsView.thinking",
|
||||
"usage.common.emptyValue",
|
||||
],
|
||||
"sessions",
|
||||
),
|
||||
).toEqual(["sessionsView.thinking"]);
|
||||
});
|
||||
|
||||
it("formats pasteable report text", () => {
|
||||
|
||||
Reference in New Issue
Block a user