I18n source of truth
Skill cristianorj22/arthus-harness/plugins/i18n/claude/skills/i18n-source-of-truth
Multi-locale invariants — single source-of-truth file, strict parity, glossary discipline, no string concatenation. Use when adding any user-facing string or modifying locale files.From its SKILL.md
npx -y skills add cristianorj22/arthus-harness --skill i18n-source-of-truthAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.7 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
i18n — source-of-truth pattern
Generic version of the invariants behind any multi-locale product. Adapt the source locale and glossary terms to your domain.
The five rules
- One file is the source of truth. Convention: the locale that ships first (commonly
en.jsonor your product's primary language). Every new key starts there. - Strict parity. Every key in the source must exist in every other locale. No orphan keys anywhere.
- No partial translations. Adding a key only to 2 of 3 locales breaks the invariant. All locales or none.
- Pre-commit validator.
scripts/check_i18n.mjs(run vianpm run i18n:check) compares key trees and fails on drift. Without this, parity won't survive a sprint. - No string concatenation. Use ICU placeholders:
t('msg', { count }), nevert('a') + count + t('b'). Word order varies by language.
Source-of-truth file
The default source is pt-BR.json. Override via:
harness.config.json:{ "i18n": { "source": "en.json" } }- Or env var:
I18N_SOURCE=en.json npm run i18n:check
Key naming
Flat with dots — <domain>.<surface>.<element>[.<modifier>]:
auth.login.button.submit
auth.login.error.invalidCredentials
dashboard.metric.totalRevenue
checkout.review.headline
Don't use:
- camelCase keys (
loginSubmit) — keep flat with dots. - generic keys (
error1,text2) — must be domain-tagged.
ICU pluralization
Use {count, plural, ...}:
{
"cart.items": "{count, plural, =0 {your cart is empty} one {# item} other {# items}}",
"results.count": "{count, plural, =0 {no results} one {# result} other {# results}}"
}
Each language has its own plural categories. Brazilian Portuguese uses one for 1 and other for everything else; languages like Russian or Polish have additional categories. The translator handles that — your job is to provide ICU placeholders, not English-shaped strings.
Intl formatting
Numbers, currencies, dates — never hardcode. Always Intl:
new Intl.NumberFormat(locale, { style: 'currency', currency: 'BRL' }).format(850);
new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(date);
Always pass the detected locale, not a hardcoded one:
const locale = i18n.language || navigator.language || DEFAULT_LOCALE;
Component contract
import { useTranslation } from 'react-i18next';
function ProductCard({ product }: { product: Product }) {
const { t, i18n } = useTranslation();
const formatPrice = new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency: 'USD',
});
return (
<article>
<h3>{product.title}</h3> {/* dynamic data — not translated */}
<p>{t('product.price', { price: formatPrice.format(product.price) })}</p>
<p>{t('product.stock', { count: product.stock })}</p> {/* ICU plural */}
</article>
);
}
User-generated content (titles, names, descriptions) is NOT translated — only the static UI shell.
Glossary
Domain jargon must be documented and followed verbatim. Google-translate-style literal renderings break trust ("Anfitrião" → "host owner" reads alien to a Brazilian Portuguese user).
TODO(arthus-harness): document your glossary here. Example structure below.
| Concept | en | pt-BR | es |
|---|---|---|---|
| (your term) | (en form) | (pt-BR form) | (es form) |
Source it once — typically in Docs/design-system/brand/glossary.md — and reference from this skill.
Common mistakes
- ❌
t('prefix') + ' ' + count + ' ' + t('suffix'). Use ICU placeholders. - ❌ Adding a key only to the source locale (when wired). The validator catches; better to mirror immediately.
- ❌ Hardcoding currency prefix (
"$","R$") in JSX. UseIntl.NumberFormat. - ❌ Translating user-generated content. Don't — that breaks correctness.
- ❌ Paraphrasing glossary terms. The glossary IS the contract.
Validator
Run before commit:
npm run i18n:check
Skips silently when src/locales/ doesn't exist. When wired, fails on:
- Missing keys in non-source locales.
- Orphan keys (in non-source but not in source).
See also
Docs/design-system/patterns/i18n.md— full pattern + library setup~/.claude/rules/i18n-source-of-truth.md— user's global rule (if applicable)
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.