feat(i18n): Konto, Editor und Karte auf Englisch

Task 10: alle sichtbaren Texte von account/, editor/, card/ und der Seite „Passwort neu setzen“ (Texte, aria-label, title, Meldungen,
Rückfragen) gehen durch t()/msg(); en.ts bekommt 87 Einträge.

- account/: Konto-Menü, Login-Dialog (Titel, Reiter, Felder, Meldungen) und errors.ts (errorMessage übersetzt beim Zurückgeben).
  validatePassword nutzt {min}/{max}. Der Login-Dialog wird nur einmal gebaut: die Beschriftung des Schließen-Knopfs wird bei jedem
  Öffnen in der aktuellen Sprache gesetzt. LOGIN_NEEDED und DEFAULT_CARD_NAME bleiben deutsche Konstanten (msg), angezeigt werden sie
  mit t(); der Standardname einer neuen Karte folgt damit der Sprache („My card“).
- editor/: Vorlagen-, Options- und Vorschauspalte, Bild-Upload, Speichern, Download, Rückfragen. Konstanten (upload.ts, download.ts,
  editor-page.ts) bleiben deutsche Strings per msg(); validateImageFile, pngSizeWarning und das Speichern („Speichern fehlgeschlagen –
  {reason}“) liefern den Text schon übersetzt, Fehler tragen weiter die deutsche Konstante (Vergleiche mit READ_MESSAGE/PIXELS_MESSAGE
  gehen weiter) und werden beim Anzeigen mit t(error.message) übersetzt. Zoom „100 %“ / „100%“ und „Bearbeitet: {name}“ mit Platzhaltern.
- card/: Bezeichnungen der Marken (msg) und die Meldungen des Renderers (msg, Anzeige im Editor über t()).
- pages/reset-page.ts, account/login-dialog.ts: Die Meldung „Die Passwörter stimmen nicht überein.“ ist nicht mehr der Schlüssel zum
  Fokus: es zählt die Bedingung selbst, nicht der Vergleich mit dem (nun übersetzten) Text.
- i18n/coverage.test.ts: Heuristik für deutsche Text-Literale außerhalb von t()/msg() (Umlaut/ß, mindestens zwei Wörter, einzelnes
  großgeschriebenes Wort; gelesen wird der Syntaxbaum) für account/, editor/, card/ und pages/reset-page.ts; Ausnahmen mit Grund, jede
  muss noch zutreffen; Scanner mit eigenen Tests. Die Liste der Bereiche ist eine Konstante für Task 11.
- i18n/english-ui.test.ts (neu): Stichproben auf Englisch (Konto-Menü, Login-Dialog, Passwort-Seite, Editor inkl. Fehlerwege) und
  „kein deutscher Wörterbuchtext auf der englischen Seite“.
- test-setup.ts: Wer Module mit vi.resetModules() frisch lädt, bekam eine neue Kopie von i18n.ts mit der Browsersprache (happy-dom: en-US);
  der Browser meldet im Test jetzt Deutsch, damit auch diese Tests deutsch beginnen. main.test.ts: nach dem Wechsel auf Englisch heißt
  der Knopf des Konto-Menüs „Sign in“.
- Tests: Vitest 53 Dateien, 1779 Tests grün, tsc und Build sauber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 23:58:49 +00:00
1 parent a75be2d44c
commit 1ff1873b27
23 files changed
+857 -169

No files matched your search

+7 -3
View File
@@ -2,6 +2,7 @@
// Reine Daten plus ein kleiner Browser-Lader; die Größe der Logos bestimmt allein die viewBox.
// Die Logos unten rechts kommen von der Community (logos/logo-asset.ts), hier steht keine fremde Marke.
import { msg } from '../i18n/i18n';
import { TONE_COLOR, type BrandId, type DetailsTone } from './config';
import aircardsSymbol from '../assets/marks/aircards-symbol.svg?raw';
@@ -11,7 +12,10 @@ export type MarkGroup = 'Logo';
export interface MarkDef {
id: Exclude<BrandId, 'none'>;
/** Gruppenname; zusammen mit dem Label der Text für `aria-label`/`title` („Logo – AirCard-IOS · Symbol“). */
/**
* Gruppenname; zusammen mit dem Label der Text für `aria-label`/`title` („Logo – AirCard-IOS · Symbol“).
* Beide sind deutsch markiert (`msg`) und werden erst beim Anzeigen mit `t()` übersetzt.
*/
group: MarkGroup;
label: string;
svg: string;
@@ -24,8 +28,8 @@ export interface MarkDef {
type MarkEntry = Omit<MarkDef, 'id'>;
const BRAND_TABLE: Record<Exclude<BrandId, 'none'>, MarkEntry> = {
'aircards-symbol': { group: 'Logo', label: 'AirCard-IOS · Symbol', svg: aircardsSymbol, tint: true, maxH: 100 },
'aircards-text': { group: 'Logo', label: 'AirCard-IOS · Text', svg: aircardsText, tint: true, maxH: 100 },
'aircards-symbol': { group: msg('Logo'), label: msg('AirCard-IOS · Symbol'), svg: aircardsSymbol, tint: true, maxH: 100 },
'aircards-text': { group: msg('Logo'), label: msg('AirCard-IOS · Text'), svg: aircardsText, tint: true, maxH: 100 },
};
export const BRAND_MARKS: readonly MarkDef[] = (
+4 -2
View File
@@ -1,5 +1,6 @@
// Kartenrenderer: zeichnet eine Karte in 1536 x 969-Koordinaten auf einen Canvas-Kontext.
// Vorschau, Vorlagen-Miniaturen, PNG-Download und Vorschaubild beim Speichern benutzen alle drawCard.
import { msg } from '../i18n/i18n';
import { loadLogoAsset } from '../logos/logo-asset';
import { TONE_COLOR, type CardConfig, type DetailsTone } from './config';
import { clampCrop, imageRect } from './crop';
@@ -180,7 +181,8 @@ export function drawCard(
): void {
// Die Überladungen beider Canvas-Typen lassen sich als Union nicht aufrufen; zur Laufzeit gleich.
const ctx = (canvas as HTMLCanvasElement).getContext('2d', { alpha: false });
if (!ctx) throw new Error('Canvas wird in diesem Browser nicht unterstützt.');
// Die Meldung geht deutsch markiert in den Fehler; der Editor zeigt sie mit `t(error.message)`.
if (!ctx) throw new Error(msg('Canvas wird in diesem Browser nicht unterstützt.'));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(canvas.width / CARD_W, canvas.height / CARD_H);
// Fotos werden stark verkleinert; die Standardqualität (low) zeigt dabei Treppenstufen.
@@ -221,7 +223,7 @@ export async function exportCard(
drawCard(canvas, config, assets);
return new Promise<Blob>((resolve, reject) => {
canvas.toBlob(
(blob) => (blob ? resolve(blob) : reject(new Error('Das Bild konnte nicht erzeugt werden.'))),
(blob) => (blob ? resolve(blob) : reject(new Error(msg('Das Bild konnte nicht erzeugt werden.')))),
opts.type,
opts.quality,
);