feat(i18n): Community, Startseite und Meine Karten auf Englisch
Task 11: alle sichtbaren Texte von community/, pages/home-page.ts, pages/community-page.ts, pages/cards-page.ts und pages/format.ts
(Texte, aria-label, title, Platzhalter, Alt-Texte, Rückfragen, Meldungen) gehen durch t()/msg(); en.ts bekommt 82 Einträge (214 → 296).
- Konstanten bleiben deutsche Strings per msg() und gehen beim Anzeigen durch t() (publish.ts, publish-dialog.ts, gallery.ts und die Seiten);
geworfene Fehler tragen weiter den deutschen Text, damit die Vergleiche gehen. Funktionen, die Text für Nutzer liefern, übersetzen beim
Zurückgeben (validatePublishForm, publishFailureText, formatDate, reportsLabel). Template-Literale sind Platzhalter geworden ({max},
{reason}, {name}, {title}, {query}, {count}); der deutsche Text bleibt Zeichen für Zeichen derselbe.
- API-Fehler: jeder ApiError wird mit apiErrorText() angezeigt (Veröffentlichen-Dialog, Detailseite, Meine Karten), nach dem Code des Dienstes
statt nach dem deutschen Servertext. Logik, die err.message vergleicht (publishCard, 404 beim Zurückziehen), bleibt unverändert.
- Bekannte Reste aus Task 10 behoben: LOGIN_NEEDED ging in publish-dialog.ts, community-page.ts und cards-page.ts ohne t() an die Oberfläche;
die Meldungen zu Bild lesen/Pixelgrenze/fehlendem Logo (cards-page.ts, GERMAN_MESSAGES in publish-dialog.ts) erscheinen jetzt übersetzt;
die Zahl der Meldungen in der Logo-Infozeile („3 reports“).
- pages/format.ts: formatDate liefert Deutsch dd.mm.yyyy und Englisch „Oct 4, 2026“ (Intl.DateTimeFormat en-US), reportsLabel „1 report“ /
„3 reports“. „Herausgeber“ heißt als Bank „Issuer / bank“.
- logos/prepare-logo.ts: der Helfer problem() ist weg, die sechs Stellen schreiben new LogoError(t(MELDUNG)) selbst (gleiches Verhalten);
so sieht der neue Prüfer unten jede Verwendung einer Konstante.
- i18n/coverage.test.ts: Die Heuristik für deutsche Literale deckt jetzt die ganze Oberfläche ab (account/, card/, community/, editor/, logos/,
pages/, shell/, main.ts, i18n/api-messages.ts). Sie erkennt zusätzlich Wörter mit Klammer („Ausgeblendet (gemeldet)“) und nimmt rel und
Namen in Klammerschreibweise (headers['X-Aircard-Jwt']) aus. Acht begründete Ausnahmen kommen dazu (zwei AppwriteException-Meldungen,
vier interne Logo-Fehler, der Sprachknopf in der Zielsprache). Neu: ein Prüfer über den Syntaxbaum, der verlangt, dass jede Konstante
mit msg() nur in t(), new Error(), Vergleichen, case und Listen/Mengen vorkommt (auch über Importe und Umbenennungen); ohne Ausnahmen.
- Tests: format.test.ts (beide Sprachen), Englisch-Blöcke in publish-dialog, gallery-grid, home-page, community-page, cards-page und
logo-info (Texte, Fehlerwege, „Publishing failed – This card is already published.“, kein deutscher Wörterbuchtext auf der Seite). Die
Hilfen texts()/germanLeft() liegen jetzt in i18n/english-check.ts (vorher in english-ui.test.ts). Der bestehende Englisch-Test in
cards-page.test.ts sucht den Knopf „Download“ statt „Herunterladen“.
- Vitest 53 Dateien, 1812 Tests grün (vorher 1779), tsc und Build sauber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1ff1873b27
commit
7e66cd0f0f
20 files changed
+1164
-211
No files matched your search
@@ -663,6 +663,10 @@ describe('Admin', () => {
|
||||
await mount(logo({ reportCount: 1, ownerId: 'u1' }));
|
||||
expect(buttons()).toEqual(['Report', 'Delete', 'Hide']);
|
||||
expect(reasons()).toEqual(['Spam']);
|
||||
expect(reportCount()).toBe('1 report');
|
||||
row = logo({ hidden: true, reportCount: 3 });
|
||||
info.update();
|
||||
expect(reportCount()).toBe('3 reports');
|
||||
row = logo({ hidden: true, reportCount: 1 });
|
||||
info.update();
|
||||
expect(buttons()).toEqual(['Delete', 'Unhide']);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const LOGO_SOURCE_MAX_BYTES = 5_242_880;
|
||||
/** Dieselbe Grenze wie beim Foto im Editor (40 Megapixel): `decodeImage` prüft sie nach dem Laden der Maße, bevor es dekodiert. */
|
||||
export const LOGO_SOURCE_MAX_PIXELS = MAX_IMAGE_PIXELS;
|
||||
|
||||
// Die Meldungen (Spec § 4 und § 11); übersetzt wird beim Auslösen, siehe `problem`.
|
||||
// Die Meldungen (Spec § 4 und § 11); übersetzt wird beim Auslösen: `new LogoError(t(MELDUNG))`.
|
||||
export const LOGO_TYPE_MESSAGE = msg('Bitte ein PNG-, JPG-, WebP- oder SVG-Bild wählen.');
|
||||
export const LOGO_SIZE_MESSAGE = msg('Die Datei ist größer als 5 MB.');
|
||||
export const LOGO_PIXELS_MESSAGE = msg('Das Bild ist zu groß (max. 40 Megapixel).');
|
||||
@@ -54,11 +54,6 @@ export interface Size {
|
||||
/** Ein Fehler mit fertigem, übersetztem Text für die Oberfläche; jeder andere Fehler beim Verarbeiten gilt als „nicht lesbar“. */
|
||||
class LogoError extends Error {}
|
||||
|
||||
/** Ein `LogoError` mit der Meldung in der Sprache der Seite. */
|
||||
function problem(text: string): LogoError {
|
||||
return new LogoError(t(text));
|
||||
}
|
||||
|
||||
const RASTER_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
|
||||
const SVG_TYPE = 'image/svg+xml';
|
||||
|
||||
@@ -127,9 +122,9 @@ export function hasTransparency(data: Uint8ClampedArray): boolean {
|
||||
*/
|
||||
export function planLogo(data: Uint8ClampedArray, w: number, h: number): { box: Box; size: Size } {
|
||||
const box = trimBox(data, w, h);
|
||||
if (!box) throw problem(LOGO_EMPTY_MESSAGE);
|
||||
if (!box) throw new LogoError(t(LOGO_EMPTY_MESSAGE));
|
||||
const size = fitWithin(box.w, box.h, LOGO_MAX_W, LOGO_MAX_H);
|
||||
if (size.w < LOGO_MIN || size.h < LOGO_MIN) throw problem(LOGO_SMALL_MESSAGE);
|
||||
if (size.w < LOGO_MIN || size.h < LOGO_MIN) throw new LogoError(t(LOGO_SMALL_MESSAGE));
|
||||
return { box, size };
|
||||
}
|
||||
|
||||
@@ -208,7 +203,7 @@ function newCanvas(w: number, h: number): { canvas: HTMLCanvasElement; ctx: Canv
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
if (!ctx) throw problem(LOGO_READ_MESSAGE);
|
||||
if (!ctx) throw new LogoError(t(LOGO_READ_MESSAGE));
|
||||
return { canvas, ctx };
|
||||
}
|
||||
|
||||
@@ -228,7 +223,7 @@ async function decode(blob: Blob): Promise<CardImage> {
|
||||
try {
|
||||
return await decodeImage(blob);
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message === PIXELS_MESSAGE) throw problem(LOGO_PIXELS_MESSAGE);
|
||||
if (err instanceof Error && err.message === PIXELS_MESSAGE) throw new LogoError(t(LOGO_PIXELS_MESSAGE));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -248,7 +243,7 @@ async function drawSvg(file: File): Promise<Drawn> {
|
||||
|
||||
function toPng(canvas: HTMLCanvasElement): Promise<Blob> {
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(problem(LOGO_READ_MESSAGE))), 'image/png');
|
||||
canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new LogoError(t(LOGO_READ_MESSAGE)))), 'image/png');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,6 +268,6 @@ export async function prepareLogo(file: File): Promise<PreparedLogo> {
|
||||
hasTransparency: hasTransparency(out.ctx.getImageData(0, 0, size.w, size.h).data),
|
||||
};
|
||||
} catch (err) {
|
||||
throw err instanceof LogoError ? err : problem(LOGO_READ_MESSAGE);
|
||||
throw err instanceof LogoError ? err : new LogoError(t(LOGO_READ_MESSAGE));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user