feat(logos): Datenschicht, API-Aufrufe und übersetzbare API-Fehler

- logos/logos.ts (Muster community/gallery.ts): CommunityLogo, toLogo, logoQueries (Beliebt/Neu, Admin-Filter Gemeldet und
  Ausgeblendet, Cursor), listLogos (nextCursor nur bei voller Seite von 24), getLogo (ungültige ID oder 404 -> null),
  myHearts (eigene Herzen einer Seite, ohne IDs keine Anfrage), listLogoReports (ohne Melder-Hash), loadLogoBlob.
- logos/logo-api.ts: uploadLogo, setHeart, reportLogo (ohne Anmeldung), deleteLogo, setLogoVisibility über apiPost.
  uploadLogo behandelt das Limit (429 tooManyLogos) wie publishCard als eindeutige Ablehnung (die hochgeladene Datei darf
  aufgeräumt werden); eine Antwort ohne id (und bei setHeart ohne Zustand und Zähler) ist 503 mit offenem Ausgang.
- account/appwrite.ts: IDS um logos, logoLikes, logoReports und logoBucket.
- community/api.ts: ApiError.serverCode (der Schlüssel aus {error, code}; code bleibt der HTTP-Status, message der deutsche
  Text). Client-Fehler tragen unavailable (auch 502/503/504 vom Proxy, vor dem Lesen des Bodys), generic, jwtUnsupported,
  alreadyPublished, tooManyPublications. Neu: unavailableError() und apiErrorText(err). Die deutschen Texte stehen einmal
  in api-messages.ts, die Vergleiche in publishCard laufen unverändert über err.message.
- i18n/api-messages.ts: API_MESSAGES (alle 33 Texte von MSG plus die Client-Codes generic und jwtUnsupported) und
  apiMessage: bekannter Code -> t(Text); unbekannt: Deutsch der Servertext, Englisch eine Meldung je Status (400, 403, 404,
  sonst allgemein). Der Code badRequest trägt auch die dynamischen Prüftexte des Dienstes, Deutsch zeigt dann den
  genaueren Servertext.
- i18n/en.ts: englische Texte zu allen API_MESSAGES.
- Tests: api-messages.test.ts lädt MSG aus deploy/api/common.js (createRequire) und prüft den Gleichlauf in beide
  Richtungen; logos.test.ts, logo-api.test.ts und neue Fälle in api.test.ts (der Helfer freshApi stellt die frische
  i18n-Instanz auf Deutsch). 156 neue Tests, die bisherigen unverändert grün (1285 gesamt).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 19:59:08 +00:00
1 parent fc9e5f5577
commit 3051b3b3f9
10 files changed
+1491 -21

No files matched your search

+40
View File
@@ -30,4 +30,44 @@ export const EN: Readonly<Record<string, string>> = {
// Fußzeile (shell/footer.ts)
'Inoffizielle Community-Seite für die App AirCard-iOS – kein Teil des Original-Projekts und nicht mit Apple verbunden. Logos und Motive stammen von Nutzern; alle Marken gehören ihren Inhabern.':
'Unofficial community site for the AirCard-iOS app – not part of the original project and not affiliated with Apple. Logos and artwork are uploaded by users; all trademarks belong to their owners.',
// API-Fehlertexte (i18n/api-messages.ts). Schlüssel und deutsche Texte stammen aus MSG (deploy/api/common.js); api-messages.test.ts hält sie gleich.
'Bitte melde dich an.': 'Please sign in.',
'Nicht gefunden.': 'Not found.',
'Diese Karte ist nicht (mehr) verfügbar.': 'This card is no longer available.',
'Dazu hast du keine Berechtigung.': 'You are not allowed to do that.',
'Nur für Admins.': 'Admins only.',
'Diese Datei gehört dir nicht.': 'This file doesn’t belong to you.',
'Bild nicht gefunden.': 'Image not found.',
'Das Bild muss eine PNG- oder WebP-Datei sein.': 'The image must be a PNG or WebP file.',
'Das Bild ist größer als 5 MB.': 'The image is larger than 5 MB.',
'Diese Karte ist schon veröffentlicht.': 'This card is already published.',
'Diese Bilder sind bereits veröffentlicht oder gesperrt.': 'These images are already published or blocked.',
'Das Bild hat nicht das Kartenformat.': 'The image doesn’t match the card format.',
'Diese Karte gehört nicht zu deinem Konto.': 'This card doesn’t belong to your account.',
'Du hast schon 20 Karten veröffentlicht. Zieh eine zurück, um eine neue zu veröffentlichen.':
'You’ve already published 20 cards. Withdraw one to publish a new one.',
'Zu viele Meldungen – bitte später nochmal.': 'Too many reports – please try again later.',
'Ungültige Anfrage.': 'Invalid request.',
'Ungültige Karten-ID.': 'Invalid card ID.',
'Bitte wähle einen gültigen Grund.': 'Please choose a valid reason.',
'Ungültige Angabe zur Sichtbarkeit.': 'Invalid visibility value.',
'Dieses Logo ist nicht (mehr) verfügbar.': 'This logo is no longer available.',
'Ungültige Logo-ID.': 'Invalid logo ID.',
'Bitte gib einen Namen an.': 'Please enter a name.',
'Das Logo muss eine PNG-Datei sein.': 'The logo must be a PNG file.',
'Das Logo ist größer als 1 MB.': 'The logo is larger than 1 MB.',
'Das Logo hat ungültige Maße (8 × 8 bis 720 × 250 Pixel).': 'The logo has invalid dimensions (8 × 8 to 720 × 250 pixels).',
'Diese Datei wird schon für ein Logo verwendet.': 'This file is already used for a logo.',
'Du hast schon 30 Logos hochgeladen. Lösche eins, um ein neues hochzuladen.':
'You’ve already uploaded 30 logos. Delete one to upload a new one.',
'Ungültige Angabe zum Herz.': 'Invalid heart value.',
'Ungültige Angabe zu „einfarbig“.': 'Invalid value for “single color”.',
'Die Anfrage ist zu groß.': 'The request is too large.',
'Nur application/json wird unterstützt.': 'Only application/json is supported.',
'Der Dienst ist gerade nicht erreichbar.': 'The service is currently unavailable.',
'Interner Fehler.': 'Internal error.',
'Etwas ist schiefgelaufen. Bitte nochmal versuchen.': 'Something went wrong. Please try again.',
'Anmeldung per Token ist auf dem Server ausgeschaltet – bitte den Betreiber informieren.':
'Token sign-in is turned off on the server – please let the site owner know.',
};