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:
@@ -853,10 +853,202 @@ export const catalog: Catalog = {
|
||||
"Saved {when}": "Opgeslagen {when}",
|
||||
"Unsaved": "Niet opgeslagen",
|
||||
"Agenda from {date}": "Agenda vanaf {date}",
|
||||
|
||||
// ── Toasts, dialogs and computed labels (second extraction pass) ───
|
||||
"(no subject)": "(geen onderwerp)",
|
||||
"(skipped: {actions})": "(overgeslagen: {actions})",
|
||||
"Activate": "Activeren",
|
||||
"Add a contact or import a vCard file.": "Voeg een contact toe of importeer een vCard-bestand.",
|
||||
"Added to your calendar": "Toegevoegd aan uw agenda",
|
||||
"All events in this calendar will be deleted.": "Alle afspraken in deze agenda worden verwijderd.",
|
||||
"All occurrences": "Alle herhalingen",
|
||||
"An occurrence cannot be moved to another calendar on its own": "Eén herhaling kan niet los naar een andere agenda worden verplaatst",
|
||||
"Anything signed in with this password stops working immediately.": "Alles wat met dit wachtwoord is aangemeld, werkt meteen niet meer.",
|
||||
"App password revoked": "App-wachtwoord ingetrokken",
|
||||
"Applies to every date in the series.": "Geldt voor elke datum in de reeks.",
|
||||
"Applies to this date only.": "Geldt alleen voor deze datum.",
|
||||
"Apply to series": "Op de reeks toepassen",
|
||||
"Applying filter to existing messages…": "Filter wordt toegepast op bestaande berichten…",
|
||||
"Attachments are still uploading": "Bijlagen worden nog geüpload",
|
||||
"Calendar saved": "Agenda opgeslagen",
|
||||
"Categorised as {name}": "Categorie ‘{name}’ toegekend",
|
||||
"Category cleared": "Categorie verwijderd",
|
||||
"Change this event?": "Deze afspraak wijzigen?",
|
||||
"Choose a calendar": "Kies een agenda",
|
||||
"Choose an address book": "Kies een adresboek",
|
||||
"Colour updated": "Kleur gewijzigd",
|
||||
"Contact created": "Contact aangemaakt",
|
||||
"Contact deleted": "Contact verwijderd",
|
||||
"Contact saved": "Contact opgeslagen",
|
||||
"Copied": "Gekopieerd",
|
||||
"Copy of {title}": "Kopie van {title}",
|
||||
"Could not cancel: {error}": "Annuleren mislukt: {error}",
|
||||
"Could not copy": "Kopiëren mislukt",
|
||||
"Could not copy the address": "Het adres kon niet worden gekopieerd",
|
||||
"Could not empty folder: {error}": "Map legen mislukt: {error}",
|
||||
"Could not load source: {error}": "De bron kon niet worden geladen: {error}",
|
||||
"Could not mark as read: {error}": "Markeren als gelezen mislukt: {error}",
|
||||
"Could not save draft: {error}": "Concept opslaan mislukt: {error}",
|
||||
"Could not save filter: {error}": "Filter opslaan mislukt: {error}",
|
||||
"Could not save filters: {error}": "Filters opslaan mislukt: {error}",
|
||||
"Could not send the receipt: {error}": "De leesbevestiging kon niet worden verzonden: {error}",
|
||||
"Could not sign in.": "Aanmelden mislukt.",
|
||||
"Could not store image: {error}": "De afbeelding kon niet worden opgeslagen: {error}",
|
||||
"Could not update labels: {error}": "Labels bijwerken mislukt: {error}",
|
||||
"Could not update the Scheduled folder: {error}": "De map Gepland kon niet worden bijgewerkt: {error}",
|
||||
"Could not update: {error}": "Bijwerken mislukt: {error}",
|
||||
"Create": "Aanmaken",
|
||||
"Create an address book first": "Maak eerst een adresboek aan",
|
||||
"Custom colour removed": "Eigen kleur verwijderd",
|
||||
"Deactivate": "Deactiveren",
|
||||
"Delete failed: {error}": "Verwijderen mislukt: {error}",
|
||||
"Delete forever": "Definitief verwijderen",
|
||||
"Delete script “{name}”?": "Script ‘{name}’ verwijderen?",
|
||||
"Delete this event?": "Deze afspraak verwijderen?",
|
||||
"Delete this identity?": "Deze afzender verwijderen?",
|
||||
"Delete {name}?": "{name} verwijderen?",
|
||||
"Deleted": "Verwijderd",
|
||||
"Deletes the whole series. This cannot be undone.": "Verwijdert de hele reeks. Dit kan niet ongedaan worden gemaakt.",
|
||||
"Did you forget the attachment?": "Bent u de bijlage vergeten?",
|
||||
"Discard": "Weggooien",
|
||||
"Discard this draft?": "Dit concept weggooien?",
|
||||
"Edit calendar": "Agenda bewerken",
|
||||
"Edit event": "Afspraak bewerken",
|
||||
"Edit identity": "Afzender bewerken",
|
||||
"Edit template": "Sjabloon bewerken",
|
||||
"End must be after start": "Het einde moet na het begin liggen",
|
||||
"Event duplicated": "Afspraak gedupliceerd",
|
||||
"Everything inside it goes too.": "Alles wat erin zit gaat mee.",
|
||||
"Filter created": "Filter aangemaakt",
|
||||
"Filter created — it will run on new mail": "Filter aangemaakt — het draait op nieuwe berichten",
|
||||
"Filter saved": "Filter opgeslagen",
|
||||
"Filter saved — it will run on new mail": "Filter opgeslagen — het draait op nieuwe berichten",
|
||||
"Filter saved, but applying it failed: {error}": "Filter opgeslagen, maar toepassen is mislukt: {error}",
|
||||
"Filters saved": "Filters opgeslagen",
|
||||
"Folder changed, but its filter rules could not be updated: {error}": "Map gewijzigd, maar de filterregels konden niet worden bijgewerkt: {error}",
|
||||
"Folder created": "Map aangemaakt",
|
||||
"Folder deleted": "Map verwijderd",
|
||||
"Folder name": "Mapnaam",
|
||||
"Folder name (use / for a subfolder, e.g. Work/Invoices)": "Mapnaam (gebruik / voor een submap, bijv. Werk/Facturen)",
|
||||
"Folder name (use / for subfolders, e.g. Work/Invoices)": "Mapnaam (gebruik / voor submappen, bijv. Werk/Facturen)",
|
||||
"Folder “{name}” created": "Map ‘{name}’ aangemaakt",
|
||||
"Forwarded": "Doorgestuurd",
|
||||
"Hand to the server, held until {when} (Ctrl+Enter)": "Naar de server gestuurd en vastgehouden tot {when} (Ctrl+Enter)",
|
||||
"Hide": "Verbergen",
|
||||
"Hide quoted text": "Geciteerde tekst verbergen",
|
||||
"Hide this from the compose picker": "Niet aanbieden bij het opstellen",
|
||||
"Identity saved": "Afzender opgeslagen",
|
||||
"Images in signatures need the Files feature, which this account doesn't have.": "Afbeeldingen in handtekeningen vereisen de functie Bestanden, die dit account niet heeft.",
|
||||
"Invalid address: {address}": "Ongeldig adres: {address}",
|
||||
"Invalid username or password.": "Gebruikersnaam of wachtwoord is onjuist.",
|
||||
"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?": "Het hoort bij een wijziging die op deze en alle latere herhalingen is toegepast, en die de server alleen in zijn geheel bewerkt. In plaats daarvan op de hele reeks toepassen?",
|
||||
"Label name": "Labelnaam",
|
||||
"Larger than {size} MB limit": "Groter dan de limiet van {size} MB",
|
||||
"Message sent": "Bericht verzonden",
|
||||
"Move failed: {error}": "Verplaatsen mislukt: {error}",
|
||||
"Move “{name}”": "‘{name}’ verplaatsen",
|
||||
"Moved": "Verplaatst",
|
||||
"Network error. Please check your connection.": "Netwerkfout. Controleer uw verbinding.",
|
||||
"New all-day event on {date}": "Nieuwe hele dag durende afspraak op {date}",
|
||||
"New event at {time}": "Nieuwe afspraak om {time}",
|
||||
"New identity": "Nieuwe afzender",
|
||||
"No Archive folder found. Create one named “Archive” first.": "Geen archiefmap gevonden. Maak er eerst een met de naam ‘Archive’.",
|
||||
"No contacts yet": "Nog geen contacten",
|
||||
"No longer shared": "Niet langer gedeeld",
|
||||
"No matches": "Geen overeenkomsten",
|
||||
"No new mail in your inbox.": "Geen nieuwe berichten in uw postvak IN.",
|
||||
"No results": "Geen resultaten",
|
||||
"Nothing here": "Hier is niets",
|
||||
"Only Deleted Items and Junk Mail can be emptied.": "Alleen de prullenbak en ongewenste e-mail kunnen worden geleegd.",
|
||||
"Please add at least one recipient": "Voeg ten minste één ontvanger toe",
|
||||
"Please use an image under 512 KB for signatures.": "Gebruik voor handtekeningen een afbeelding onder 512 KB.",
|
||||
"Preview": "Voorbeeld",
|
||||
"Read receipt sent": "Leesbevestiging verzonden",
|
||||
"Remove attachments that failed to upload first": "Verwijder eerst de bijlagen die niet geüpload konden worden",
|
||||
"Removed from calendar": "Uit de agenda verwijderd",
|
||||
"Removes this date and leaves the rest of the series.": "Verwijdert deze datum en laat de rest van de reeks staan.",
|
||||
"Rename address book": "Adresboek hernoemen",
|
||||
"Rename category": "Categorie hernoemen",
|
||||
"Replied": "Beantwoord",
|
||||
"Report spam (!)": "Als spam melden (!)",
|
||||
"Response sent": "Antwoord verzonden",
|
||||
"Revoke “{name}”?": "‘{name}’ intrekken?",
|
||||
"Script has errors": "Het script bevat fouten",
|
||||
"Script is valid": "Het script is geldig",
|
||||
"Script name is required": "Een scriptnaam is verplicht",
|
||||
"Script saved": "Script opgeslagen",
|
||||
"Send (Ctrl+Enter)": "Verzenden (Ctrl+Enter)",
|
||||
"Send anyway": "Toch verzenden",
|
||||
"Send cancelled — the message is back in Drafts": "Verzenden geannuleerd — het bericht staat weer bij Concepten",
|
||||
"Send failed: {error}": "Verzenden mislukt: {error}",
|
||||
"Send invites": "Uitnodigingen verzenden",
|
||||
"Send scheduled for {when}": "Verzenden gepland voor {when}",
|
||||
"Send without a subject?": "Verzenden zonder onderwerp?",
|
||||
"Share “{name}”": "‘{name}’ delen",
|
||||
"Sharing updated": "Delen bijgewerkt",
|
||||
"Show": "Tonen",
|
||||
"Show quoted text": "Geciteerde tekst tonen",
|
||||
"Show this in the compose picker": "Aanbieden bij het opstellen",
|
||||
"Sign out other sessions?": "Andere sessies afmelden?",
|
||||
"Sign out others": "Andere afmelden",
|
||||
"Stop sharing “{name}”?": "Delen van ‘{name}’ stoppen?",
|
||||
"Switch to {theme}": "Overschakelen naar {theme}",
|
||||
"Template": "Sjabloon",
|
||||
"Template name": "Sjabloonnaam",
|
||||
"The contacts in it go too.": "De contacten erin gaan mee.",
|
||||
"The default identity is always offered when composing": "De standaardafzender wordt altijd aangeboden bij het opstellen",
|
||||
"The new passwords don't match": "De nieuwe wachtwoorden komen niet overeen",
|
||||
"The server scheduled this for {when}, not the time requested.": "De server heeft dit gepland voor {when}, niet voor de gevraagde tijd.",
|
||||
"This date cannot be changed on its own": "Deze datum kan niet los worden gewijzigd",
|
||||
"This occurrence": "Deze herhaling",
|
||||
"Too many attempts. Please wait a few minutes and try again.": "Te veel pogingen. Wacht een paar minuten en probeer het opnieuw.",
|
||||
"Try another search.": "Probeer een andere zoekopdracht.",
|
||||
"Try different keywords or filters.": "Probeer andere zoekwoorden of filters.",
|
||||
"Two-factor authentication is off": "Tweefactorauthenticatie staat uit",
|
||||
"Undo window: {seconds}s": "Tijd om ongedaan te maken: {seconds} s",
|
||||
"You're all caught up": "U bent helemaal bij",
|
||||
"Your browser refused the request: {error}": "Uw browser heeft het verzoek geweigerd: {error}",
|
||||
"Your browser will ask whether to open mail links in ihasmail": "Uw browser vraagt of e-maillinks in ihasmail moeten worden geopend",
|
||||
"Your message mentions an attachment, but nothing is attached.": "Uw bericht noemt een bijlage, maar er is niets bijgevoegd.",
|
||||
"event": "afspraak",
|
||||
"Hide password": "Wachtwoord verbergen",
|
||||
"Show password": "Wachtwoord tonen",
|
||||
"Settings reset to defaults": "Instellingen teruggezet naar de standaardwaarden",
|
||||
"Removed. Mail links will open in whatever your browser falls back to.": "Verwijderd. E-maillinks worden geopend in waar uw browser op terugvalt.",
|
||||
"Edit rule": "Regel bewerken",
|
||||
"Address copied": "Adres gekopieerd",
|
||||
"Search or create label": "Label zoeken of aanmaken",
|
||||
"New label name": "Naam van het nieuwe label",
|
||||
"That folder no longer exists. Showing your inbox instead.": "Die map bestaat niet meer. Uw postvak IN wordt getoond.",
|
||||
"Labels updated": "Labels bijgewerkt",
|
||||
"Unsubscribe message prepared — just hit Send": "Afmeldbericht klaargezet — u hoeft alleen op Verzenden te klikken",
|
||||
"Maximize": "Maximaliseren",
|
||||
"Full screen": "Volledig scherm",
|
||||
"Resize panes": "Vensterdelen verslepen",
|
||||
"QR code": "QR-code",
|
||||
"Draft discarded": "Concept weggegooid",
|
||||
"Draft saved": "Concept opgeslagen",
|
||||
"Emptying folder…": "Map wordt geleegd…",
|
||||
"Nothing unread here": "Hier is niets ongelezen",
|
||||
},
|
||||
plurals: {
|
||||
"{n} messages": { one: "{n} bericht", other: "{n} berichten" },
|
||||
"{n} selected": { one: "{n} geselecteerd", other: "{n} geselecteerd" },
|
||||
"{n} conversations": { one: "{n} gesprek", other: "{n} gesprekken" },
|
||||
"{n} messages will be permanently deleted.": { one: "{n} bericht wordt definitief verwijderd.", other: "{n} berichten worden definitief verwijderd." },
|
||||
"Signed out {n} other sessions": { one: "{n} andere sessie afgemeld", other: "{n} andere sessies afgemeld" },
|
||||
"applied to {matched} of {n} messages": { one: "toegepast op {matched} van {n} bericht", other: "toegepast op {matched} van {n} berichten" },
|
||||
"Move {n} messages to…": { one: "{n} bericht verplaatsen naar…", other: "{n} berichten verplaatsen naar…" },
|
||||
"This permanently deletes the folder and its {n} messages.": { one: "Dit verwijdert de map en het {n} bericht erin definitief.", other: "Dit verwijdert de map en de {n} berichten erin definitief." },
|
||||
"Added {n} contacts": { one: "{n} contact toegevoegd", other: "{n} contacten toegevoegd" },
|
||||
"{n} people will lose access. The contacts in it are not affected.": { one: "{n} persoon verliest toegang. De contacten erin blijven ongemoeid.", other: "{n} personen verliezen toegang. De contacten erin blijven ongemoeid." },
|
||||
"Imported {n} contacts": { one: "{n} contact geïmporteerd", other: "{n} contacten geïmporteerd" },
|
||||
"Saved for this date. {names} apply to the whole series and were left unchanged.": { one: "Opgeslagen voor deze datum. {names} geldt voor de hele reeks en is ongewijzigd gelaten.", other: "Opgeslagen voor deze datum. {names} gelden voor de hele reeks en zijn ongewijzigd gelaten." },
|
||||
"{n} people will lose access. Events in it are not affected.": { one: "{n} persoon verliest toegang. De afspraken erin blijven ongemoeid.", other: "{n} personen verliezen toegang. De afspraken erin blijven ongemoeid." },
|
||||
"{n} messages could not be deleted": { one: "{n} bericht kon niet worden verwijderd", other: "{n} berichten konden niet worden verwijderd" },
|
||||
"({n} deleted first)": { one: "({n} eerst verwijderd)", other: "({n} eerst verwijderd)" },
|
||||
"Marked {n} messages as read": { one: "{n} bericht als gelezen gemarkeerd", other: "{n} berichten als gelezen gemarkeerd" },
|
||||
"in {n} folders": { one: "in {n} map", other: "in {n} mappen" },
|
||||
"Deleted {n} messages": { one: "{n} bericht verwijderd", other: "{n} berichten verwijderd" },
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user