Second extraction pass: the strings the codemod could not see
`i18n:coverage` reported 100% while a hundred-odd strings rendered
English in every language. It was not wrong about what it measured: it
reads JSX text, and none of these were JSX text. They were toast
arguments, `confirmDialog({ title, confirmLabel })` props, `title=` and
`aria-label=` attributes, and template literals — every one built from an
expression the codemod cannot read.
176 source strings and 15 plural sets now go through t() and plural(),
translated into all nine languages. Where English put a word in a slot,
the sentence is spelled out per branch instead: `Filter ${verb}` became
"Filter saved" and "Filter created", because which word agrees with what,
and where it sits, is not a property English gets to decide for everyone.
Counts that were `${n} message${n === 1 ? "" : "s"}` are plural() calls,
so Russian and Ukrainian get three forms and Japanese and Chinese get the
one they actually have.
Two of the catalogue's own conventions were worth learning the hard way.
Plural entries are keyed on the English *other* form, not `one` — `one`
is a form English happens to have and Japanese does not. And a constant
table holding English that is translated at the render site is fine: the
literal is a key, not a leak.
Which is what the new check encodes. `scripts/i18n-literals.mjs` accepts
a string that is wrapped where it is written or is a catalogue key
somewhere, and refuses one that is neither — a string no catalogue can
translate, however many languages ship. It found twenty more than my own
sweep had, including the stale-folder toast seen in production. It runs
as part of `npm run i18n:check`.
Also fixed: the catalogue is now awaited before the first paint. The
tree is rebuilt when a catalogue lands, so components recover on their
own, but a string computed in an effect does not — a toast fired in that
gap is emitted in English and stays English. The wait costs nothing
visible, since the session bootstrap already shows a spinner and English
resolves immediately.
And the Japanese agenda title loses a space Japanese does not use:
"{date} からの予定" was written with the English habit of spacing around
a placeholder.
This commit is contained in:
@@ -884,10 +884,202 @@ export const catalog: Catalog = {
|
||||
"Saved {when}": "Gespeichert {when}",
|
||||
"Unsaved": "Nicht gespeichert",
|
||||
"Agenda from {date}": "Agenda ab {date}",
|
||||
|
||||
// ── Toasts, dialogs and computed labels (second extraction pass) ───
|
||||
"(no subject)": "(kein Betreff)",
|
||||
"(skipped: {actions})": "(übersprungen: {actions})",
|
||||
"Activate": "Aktivieren",
|
||||
"Add a contact or import a vCard file.": "Fügen Sie einen Kontakt hinzu oder importieren Sie eine vCard-Datei.",
|
||||
"Added to your calendar": "Zu Ihrem Kalender hinzugefügt",
|
||||
"All events in this calendar will be deleted.": "Alle Termine in diesem Kalender werden gelöscht.",
|
||||
"All occurrences": "Alle Termine der Serie",
|
||||
"An occurrence cannot be moved to another calendar on its own": "Ein einzelner Termin einer Serie kann nicht allein in einen anderen Kalender verschoben werden",
|
||||
"Anything signed in with this password stops working immediately.": "Alles, was mit diesem Passwort angemeldet ist, funktioniert sofort nicht mehr.",
|
||||
"App password revoked": "App-Passwort widerrufen",
|
||||
"Applies to every date in the series.": "Gilt für alle Termine der Serie.",
|
||||
"Applies to this date only.": "Gilt nur für diesen Termin.",
|
||||
"Apply to series": "Auf die Serie anwenden",
|
||||
"Applying filter to existing messages…": "Filter wird auf vorhandene Nachrichten angewendet…",
|
||||
"Attachments are still uploading": "Anhänge werden noch hochgeladen",
|
||||
"Calendar saved": "Kalender gespeichert",
|
||||
"Categorised as {name}": "Kategorie „{name}“ zugewiesen",
|
||||
"Category cleared": "Kategorie entfernt",
|
||||
"Change this event?": "Diesen Termin ändern?",
|
||||
"Choose a calendar": "Wählen Sie einen Kalender",
|
||||
"Choose an address book": "Wählen Sie ein Adressbuch",
|
||||
"Colour updated": "Farbe geändert",
|
||||
"Contact created": "Kontakt erstellt",
|
||||
"Contact deleted": "Kontakt gelöscht",
|
||||
"Contact saved": "Kontakt gespeichert",
|
||||
"Copied": "Kopiert",
|
||||
"Copy of {title}": "Kopie von {title}",
|
||||
"Could not cancel: {error}": "Abbrechen fehlgeschlagen: {error}",
|
||||
"Could not copy": "Kopieren fehlgeschlagen",
|
||||
"Could not copy the address": "Die Adresse konnte nicht kopiert werden",
|
||||
"Could not empty folder: {error}": "Ordner konnte nicht geleert werden: {error}",
|
||||
"Could not load source: {error}": "Quelltext konnte nicht geladen werden: {error}",
|
||||
"Could not mark as read: {error}": "Konnte nicht als gelesen markiert werden: {error}",
|
||||
"Could not save draft: {error}": "Entwurf konnte nicht gespeichert werden: {error}",
|
||||
"Could not save filter: {error}": "Filter konnte nicht gespeichert werden: {error}",
|
||||
"Could not save filters: {error}": "Filter konnten nicht gespeichert werden: {error}",
|
||||
"Could not send the receipt: {error}": "Die Lesebestätigung konnte nicht gesendet werden: {error}",
|
||||
"Could not sign in.": "Anmeldung fehlgeschlagen.",
|
||||
"Could not store image: {error}": "Bild konnte nicht gespeichert werden: {error}",
|
||||
"Could not update labels: {error}": "Labels konnten nicht aktualisiert werden: {error}",
|
||||
"Could not update the Scheduled folder: {error}": "Der Ordner „Geplant“ konnte nicht aktualisiert werden: {error}",
|
||||
"Could not update: {error}": "Aktualisierung fehlgeschlagen: {error}",
|
||||
"Create": "Erstellen",
|
||||
"Create an address book first": "Legen Sie zuerst ein Adressbuch an",
|
||||
"Custom colour removed": "Eigene Farbe entfernt",
|
||||
"Deactivate": "Deaktivieren",
|
||||
"Delete failed: {error}": "Löschen fehlgeschlagen: {error}",
|
||||
"Delete forever": "Endgültig löschen",
|
||||
"Delete script “{name}”?": "Skript „{name}“ löschen?",
|
||||
"Delete this event?": "Diesen Termin löschen?",
|
||||
"Delete this identity?": "Diese Absenderidentität löschen?",
|
||||
"Delete {name}?": "{name} löschen?",
|
||||
"Deleted": "Gelöscht",
|
||||
"Deletes the whole series. This cannot be undone.": "Löscht die gesamte Serie. Das lässt sich nicht rückgängig machen.",
|
||||
"Did you forget the attachment?": "Haben Sie den Anhang vergessen?",
|
||||
"Discard": "Verwerfen",
|
||||
"Discard this draft?": "Diesen Entwurf verwerfen?",
|
||||
"Edit calendar": "Kalender bearbeiten",
|
||||
"Edit event": "Termin bearbeiten",
|
||||
"Edit identity": "Absenderidentität bearbeiten",
|
||||
"Edit template": "Vorlage bearbeiten",
|
||||
"End must be after start": "Das Ende muss nach dem Beginn liegen",
|
||||
"Event duplicated": "Termin dupliziert",
|
||||
"Everything inside it goes too.": "Alles darin wird ebenfalls gelöscht.",
|
||||
"Filter created": "Filter erstellt",
|
||||
"Filter created — it will run on new mail": "Filter erstellt – er läuft ab jetzt für neue Nachrichten",
|
||||
"Filter saved": "Filter gespeichert",
|
||||
"Filter saved — it will run on new mail": "Filter gespeichert – er läuft ab jetzt für neue Nachrichten",
|
||||
"Filter saved, but applying it failed: {error}": "Filter gespeichert, aber das Anwenden ist fehlgeschlagen: {error}",
|
||||
"Filters saved": "Filter gespeichert",
|
||||
"Folder changed, but its filter rules could not be updated: {error}": "Ordner geändert, aber seine Filterregeln konnten nicht aktualisiert werden: {error}",
|
||||
"Folder created": "Ordner erstellt",
|
||||
"Folder deleted": "Ordner gelöscht",
|
||||
"Folder name": "Ordnername",
|
||||
"Folder name (use / for a subfolder, e.g. Work/Invoices)": "Ordnername (/ für einen Unterordner, z. B. Arbeit/Rechnungen)",
|
||||
"Folder name (use / for subfolders, e.g. Work/Invoices)": "Ordnername (/ für Unterordner, z. B. Arbeit/Rechnungen)",
|
||||
"Folder “{name}” created": "Ordner „{name}“ erstellt",
|
||||
"Forwarded": "Weitergeleitet",
|
||||
"Hand to the server, held until {when} (Ctrl+Enter)": "An den Server übergeben, zurückgehalten bis {when} (Strg+Enter)",
|
||||
"Hide": "Ausblenden",
|
||||
"Hide quoted text": "Zitierten Text ausblenden",
|
||||
"Hide this from the compose picker": "Beim Verfassen nicht anbieten",
|
||||
"Identity saved": "Absenderidentität gespeichert",
|
||||
"Images in signatures need the Files feature, which this account doesn't have.": "Bilder in Signaturen brauchen die Dateien-Funktion, die dieses Konto nicht hat.",
|
||||
"Invalid address: {address}": "Ungültige Adresse: {address}",
|
||||
"Invalid username or password.": "Benutzername oder Passwort ist falsch.",
|
||||
"It belongs to a change that was applied to this and all later occurrences, which the server will only edit as a whole. Apply to the entire series instead?": "Er gehört zu einer Änderung, die auf diesen und alle späteren Termine angewendet wurde und die der Server nur als Ganzes bearbeitet. Stattdessen auf die gesamte Serie anwenden?",
|
||||
"Label name": "Labelname",
|
||||
"Larger than {size} MB limit": "Größer als das Limit von {size} MB",
|
||||
"Message sent": "Nachricht gesendet",
|
||||
"Move failed: {error}": "Verschieben fehlgeschlagen: {error}",
|
||||
"Move “{name}”": "„{name}“ verschieben",
|
||||
"Moved": "Verschoben",
|
||||
"Network error. Please check your connection.": "Netzwerkfehler. Bitte prüfen Sie Ihre Verbindung.",
|
||||
"New all-day event on {date}": "Neuer ganztägiger Termin am {date}",
|
||||
"New event at {time}": "Neuer Termin um {time}",
|
||||
"New identity": "Neue Absenderidentität",
|
||||
"No Archive folder found. Create one named “Archive” first.": "Kein Archivordner gefunden. Legen Sie zuerst einen mit dem Namen „Archive“ an.",
|
||||
"No contacts yet": "Noch keine Kontakte",
|
||||
"No longer shared": "Nicht mehr freigegeben",
|
||||
"No matches": "Keine Treffer",
|
||||
"No new mail in your inbox.": "Keine neuen Nachrichten im Posteingang.",
|
||||
"No results": "Keine Ergebnisse",
|
||||
"Nothing here": "Nichts vorhanden",
|
||||
"Only Deleted Items and Junk Mail can be emptied.": "Nur Papierkorb und Spam lassen sich leeren.",
|
||||
"Please add at least one recipient": "Bitte geben Sie mindestens einen Empfänger an",
|
||||
"Please use an image under 512 KB for signatures.": "Bitte verwenden Sie für Signaturen ein Bild unter 512 KB.",
|
||||
"Preview": "Vorschau",
|
||||
"Read receipt sent": "Lesebestätigung gesendet",
|
||||
"Remove attachments that failed to upload first": "Entfernen Sie zuerst die Anhänge, deren Upload fehlgeschlagen ist",
|
||||
"Removed from calendar": "Aus dem Kalender entfernt",
|
||||
"Removes this date and leaves the rest of the series.": "Entfernt diesen Termin und lässt die übrige Serie bestehen.",
|
||||
"Rename address book": "Adressbuch umbenennen",
|
||||
"Rename category": "Kategorie umbenennen",
|
||||
"Replied": "Beantwortet",
|
||||
"Report spam (!)": "Als Spam melden (!)",
|
||||
"Response sent": "Antwort gesendet",
|
||||
"Revoke “{name}”?": "„{name}“ widerrufen?",
|
||||
"Script has errors": "Das Skript enthält Fehler",
|
||||
"Script is valid": "Das Skript ist gültig",
|
||||
"Script name is required": "Ein Skriptname ist erforderlich",
|
||||
"Script saved": "Skript gespeichert",
|
||||
"Send (Ctrl+Enter)": "Senden (Strg+Enter)",
|
||||
"Send anyway": "Trotzdem senden",
|
||||
"Send cancelled — the message is back in Drafts": "Senden abgebrochen – die Nachricht liegt wieder in den Entwürfen",
|
||||
"Send failed: {error}": "Senden fehlgeschlagen: {error}",
|
||||
"Send invites": "Einladungen senden",
|
||||
"Send scheduled for {when}": "Senden geplant für {when}",
|
||||
"Send without a subject?": "Ohne Betreff senden?",
|
||||
"Share “{name}”": "„{name}“ freigeben",
|
||||
"Sharing updated": "Freigabe aktualisiert",
|
||||
"Show": "Einblenden",
|
||||
"Show quoted text": "Zitierten Text einblenden",
|
||||
"Show this in the compose picker": "Beim Verfassen anbieten",
|
||||
"Sign out other sessions?": "Andere Sitzungen abmelden?",
|
||||
"Sign out others": "Andere abmelden",
|
||||
"Stop sharing “{name}”?": "Freigabe von „{name}“ aufheben?",
|
||||
"Switch to {theme}": "Zu {theme} wechseln",
|
||||
"Template": "Vorlage",
|
||||
"Template name": "Vorlagenname",
|
||||
"The contacts in it go too.": "Die enthaltenen Kontakte werden ebenfalls gelöscht.",
|
||||
"The default identity is always offered when composing": "Die Standardidentität wird beim Verfassen immer angeboten",
|
||||
"The new passwords don't match": "Die neuen Passwörter stimmen nicht überein",
|
||||
"The server scheduled this for {when}, not the time requested.": "Der Server hat dies für {when} geplant, nicht für die gewünschte Zeit.",
|
||||
"This date cannot be changed on its own": "Dieser Termin lässt sich nicht einzeln ändern",
|
||||
"This occurrence": "Nur dieser Termin",
|
||||
"Too many attempts. Please wait a few minutes and try again.": "Zu viele Versuche. Bitte warten Sie einige Minuten und versuchen Sie es erneut.",
|
||||
"Try another search.": "Versuchen Sie eine andere Suche.",
|
||||
"Try different keywords or filters.": "Versuchen Sie andere Suchbegriffe oder Filter.",
|
||||
"Two-factor authentication is off": "Zwei-Faktor-Authentifizierung ist aus",
|
||||
"Undo window: {seconds}s": "Rückgängig-Zeitfenster: {seconds}s",
|
||||
"You're all caught up": "Sie sind auf dem neuesten Stand",
|
||||
"Your browser refused the request: {error}": "Ihr Browser hat die Anfrage abgelehnt: {error}",
|
||||
"Your browser will ask whether to open mail links in ihasmail": "Ihr Browser wird fragen, ob Mail-Links in ihasmail geöffnet werden sollen",
|
||||
"Your message mentions an attachment, but nothing is attached.": "Ihre Nachricht erwähnt einen Anhang, aber es ist nichts angehängt.",
|
||||
"event": "Termin",
|
||||
"Hide password": "Passwort verbergen",
|
||||
"Show password": "Passwort anzeigen",
|
||||
"Settings reset to defaults": "Einstellungen auf Standard zurückgesetzt",
|
||||
"Removed. Mail links will open in whatever your browser falls back to.": "Entfernt. Mail-Links öffnen sich künftig in dem, worauf Ihr Browser zurückfällt.",
|
||||
"Edit rule": "Regel bearbeiten",
|
||||
"Address copied": "Adresse kopiert",
|
||||
"Search or create label": "Label suchen oder anlegen",
|
||||
"New label name": "Name des neuen Labels",
|
||||
"That folder no longer exists. Showing your inbox instead.": "Diesen Ordner gibt es nicht mehr. Stattdessen wird der Posteingang angezeigt.",
|
||||
"Labels updated": "Labels aktualisiert",
|
||||
"Unsubscribe message prepared — just hit Send": "Abmeldenachricht vorbereitet – klicken Sie einfach auf Senden",
|
||||
"Maximize": "Maximieren",
|
||||
"Full screen": "Vollbild",
|
||||
"Resize panes": "Bereichsgröße ändern",
|
||||
"QR code": "QR-Code",
|
||||
"Draft discarded": "Entwurf verworfen",
|
||||
"Draft saved": "Entwurf gespeichert",
|
||||
"Emptying folder…": "Ordner wird geleert…",
|
||||
"Nothing unread here": "Hier ist nichts ungelesen",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} Nachricht", other: "{n} Nachrichten" },
|
||||
"{n} selected": { one: "{n} ausgewählt", other: "{n} ausgewählt" },
|
||||
"{n} conversations": { one: "{n} Konversation", other: "{n} Konversationen" },
|
||||
"{n} messages will be permanently deleted.": { one: "{n} Nachricht wird endgültig gelöscht.", other: "{n} Nachrichten werden endgültig gelöscht." },
|
||||
"Signed out {n} other sessions": { one: "{n} andere Sitzung abgemeldet", other: "{n} andere Sitzungen abgemeldet" },
|
||||
"applied to {matched} of {n} messages": { one: "auf {matched} von {n} Nachricht angewendet", other: "auf {matched} von {n} Nachrichten angewendet" },
|
||||
"Move {n} messages to…": { one: "{n} Nachricht verschieben nach…", other: "{n} Nachrichten verschieben nach…" },
|
||||
"This permanently deletes the folder and its {n} messages.": { one: "Das löscht den Ordner und seine {n} Nachricht endgültig.", other: "Das löscht den Ordner und seine {n} Nachrichten endgültig." },
|
||||
"Added {n} contacts": { one: "{n} Kontakt hinzugefügt", other: "{n} Kontakte hinzugefügt" },
|
||||
"{n} people will lose access. The contacts in it are not affected.": { one: "{n} Person verliert den Zugriff. Die enthaltenen Kontakte sind nicht betroffen.", other: "{n} Personen verlieren den Zugriff. Die enthaltenen Kontakte sind nicht betroffen." },
|
||||
"Imported {n} contacts": { one: "{n} Kontakt importiert", other: "{n} Kontakte importiert" },
|
||||
"Saved for this date. {names} apply to the whole series and were left unchanged.": { one: "Für diesen Termin gespeichert. {names} gilt für die gesamte Serie und blieb unverändert.", other: "Für diesen Termin gespeichert. {names} gelten für die gesamte Serie und blieben unverändert." },
|
||||
"{n} people will lose access. Events in it are not affected.": { one: "{n} Person verliert den Zugriff. Die enthaltenen Termine sind nicht betroffen.", other: "{n} Personen verlieren den Zugriff. Die enthaltenen Termine sind nicht betroffen." },
|
||||
"{n} messages could not be deleted": { one: "{n} Nachricht konnte nicht gelöscht werden", other: "{n} Nachrichten konnten nicht gelöscht werden" },
|
||||
"({n} deleted first)": { one: "({n} zuvor gelöscht)", other: "({n} zuvor gelöscht)" },
|
||||
"Marked {n} messages as read": { one: "{n} Nachricht als gelesen markiert", other: "{n} Nachrichten als gelesen markiert" },
|
||||
"in {n} folders": { one: "in {n} Ordner", other: "in {n} Ordnern" },
|
||||
"Deleted {n} messages": { one: "{n} Nachricht gelöscht", other: "{n} Nachrichten gelöscht" },
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user