conventions-i18n
GitHubStella项目的国际化规范,定义使用use-intl、多语言文件管理、翻译流程及术语标准。
Trigger Scenarios
Install
npx skills add stella/stella --skill conventions-i18n -g -y
SKILL.md
Frontmatter
{
"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
- Add or modify keys in
apps/web/src/i18n/langs/en.json. - Add corresponding translations to all target language files
found in
apps/web/src/i18n/langs/(every.jsonfile excepten.json). Write natural, idiomatic translations; avoid literal/robotic phrasing. - Run
bun run i18n:syncfromapps/web. This synchronizes locale structure, regenerates typed messages, and updates generated glossary output. Never editmessages.gen.tsor generated terminology tables by hand. - Run
bun run i18n:checkfromapps/web. Typecheck does not regenerate or validate the catalogs, so a clean typecheck is not evidence that i18n is in sync. - 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).
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):
- 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. - Add it to
glossary.json, never only toTERMINOLOGY.md(the.mdis generated). Runbun run i18n:sync(fromapps/web) to regenerate the tables. - 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, skVec/veci/vecou, etasi/asja/asjad. A base form alone misses inflected drift. - Pick the right ban field.
forbiddenis concept-gated: it fires only where the English source names the concept (or akeyTriggerspath matches), which is what stops a common word from false-firing everywhere.forbiddenAlwaysdrops 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. - Apply consistently.
bun run i18n:checkfails on new forbidden renderings; fix the translations, or — only for genuine pre-existing debt — grandfather withi18n-lint <dir> --write-baselineand 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.
Version History
-
dd81665
Current 2026-08-16 07:09
更新i18n同步命令为bun run i18n:sync,引入glossary.json作为术语权威来源,新增禁止使用'entity'的强制规则。
- 85792bd 2026-07-24 16:12


