Files
fusion/packages/dashboard/app/components/DuplicateWarningModal.tsx
gsxdsm 1e49494bac feat(i18n): full-sweep string migration — 5,930 keys across 5 locales (#1352)
Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
  English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
  carry ~5,930 keys each across common/app/errors/cli namespaces;
  CLI bundles regenerated (6 locales incl. ko)

Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
  plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
  moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
  literal {{placeholders}}); dashboard vitest.setup boots a minimal en
  i18next instance for the same reason

Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:06:53 -07:00

72 lines
3.0 KiB
TypeScript

import "./DuplicateWarningModal.css";
import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import type { DuplicateMatch } from "../api";
interface DuplicateWarningModalProps {
matches: DuplicateMatch[];
onOpen: (id: string) => void;
onProceed: () => void;
onCancel: () => void;
}
function toStatusClass(column: string): string {
return `card-status-badge--${column}`;
}
export function DuplicateWarningModal({ matches, onOpen, onProceed, onCancel }: DuplicateWarningModalProps) {
const { t } = useTranslation("app");
const cancelButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
cancelButtonRef.current?.focus();
}, []);
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
event.preventDefault();
onCancel();
}
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onCancel]);
return (
<div className="modal-overlay open" role="presentation">
<div className="modal duplicate-warning-modal" role="dialog" aria-modal="true" aria-labelledby="duplicate-warning-modal-title">
<div className="modal-header">
<h3 id="duplicate-warning-modal-title">{t("duplicateWarning.title", "Possible duplicates")}</h3>
</div>
<div className="duplicate-warning-modal-body">
<p className="duplicate-warning-modal-copy">{t("duplicateWarning.message", "We found similar active tasks. Open an existing task or create this one anyway.")}</p>
<div className="duplicate-warning-modal-list">
{matches.map((match) => (
<article className="card duplicate-warning-modal-item" key={match.id}>
<div className="duplicate-warning-modal-item-header">
<span className="card-id">{match.id}</span>
<span className={`card-status-badge ${toStatusClass(match.column)}`}>{match.column}</span>
<span className="duplicate-warning-modal-score">{Math.round(match.score * 100)}%</span>
</div>
<div className="card-title duplicate-warning-modal-title">{match.title || t("duplicateWarning.untitledTask", "Untitled task")}</div>
<div className="duplicate-warning-modal-actions">
<button className="btn btn-sm" type="button" onClick={() => onOpen(match.id)}>{t("duplicateWarning.open", "Open")}</button>
</div>
</article>
))}
</div>
</div>
<div className="modal-actions">
<div className="modal-actions-left">
<button className="btn" type="button" ref={cancelButtonRef} onClick={onCancel}>{t("duplicateWarning.cancel", "Cancel")}</button>
</div>
<div className="modal-actions-right">
<button className="btn btn-primary" type="button" onClick={onProceed}>{t("duplicateWarning.createAnyway", "Create anyway")}</button>
</div>
</div>
</div>
</div>
);
}