дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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
112 рядки
3.7 KiB
TypeScript
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>
|
|
);
|
|
}
|