feat(34-02): extract hardcoded strings from thread/candidate components

- CandidateCard: replace all hardcoded titles and badge text with t()
- CandidateListItem: add useTranslation, replace winner/delete/open labels and +/- Notes badge
- CandidateForm: add useTranslation, replace all form labels, placeholders, validation errors, submit button
- ComparisonTable: move STATUS_LABELS inside component with t(), replace all ATTRIBUTE_ROWS labels, View button, impact row labels
- StatusBadge: refactor STATUS_CONFIG to STATUS_ICONS + runtime STATUS_LABELS via t()
- CreateThreadModal: replace title, thread name label, category label, placeholder, cancel/submit buttons, error messages
- AddToThreadModal: replace modal titles, labels, placeholders, back/cancel/submit buttons, error messages
- threads.json: extend candidateForm with category, notes, pros, cons, product link labels and all placeholders
This commit is contained in:
2026-04-18 13:44:26 +02:00
parent c5af1247c0
commit 6fd8874970
8 changed files with 158 additions and 84 deletions

View File

@@ -1,4 +1,5 @@
import { useNavigate } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import { useFormatters } from "../hooks/useFormatters";
import type { CandidateDelta } from "../hooks/useImpactDeltas";
import { LucideIcon } from "../lib/iconData";
@@ -55,6 +56,7 @@ export function CandidateCard({
rank,
delta,
}: CandidateCardProps) {
const { t } = useTranslation("threads");
const { weight, price } = useFormatters();
const navigate = useNavigate();
const openConfirmDeleteCandidate = useUIStore(
@@ -90,10 +92,10 @@ export function CandidateCard({
}
}}
className="absolute top-2 left-2 z-10 px-2 py-0.5 flex items-center gap-1 rounded-full text-xs font-medium bg-amber-100/90 text-amber-700 hover:bg-amber-200 opacity-0 group-hover:opacity-100 transition-all cursor-pointer"
title="Pick as winner"
title={t("candidateCard.pickAsWinner")}
>
<LucideIcon name="trophy" size={12} />
Winner
{t("candidateCard.winner")}
</span>
)}
<span
@@ -110,7 +112,7 @@ export function CandidateCard({
}
}}
className={`absolute top-2 ${productUrl ? "right-10" : "right-2"} z-10 w-6 h-6 flex items-center justify-center rounded-full bg-gray-100/80 text-gray-400 hover:bg-red-100 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all cursor-pointer`}
title="Delete candidate"
title={t("candidateCard.deleteCandidate")}
>
<svg
className="w-3.5 h-3.5"
@@ -141,7 +143,7 @@ export function CandidateCard({
}
}}
className="absolute top-2 right-2 z-10 w-6 h-6 flex items-center justify-center rounded-full bg-gray-100/80 text-gray-400 hover:bg-gray-200 hover:text-gray-600 opacity-0 group-hover:opacity-100 transition-all cursor-pointer"
title="Open product link"
title={t("candidateCard.openProductLink")}
>
<svg
className="w-3.5 h-3.5"
@@ -214,7 +216,7 @@ export function CandidateCard({
<StatusBadge status={status} onStatusChange={onStatusChange} />
{(pros || cons) && (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-50 text-purple-700">
+/- Notes
{t("candidateCard.prosCons")}
</span>
)}
</div>