Файли
openidea/src/features/macros/MacrosDialog.tsx
T
l0ng-ai b03983847c fix(i18n): Translate UI text built from template literals
Template literals such as `Remove data source '${name}'?` reached props and
calls that look text up exactly (dialog messages, labels, aria-labels, menu
items, action titles), so they always showed in English. Some notification
titles also spliced lowercase English words into a sentence.

- `tx` also works as a tag: tx`Remove data source '${name}'?` looks up
  "Remove data source '{name}'?" by its literal parts, fills in the values
  (translated when the catalog has them) and remembers the result, so the
  primitives that pass strings through `tx` translate it again after a locale
  change or once the catalog has loaded (actions registered at startup).
- 269 template literals at exact-lookup sites now use the tag; text that
  spliced words or plural suffixes is written as one sentence per variant
  (safe delete, Docker delete/prune, rebase, conflicts, VCS "No … in
  progress", database copy errors, inspection descriptions, ...).
- zh: 280 new entries; stale and duplicate-shape entries removed.
- textCatalog guard: untagged template literals at exact-lookup sites fail
  (with an allowlist for code-like text), tagged text is checked against the
  catalog by shape, case-changing values and `c ? "a" : "b"` suffixes inside
  translated templates fail, and a catalog may hold one entry per template shape.

Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
2026-10-27 18:43:53 +08:00

112 рядки
3.7 KiB
TypeScript

/** The "Macro recording" status bar item and the Edit Macros dialog. */
import { useState } from "react";
import { create } from "zustand";
import { executeAction, getAction } from "@/platform/actions";
import { t, tx } from "@/platform/i18n";
import { BarButton, Button, Dialog, Icon, List, T } from "@/ui";
import { confirm } from "@/workbench/messages";
import { promptInput } from "@/workbench/inputDialogs";
import { deleteMacro, describeStep, renameMacro, useMacros } from "./model";
import styles from "./macros.module.css";
const useEditOpen = create<{ open: boolean }>(() => ({ open: false }));
export const showEditMacros = () => useEditOpen.setState({ open: true });
const actionTitle = (id: string) => getAction(id)?.title ?? id;
/** Shown while recording (IDEA: "Macro recording started" in the status bar); click stops. */
export function MacroStatus() {
const steps = useMacros((s) => s.recording?.length);
if (steps === undefined) return null;
return (
<BarButton
className={styles.status}
tooltip="Stop Macro Recording"
aria-label="Stop Macro Recording"
onClick={() => void executeAction("macro.stopRecording")}
>
<span className={styles.dot} aria-hidden="true" />
<T>Recording macro…</T>
<Icon name="stop" size={14} />
</BarButton>
);
}
export function MacrosDialogHost() {
const open = useEditOpen((s) => s.open);
return open ? <EditMacrosDialog onClose={() => useEditOpen.setState({ open: false })} /> : null;
}
function EditMacrosDialog({ onClose }: { onClose: () => void }) {
const saved = useMacros((s) => s.saved);
const [active, setActive] = useState(0);
const macro = saved[Math.min(active, saved.length - 1)];
const rename = async () => {
if (!macro) return;
const name = await promptInput({
title: "Rename Macro",
label: "New name:",
initial: macro.name,
okLabel: "Rename",
validate: (v) =>
!v.trim()
? "Enter a macro name"
: v.trim() !== macro.name && saved.some((m) => m.name === v.trim())
? "A macro with this name exists"
: null,
});
if (name) renameMacro(macro.name, name);
};
const remove = async () => {
if (!macro) return;
if (await confirm({ title: "Delete Macro", message: tx`Delete the macro '${macro.name}'?`, ok: "Delete", icon: "warning" }))
deleteMacro(macro.name);
};
return (
<Dialog
open
title="Edit Macros"
onClose={onClose}
width={620}
buttons={[{ label: t("common.close"), role: "cancel" }]}
>
<div className={styles.edit}>
<div className={styles.column}>
<List
items={saved}
getKey={(m) => m.name}
activeIndex={Math.min(active, saved.length - 1)}
onActiveChange={setActive}
rowHeight={24}
className={styles.list}
aria-label="Macros"
empty={
<div className={styles.empty}>
<T>No saved macros</T>
</div>
}
renderItem={(m) => <div className={styles.row}>{m.name}</div>}
/>
<div className={styles.buttons}>
<Button disabled={!macro} onClick={() => void rename()}>
<T>Rename…</T>
</Button>
<Button disabled={!macro} onClick={() => void remove()}>
<T>Delete</T>
</Button>
</div>
</div>
<ol className={styles.steps} aria-label="Recorded steps">
{macro?.steps.map((step, i) => (
<li key={i} className={styles.step}>
{describeStep(step, actionTitle)}
</li>
))}
</ol>
</div>
</Dialog>
);
}