i18next internationalization patterns for the Flux project. Flat keys, type-safe namespaces, on-demand loading, Intl-based formatting, and i18next-cli toolchain. Use when adding translation keys, creating namespaces, using useTranslation, formatting numbers/dates/durations, working with pluralization, running i18n extraction/validation, or debugging CLI issues. Triggers on tasks involving i18n, translations, localization, language switching, locale files, or i18next-cli.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: i18next description: i18next internationalization patterns for the Flux project. Flat keys, type-safe namespaces, on-demand loading, Intl-based formatting, and i18next-cli toolchain. Use when adding translation keys, creating namespaces, using useTranslation, formatting numbers/dates/durations, working with pluralization, running i18n extraction/validation, or debugging CLI issues. Triggers on tasks involving i18n, translations, localization, language switching, locale files, or i18next-cli.
i18next
Type-safe i18n with flat keys, on-demand namespace loading, Intl-based formatting, and i18next-cli for automated key management.
Architecture
- Shared Config:
apps/web/src/i18n/config.ts—defaultNS,fallbackLng,keySeparator, language list shared by runtime + CLI - Runtime Config:
apps/web/src/i18n/index.ts— i18next init, backend loader, language detector, custom formatters - CLI Config:
apps/web/i18next.config.ts— i18next-cli extraction and validation settings - Types:
apps/web/src/i18n/i18next.d.ts— module augmentation for type-safet();defaultNS/keySeparatorinferred from shared config - Locales:
apps/web/src/locales/{en-US,zh-CN}/{namespace}.json - Loading:
i18next-resources-to-backenddynamic import. Thecommonnamespace is preloaded at init vians: ["common"]. Route-scoped namespaces (e.g.auth) are preloaded via TanStack Routerloader(e.g._auth.tsx). Other namespaces are loaded on demand byuseTranslation("ns")withuseSuspense: true(caught by TanStack Router's per-route Suspense boundary fromdefaultPendingComponent)
apps/web/
├── i18next.config.ts # CLI config (extraction, validation)
└── src/
├── i18n/
│ ├── config.ts # shared i18n constants (runtime + CLI + types)
│ ├── index.ts # runtime init + custom formatters
│ └── i18next.d.ts # type augmentation (update when adding namespace)
└── locales/
├── en-US/ # source of truth for types
│ ├── common.json # default namespace
│ ├── auth.json
│ ├── ai.json
│ └── dify.json
└── zh-CN/ # must have same keys as en-US (except plural variants)
i18next-cli
Automated key extraction, unused key detection, and multi-language sync. See references/cli.md for full configuration and command details.
Commands
Run from apps/web/:
pnpm i18n # extract keys from code, remove unused keys, sync languages
pnpm i18n:ci # CI mode — fails if translation files need updating
pnpm i18n:status # translation health report by namespace/language
pnpm i18n:sync # sync secondary languages against primary (en-US)
pnpm i18n:lint # detect hardcoded strings that should be translated
CLI-Compatible Code Patterns
CRITICAL — the CLI uses SWC static AST analysis. It can only extract t() calls with string literals. Violating these rules causes keys to be misidentified or deleted.
// ✅ CLI extracts correctly — string literal
t("nav.home")
// ❌ CLI CANNOT extract — variable
t(someVariable)
// ❌ CLI CANNOT trace namespace — t passed as function parameter
const helper = (t: TFunction) => t("key")
Rules to keep CLI happy:
- Always use string literals in
t()calls — nevert(variable)ort(computedKey) - Keep
t()calls insideuseTranslation()scope — the CLI tracesuseTranslation("ns")to determine which namespace a key belongs to. Iftis passed as a parameter to a utility function outside the component, the CLI loses namespace tracking and assigns keys tocommon(default) - When a utility function needs translated strings, pass the strings not the
tfunction:
// ❌ WRONG — CLI cannot trace t's namespace
const getLabel = (t: TFunction) => t("my.key");
// ✅ RIGHT — t() called inside component scope, plain strings passed out
const { t } = useTranslation("ai");
const label = getLabel({ myKey: t("my.key") });
Key Naming Rules
CRITICAL — keys are flat strings, NOT nested paths (keySeparator: false).
{ "nav.home": "Home", "nav.dashboard": "Dashboard" }
- Use
category.itemgrouping within a namespace:signIn.email,validation.passwordMin - Use camelCase:
emptyState,apiMessage - Plurals use
_one/_othersuffixes:eventsCount_one,eventsCount_other - Never prefix keys with namespace name —
auth.jsonkeys must not start withauth. - Never use nested JSON —
{ "nav": { "home": "..." } }is wrong
Adding a New Namespace
- Create
locales/en-US/{ns}.jsonandlocales/zh-CN/{ns}.json - If the namespace is used in the root layout (outside route components, e.g. in
__root.tsx,Header,AuthBrandPanel), add it to thensarray ini18n/index.tsfor preloading. Route-level namespaces do NOT need this —useSuspense: true+ TanStack Router's per-route Suspense handles them automatically. - Add import + resource entry in
i18next.d.ts:
import type settings from "../locales/en-US/settings.json";
// inside CustomTypeOptions.resources:
settings: typeof settings;
- Run
pnpm i18nto validate extraction
Adding Keys to Existing Namespace
- Add key to
locales/en-US/{ns}.json(source of truth) - Add key to
locales/zh-CN/{ns}.json - JSON keys must be alphabetically sorted (enforced by Biome
useSortedKeys) - JSON must use tab indentation (enforced by Biome)
- Run
pnpm i18n:cito verify — or just writet("newKey")in code and runpnpm i18nto auto-generate
Usage Patterns
// React component — namespace via hook
const { t } = useTranslation("auth");
t("signIn.email");
// Default namespace (common)
const { t } = useTranslation();
t("nav.home");
// With interpolation
t("welcome", { name: "Alice" }); // "Welcome Alice"
// With count (auto plural resolution)
t("items", { count: 5 }); // selects _one or _other based on CLDR
Pluralization
English needs _one + _other. Chinese only needs _other (CLDR rules).
// en-US
{ "items_one": "{{count}} item", "items_other": "{{count}} items" }
// zh-CN
{ "items_other": "{{count}} 个项目" }
Call with base key — i18next resolves the suffix automatically:
t("items", { count: 5 });
Formatting (built-in Intl API)
{
"count": "{{val, number}}",
"price": "{{val, currency(USD)}}",
"date": "{{val, datetime}}",
"elapsed": "{{val, duration}}"
}
Custom duration formatter in index.ts — accepts milliseconds, outputs locale-aware seconds.
Rules
- Always use string literal
t()calls — CLI cannot extract dynamic keys - Keep
t()insideuseTranslation()scope — pass translated strings to helpers, not thetfunction - Never prefix keys with namespace name — namespace is already the file
- Keep JSON keys sorted alphabetically —
pnpm checkenforces this - zh-CN must match en-US keys — except
_one(Chinese doesn't need it) - Always pass raw values to formatters — let i18next handle conversion
- Run
pnpm i18n:cibefore committing — or let CI catch it
More by lyzno1
View allTurborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries. Use when user: configures tasks/workflows/pipelines, creates packages, sets up monorepo, shares code between apps, runs changed/affected packages, debugs cache, or has apps/packages directories.
Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences.
Zustand state management patterns for the Flux project. Slice pattern, strict selectors, and app-local store definitions. Use when creating new zustand stores, adding slices, writing selectors, integrating stores with React components, or refactoring client-side state management. Triggers on tasks involving zustand, store creation, state management, selectors, or client UI state.
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
