feat(webui): improve slash command actions

This commit is contained in:
Xubin Ren
2026-05-24 21:24:54 +08:00
parent 3f0098839e
commit 92915ea424
14 changed files with 576 additions and 52 deletions
+81
View File
@@ -8,6 +8,20 @@ import { resources } from "@/i18n";
const QUICK_ACTION_KEYS = ["plan", "analyze", "brainstorm", "code", "summarize", "more"];
const IMAGE_QUICK_ACTION_KEYS = ["icon", "sticker", "poster", "product", "portrait", "edit"];
const SLASH_COMMAND_KEYS = [
"new",
"stop",
"restart",
"status",
"model",
"history",
"dream",
"dream_log",
"dream_restore",
"goal",
"help",
"pairing",
];
const SETTINGS_NAV_KEYS = [
"overview",
"appearance",
@@ -18,6 +32,33 @@ const SETTINGS_NAV_KEYS = [
"advanced",
];
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function flattenResource(value: unknown, prefix = ""): Map<string, unknown> {
const out = new Map<string, unknown>();
if (!isRecord(value)) return out;
for (const [key, child] of Object.entries(value)) {
const path = prefix ? `${prefix}.${key}` : key;
if (isRecord(child)) {
for (const [childPath, childValue] of flattenResource(child, path)) {
out.set(childPath, childValue);
}
} else {
out.set(path, child);
}
}
return out;
}
function interpolationKeys(value: unknown): string[] {
if (typeof value !== "string") return [];
return Array.from(value.matchAll(/{{\s*([\w.-]+)\s*}}/g))
.map((match) => match[1])
.sort();
}
describe("webui i18n", () => {
it("switches UI copy and document locale through the language switcher", async () => {
const user = userEvent.setup();
@@ -72,6 +113,46 @@ describe("webui i18n", () => {
}
});
it("keeps every locale aligned with the English resource shape", () => {
const reference = flattenResource(resources.en.common);
for (const [locale, resource] of Object.entries(resources)) {
if (locale === "en") continue;
const current = flattenResource(resource.common);
const missing = Array.from(reference.keys()).filter((key) => !current.has(key));
const extra = Array.from(current.keys()).filter((key) => !reference.has(key));
const interpolationMismatches = Array.from(reference.entries())
.filter(([key]) => current.has(key))
.filter(([key, value]) =>
interpolationKeys(value).join(",") !== interpolationKeys(current.get(key)).join(",")
)
.map(([key]) => key);
expect({ locale, missing, extra, interpolationMismatches }).toEqual({
locale,
missing: [],
extra: [],
interpolationMismatches: [],
});
}
});
it("keeps slash commands localized for every registered locale", () => {
for (const resource of Object.values(resources)) {
const slash = resource.common.thread.composer.slash;
expect(slash.badges.current).toBeTruthy();
expect(slash.badges.recent).toBeTruthy();
expect(slash.details.goalActive).toBeTruthy();
expect(slash.details.goalReady).toBeTruthy();
expect(slash.details.history).toBeTruthy();
expect(slash.details.stopRunning).toBeTruthy();
for (const key of SLASH_COMMAND_KEYS) {
const command = slash.commands[key as keyof typeof slash.commands];
expect(command.title).toBeTruthy();
expect(command.description).toBeTruthy();
}
}
});
it("keeps settings navigation localized for every registered locale", () => {
for (const resource of Object.values(resources)) {
const common = resource.common;