One sentence in a kit screen is wrong for your product. You want to change it, and you do not want to own the other four hundred.
i18n({
supportedLocales: ["en", "es"],
messages: {
en: { "auth/sign_in.title": "Welcome back" },
es: { "auth/sign_in.title": "Bienvenido de nuevo" },
},
}),That is it. Every key you did not mention keeps flowing from the package.
Why it is a merge
The layer order is the mechanism, serving a second language is the wider setup, and writing a locale is what to do when one sentence is not enough.
A lookup walks layers per key, not per catalog:
- Your catalog for the resolved locale
- Your catalog for the project default
- The kit’s translation for the resolved locale
- Every composed capability’s English for the resolved locale
- That English for the project default
Per key is the whole design. If the walk stopped at the first catalog that existed, providing one Spanish sentence would mean providing all of them — and every adopter would become a fork on the day they changed a word.
So a typo fix or a new locale in the kit reaches you as a package upgrade, for every key you did not override.
Overriding is always allowed. Declaring is not
Two different acts, and only one is refused.
| Act | Allowed |
|---|---|
| Overriding a key the kit ships, in any locale | Always |
| Declaring a new key under a kit domain, through the capability contract | Refused, naming the key and the domain |
Your own new keys go under your own domain — app/nav.settings is yours to declare, and auth/sign_in.title is not.
That is the same partitioning rule as the table prefix and the error domain, and it is what makes merge order something nobody has to think about.
Override the default locale too, usually
An override in one locale and not the other is a screen that changes wording when somebody switches language.
If you are changing a sentence because your product calls something by a different name, that is true in every language you serve — so override it in each.
Where the kit’s own sentences live
Worth knowing, because it explains what an upgrade brings you.
A capability holds its own domain in every language it is written in. Email carries its own translations beside its English, because the send Worker is a separate deploy that has to be built with them.
The i18n package holds what no capability can: the error taxonomy, whose domains are not capability names, and the screens, which are copied into your repository rather than imported.
Either way it ships in a package. Neither is ever copied into your tree.
Passing a whole locale object is the fork
You can. And it is the thing to understand before you do: hand over a complete locale and you have taken ownership of every sentence in it, including the ones that will be corrected next month.
That is why no eject command is offered for this, and none is needed. The fork is available and it is spelled out rather than hidden behind a flag.
No markup in a catalog value
Ever. An email’s subject and its plain-text part are precompiled with escaping off, so a value substituted there is substituted verbatim.
The HTML body escapes what it renders, and interpolated parameters are escaped with the sentence that carries them.
If you need emphasis, it belongs in the template rather than in the string.
Check it worked
- The overridden sentence appears, in every locale you overrode
- A neighboring key on the same screen still comes from the package
- An upgrade that changes a kit sentence you did not override brings the new one through