feat(frontend): i18n template pages (list, form, picker modal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-06-21 23:36:20 +02:00
parent 5b93f880a3
commit 284494cee8
6 changed files with 124 additions and 124 deletions

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { extractApiError } from '@/api/client';
import type { SimulationTemplate } from '@/api/types';
import { useTemplates } from '@/hooks/useTemplates';
@@ -23,6 +24,7 @@ export function TemplatePickerModal({
onSelectTemplate,
isPending = false,
}: TemplatePickerModalProps): JSX.Element {
const { t } = useTranslation();
const { data, isLoading, isError, error, refetch } = useTemplates();
return (
@@ -37,11 +39,11 @@ export function TemplatePickerModal({
<div className="relative card-product max-w-xl w-full mx-md flex flex-col gap-md max-h-[80vh] overflow-hidden">
<div className="flex items-center justify-between">
<h2 id="tpl-picker-title" className="text-[20px] font-medium text-ink">
From template
{t('template.picker.title')}
</h2>
<button
type="button"
aria-label="Close"
aria-label={t('common.close')}
onClick={onClose}
className="text-graphite hover:text-ink text-[20px] leading-none"
>
@@ -50,19 +52,18 @@ export function TemplatePickerModal({
</div>
<div className="overflow-y-auto flex-1 -mx-xl px-xl">
{isLoading ? <LoadingState label="Loading templates…" /> : null}
{isLoading ? <LoadingState label={t('template.picker.loading')} /> : null}
{isError ? (
<ErrorState
message={extractApiError(error, 'Could not load templates')}
message={extractApiError(error, t('template.form.errorLoad'))}
onRetry={() => refetch()}
/>
) : null}
{!isLoading && !isError && data && data.length === 0 ? (
<EmptyState
title="No templates available"
description="Create one from the Templates page."
title={t('template.picker.empty')}
/>
) : null}
@@ -70,22 +71,22 @@ export function TemplatePickerModal({
<table className="w-full text-left" data-testid="template-picker-table">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-md py-sm">Name</th>
<th className="px-md py-sm">MITRE</th>
<th className="px-md py-sm">Created by</th>
<th className="px-md py-sm">{t('template.list.col.name')}</th>
<th className="px-md py-sm">{t('simulation.list.col.mitre')}</th>
<th className="px-md py-sm">{t('template.list.col.createdBy')}</th>
</tr>
</thead>
<tbody>
{data.map((t) => (
{data.map((template) => (
<tr
key={t.id}
key={template.id}
className="border-b border-hairline last:border-0 hover:bg-cloud cursor-pointer"
onClick={() => !isPending && onSelectTemplate(t)}
data-testid={`template-row-${t.id}`}
onClick={() => !isPending && onSelectTemplate(template)}
data-testid={`template-row-${template.id}`}
>
<td className="px-md py-sm text-ink font-medium">{t.name}</td>
<td className="px-md py-sm text-charcoal text-[14px]">{mitreCount(t)}</td>
<td className="px-md py-sm text-charcoal text-[14px]">{t.created_by.username}</td>
<td className="px-md py-sm text-ink font-medium">{template.name}</td>
<td className="px-md py-sm text-charcoal text-[14px]">{mitreCount(template)}</td>
<td className="px-md py-sm text-charcoal text-[14px]">{template.created_by.username}</td>
</tr>
))}
</tbody>
@@ -95,7 +96,7 @@ export function TemplatePickerModal({
<div className="border-t border-hairline pt-sm">
<button type="button" className="btn-outline-ink" onClick={onClose}>
Cancel
{t('common.cancel')}
</button>
</div>
</div>