feat(i18n): migrate hardcoded German strings to t() across 104 components/pages — AST-based batch with re-parse gate, 423 new de.json keys; tsc clean; vitest failures byte-identical to clean-tree baseline (pre-existing)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { X } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface ModalProps {
|
||||
open: boolean;
|
||||
@@ -32,6 +33,7 @@ export function Modal({
|
||||
showCloseButton = true,
|
||||
fullScreenMobile = false,
|
||||
}: ModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const previouslyFocused = useRef<HTMLElement | null>(null);
|
||||
|
||||
@@ -98,7 +100,7 @@ export function Modal({
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-3 right-3 md:top-4 md:right-4 text-secondary-400 hover:text-secondary-600 min-h-touch min-w-touch flex items-center justify-center rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
||||
aria-label="Close dialog"
|
||||
aria-label={t('modal.closedialog')}
|
||||
>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function Pagination({ currentPage, totalPages, total, pageSize, onPageCha
|
||||
if (totalPages <= 1) return null;
|
||||
|
||||
return (
|
||||
<nav className="flex items-center justify-between px-3 py-1.5 border-t border-secondary-200 bg-white" aria-label="Pagination">
|
||||
<nav className="flex items-center justify-between px-3 py-1.5 border-t border-secondary-200 bg-white" aria-label={t('pagination.pagination')}>
|
||||
<div className="text-xs text-secondary-500">
|
||||
<span>{start}–{end}</span> <span>{t('table.of')}</span> <span>{total}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface SkeletonProps {
|
||||
className?: string;
|
||||
@@ -9,6 +10,7 @@ export interface SkeletonProps {
|
||||
}
|
||||
|
||||
export function Skeleton({ className, variant = 'rect', width, height }: SkeletonProps) {
|
||||
const { t } = useTranslation();
|
||||
const variantClass = {
|
||||
text: 'rounded',
|
||||
rect: 'rounded-md',
|
||||
@@ -24,14 +26,15 @@ export function Skeleton({ className, variant = 'rect', width, height }: Skeleto
|
||||
)}
|
||||
style={{ width, height }}
|
||||
role="status"
|
||||
aria-label="Wird geladen"
|
||||
aria-label={t('skeleton.wirdgeladen')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonText({ lines = 3, className }: { lines?: number; className?: string }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={clsx('space-y-2', className)} role="status" aria-label="Wird geladen">
|
||||
<div className={clsx('space-y-2', className)} role="status" aria-label={t('skeleton.wirdgeladen')}>
|
||||
{Array.from({ length: lines }).map((_, i) => (
|
||||
<Skeleton
|
||||
key={i}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface TableColumn<T> {
|
||||
key: string;
|
||||
@@ -30,6 +31,7 @@ export function Table<T extends Record<string, any>>({
|
||||
emptyMessage = 'Keine Daten vorhanden',
|
||||
loading = false,
|
||||
}: TableProps<T>) {
|
||||
const { t } = useTranslation();
|
||||
const [sortColumn, setSortColumn] = useState<string | null>(null);
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
|
||||
|
||||
@@ -59,7 +61,7 @@ export function Table<T extends Record<string, any>>({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto" role="region" aria-label="Data table">
|
||||
<div className="overflow-x-auto" role="region" aria-label={t('table.datatable')}>
|
||||
<table className="min-w-full divide-y divide-secondary-200">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -102,7 +104,7 @@ export function Table<T extends Record<string, any>>({
|
||||
<td colSpan={columns.length} className="px-6 py-8 text-center text-secondary-500">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Loader2 className="animate-spin motion-reduce:animate-none h-5 w-5" aria-hidden="true" />
|
||||
Wird geladen...
|
||||
{t('table.wirdgeladen')}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Check, X, AlertTriangle, Info } from 'lucide-react';
|
||||
import { useUIStore, Toast as ToastType } from '@/store/uiStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const toastStyles: Record<ToastType['type'], string> = {
|
||||
success: 'bg-success-50 border-success-500 text-success-800',
|
||||
@@ -18,6 +19,7 @@ const toastIcons: Record<ToastType['type'], React.ComponentType<{ className?: st
|
||||
};
|
||||
|
||||
function ToastItem({ toast, onRemove }: { toast: ToastType; onRemove: (id: string) => void }) {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
const duration = toast.duration ?? 5000;
|
||||
const timer = setTimeout(() => onRemove(toast.id), duration);
|
||||
@@ -42,7 +44,7 @@ function ToastItem({ toast, onRemove }: { toast: ToastType; onRemove: (id: strin
|
||||
<button
|
||||
onClick={() => onRemove(toast.id)}
|
||||
className="flex-shrink-0 text-current opacity-60 hover:opacity-100 min-h-touch min-w-touch flex items-center justify-center rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-current"
|
||||
aria-label="Close notification"
|
||||
aria-label={t('toast.closenotification')}
|
||||
>
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user