Internationalization conventions for Stella. Apply when adding or modifying user-facing strings.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add stella/stella --skill conventions-i18n --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Conventions I18n?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/stella-conventions-i18n-stella)More formats (shields.io, HTML) on the badges page.
---
name: conventions-i18n
description: "Internationalization conventions for Stella. Apply when adding or modifying user-facing strings."
---
# i18n Conventions
Internationalization conventions for Stella. Apply when adding or
modifying user-facing strings.
## Stack
`use-intl` for runtime.
## Supported Languages
en is the source language. Check `apps/web/src/i18n/langs/` for
the current list of target languages (add translations to every
`.json` file found there).
## Translation Flow
1. Add or modify keys in
`apps/web/src/i18n/langs/en.json`.
2. Add corresponding translations to **all target language files**
found in `apps/web/src/i18n/langs/` (every `.json` file
except `en.json`). Write natural, idiomatic translations;
avoid literal/robotic phrasing.
3. Run `bun run i18n:sync` from `apps/web`. This synchronizes locale
structure, regenerates typed messages, and updates generated glossary
output. Never edit `messages.gen.ts` or generated terminology tables by
hand.
4. Run `bun run i18n:check` from `apps/web`. Typecheck does not regenerate or
validate the catalogs, so a clean typecheck is not evidence that i18n is in
sync.
5. Read the rendered sentence in context, including interpolation and plural
branches. Passing key parity is not proof that a translation is natural or
that placeholders remain grammatically valid.
## Key Naming
**Prefer generic, reusable keys over feature-specific ones.**
Before adding any new i18n key, search `en.json` for an existing
key with the same or similar wording (e.g., `common.filter`,
`common.sort`, `common.columns`). Reuse `common.*` or shared
namespace keys instead of creating feature-scoped duplicates
like `billing.expenses.deleteExpense`. Feature-specific keys
are only justified when the wording truly differs from the
generic version (e.g., a confirmation message that mentions
the resource by name).
When a translation key crosses indirection through a constant, map, prop, or
helper return type, type it with `TranslationKey` from
`apps/web/src/i18n/types` instead of `string`. Missing or stale keys must fail
typecheck at the point where they are stored.
Prefer complete translatable sentences over fragments assembled in JSX. Keep
interpolation variables semantic (`{documentName}`, not `{value}`), use ICU
plural/select branches for grammatical variation, and never concatenate
translated fragments whose word order differs by locale.
**Never call anything an "entity" in user-facing copy**, in English or as a
calque (`entita`, `Entität`, `entidad`, `entité`, `entidade`, `encja`, ...).
It is the database's word for a row and means nothing to a lawyer. Name the
concrete thing the string is about — document, file, folder, task, matter —
and fall back to "item" only when the string genuinely covers all of them. In
anonymization, what the detector finds is a **match**, not an entity. The
`item` concept in `glossary.json` enforces this with `forbiddenAlways`, so it
fires on the English source too, not only on translations.
Key naming, pluralization, and style rules are documented
in `apps/web/src/i18n/TERMINOLOGY.md`.
## Terminology (glossary)
Canonical legal/domain terms live in
`apps/web/src/i18n/glossary.json` (the source of truth);
`glossary-gen` renders them into the tables in `TERMINOLOGY.md`.
The `i18n-lint` checker enforces them: per concept it flags a
translation that uses a `forbidden` rendering when the English
source is about that concept (concept-gated, so a common word only
fires in the right context).
When introducing a NEW concept (or changing a preferred term):
1. **Research it first.** Confirm the sector-standard term and the
synonyms to avoid in **each** language against authoritative
sources (IATE/EU terminology, national legal glossaries,
established legal-tech usage) — do not guess. Record the
rationale in the concept's `note`.
2. **Add it to `glossary.json`**, never only to `TERMINOLOGY.md`
(the `.md` is generated). Run `bun run i18n:sync` (from
`apps/web`) to regenerate the tables.
3. **Account for declensions/inflections.** The lint matches
forbidden terms whole-word (English source triggers also match
their regular plural), so list the actual inflected, declined,
and compound forms a translator might use — e.g. de
`Sache`/`Sachen`/`Mandatsdaten`, sk `Vec`/`veci`/`vecou`, et
`asi`/`asja`/`asjad`. A base form alone misses inflected drift.
4. **Pick the right ban field.** `forbidden` is concept-gated: it fires
only where the English source names the concept (or a `keyTriggers`
path matches), which is what stops a common word from false-firing
everywhere. `forbiddenAlways` drops that gate, for wording that is
wrong in every context. Use it when a concept-gated ban would go
blind the moment someone rewords the English source, and only once
you have checked that no legitimate use of the word exists in that
locale.
5. **Apply consistently.** `bun run i18n:check` fails on new
forbidden renderings; fix the translations, or — only for
genuine pre-existing debt — grandfather with
`i18n-lint <dir> --write-baseline` and flag for native review.
## Landing (marketing) catalogs
The landing app has its own catalogs (`apps/landing/src/i18n/messages/`)
with the same glossary enforcement plus marketing-specific rules —
register per locale, the identity phrase, hero structure differences,
meta-string length budgets, cognate policy, and brand handling. Before
translating landing strings, read
`apps/landing/src/i18n/TRANSLATION.md`; it explains the intent behind
each rule so translations stay coherent rather than merely passing the
checks. Landing gates: `bun run i18n:sync` and `bun run i18n:check`
from `apps/landing`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!