i18next-typesafe
v0.3.0
Published
Type-safe i18next wrapper for React with automatic type generation
Maintainers
Readme
i18next-typesafe
Type-safe i18next wrapper for React with automatic TypeScript type generation.
Features
- Full type safety - Autocomplete and type checking for translation keys
- Scoped translators - Create multiple translators scoped to specific prefixes
- Zero runtime overhead - Types are compile-time only
- Simple API - Uses familiar
react-i18nextunder the hood - CLI for type generation - Automatically generate types from JSON files
- Built-in validation - Find missing keys, unused translations, and cross-language mismatches
Installation
npm install i18next-typesafe
# or
yarn add i18next-typesafe
# or
pnpm add i18next-typesafePeer dependencies (you probably already have these):
npm install react react-i18next i18nextQuick Start
1. Generate Types
First, generate TypeScript types from your translation JSON files:
npx i18next-typesafe generate -i src/locales/en.json -o src/types/i18n.generated.tsThis creates a TranslationKey type from your translations:
// src/types/i18n.generated.ts (auto-generated)
export type TranslationKey =
| 'navigation.home'
| 'navigation.dashboard'
| 'form.newItem.pricing.title'
| 'form.newItem.pricing.startingPrice'
| 'general.save'
| 'general.cancel'
// ... all your keys
;2. Use in Your Components
import { useTypedTranslation, prefixes } from 'i18next-typesafe';
function MyComponent() {
// Create scoped translators
const [tPricing, tGeneral] = useTypedTranslation(
prefixes('form.newItem.pricing', 'general')
);
return (
<div>
<h2>{tPricing('title')}</h2> {/* ✅ Type-safe */}
<p>{tPricing('startingPrice')}</p> {/* ✅ Autocomplete */}
<button>{tGeneral('save')}</button> {/* ✅ Type checked */}
{/* ❌ TypeScript errors: */}
{/* tPricing('save') - 'save' is not in pricing */}
{/* tGeneral('title') - 'title' is not in general */}
</div>
);
}Usage Patterns
Pattern 1: Scoped Translators (Recommended)
Most components only need translations from specific sections:
const [tPricing, tProduct, tGeneral] = useTypedTranslation(
prefixes('form.newItem.pricing', 'form.newItem.product', 'general')
);
return (
<>
<h2>{tPricing('title')}</h2>
<p>{tProduct('name')}</p>
<button>{tGeneral('save')}</button>
</>
);Pattern 2: With Parameters
i18next supports interpolation - pass parameters as the second argument:
const [tForm] = useTypedTranslation(prefixes('form.newItem'));
// Translation: "Item will be moved from catalog {{from}} to catalog {{to}}"
return <p>{tForm('catalogChanged', { from: 'A', to: 'B' })}</p>;Pattern 3: Full + Scoped (For Dynamic Keys)
When you need both static type-safe keys and dynamic runtime keys:
const TRANSLATE_BASE = 'form.newItem.pricing.';
const [t, tPricing] = useTypedTranslation(
prefixes('', 'form.newItem.pricing')
);
return (
<>
{/* Dynamic keys with full translator */}
<h2>{t(TRANSLATE_BASE + 'title')}</h2>
{/* Type-safe keys with scoped translator */}
<p>{tPricing('startingPrice')}</p>
</>
);Pattern 4: Full Translator Only (Backward Compatible)
For maximum flexibility, use the full translator:
const [t] = useTypedTranslation(prefixes(''));
return <h2>{t('any.translation.key')}</h2>;Configuration
You can configure i18next-typesafe using a configuration file instead of CLI arguments. This is especially useful for complex setups or to ignore specific translation keys/blocks.
Create a .i18next-typesafe.json or i18next-typesafe.config.json file in your project root:
{
"input": "src/locales/en.json",
"output": "src/types/i18n.generated.ts",
"locales": "src/locales",
"source": "src",
"languages": ["en", "he", "fr"],
"validation": {
"ignoreKeys": [
"legacy.oldFeature.*",
"temp.debugging.message"
],
"ignoreBlocks": [
"experimental.features",
"deprecated"
]
}
}Configuration options:
input- Input JSON file path (default:src/locales/en.json)output- Output TypeScript file path (default:src/types/i18n.generated.ts)locales- Locales directory path (default:src/locales)source- Source code directory path (default:src)languages- Array of language codes (default:["en", "he", "fr"])watch- Watch mode for development (default:false)validation.ignoreKeys- Array of key patterns to ignore during validation (supports wildcards with*)validation.ignoreBlocks- Array of block prefixes to ignore during validation (supports wildcards with*)
CLI options take precedence over configuration file values.
You can also specify a custom config file path:
npx i18next-typesafe -c custom-config.json generateCLI Commands
Generate Types
npx i18next-typesafe generate [options]Options:
-c, --config <path>- Path to config file (global option)-i, --input <path>- Input JSON file (default:src/locales/en.json)-o, --output <path>- Output TypeScript file (default:src/types/i18n.generated.ts)-w, --watch- Watch mode for development
Examples:
# Basic usage
npx i18next-typesafe generate
# Custom paths via CLI
npx i18next-typesafe generate -i locales/en.json -o types/translations.ts
# Watch mode for development
npx i18next-typesafe generate --watch
# Use custom config file
npx i18next-typesafe -c my-config.json generateValidate Translations
Run All Validations
npx i18next-typesafe validate [options]Runs all validation checks: cross-language sync, unused blocks, and unused keys.
Options:
-l, --locales <path>- Locales directory (default:src/locales)-s, --source <path>- Source code directory (default:src)-i, --input <path>- Input JSON file (default:src/locales/en.json)--languages <langs>- Comma-separated language codes (default:en,he,fr)
Individual Validation Commands
Check cross-language synchronization:
npx i18next-typesafe validate:sync --languages en,he,frFinds keys that exist in one language but not in others.
Check for unused translation blocks:
npx i18next-typesafe validate:blocksFinds translation blocks/namespaces that aren't referenced in code via useTypedTranslation(prefixes('block.name')).
Check for unused individual keys:
npx i18next-typesafe validate:keysFinds individual translation keys that aren't used anywhere in the code.
Add to package.json
{
"scripts": {
"i18n:generate": "i18next-typesafe generate",
"i18n:watch": "i18next-typesafe generate --watch",
"i18n:validate": "i18next-typesafe validate",
"i18n:validate:sync": "i18next-typesafe validate:sync",
"i18n:validate:blocks": "i18next-typesafe validate:blocks",
"i18n:validate:keys": "i18next-typesafe validate:keys"
}
}Naming Convention
For validation commands to work correctly, follow this naming convention:
- ✅
t- full translator (detected by validation) - ✅
tPricing,tGeneral,tProduct- scoped translators (detected) - ❌
pricing,general- won't be detected
// ✅ GOOD - Will be detected by validate:keys
const [t] = useTypedTranslation(prefixes(''));
const [tPricing, tGeneral] = useTypedTranslation(prefixes('form.pricing', 'general'));
// ❌ BAD - Won't be detected
const [pricing, general] = useTypedTranslation(prefixes('form.pricing', 'general'));TypeScript Configuration
Make sure your tsconfig.json includes the generated types:
{
"compilerOptions": {
"strict": true
},
"include": [
"src/**/*",
"src/types/i18n.generated.ts"
]
}How It Works
- Type Generation: The CLI reads your translation JSON, flattens nested keys to dot notation, and generates a TypeScript union type
- Type Utilities: Generic TypeScript types extract valid keys for each prefix
- Runtime Hook: Wraps
react-i18next'suseTranslationwith type-safe scoped translators - Zero Overhead: All type checking happens at compile-time - no runtime cost
API Reference
useTypedTranslation(prefixes)
Creates type-safe translators scoped to specific prefixes.
Parameters:
prefixes- Array of prefix strings (use theprefixes()helper)- Empty string
''creates a full translator (any key) - Non-empty string creates a scoped translator (only keys under that prefix)
- Empty string
Returns: Array of translator functions matching the input prefixes
prefixes(...args)
Helper function to create the prefixes array with proper TypeScript inference.
Parameters:
...args- Prefix strings
Returns: Readonly array of prefix strings with literal types preserved
License
MIT
Contributing
Contributions welcome! Please open an issue or PR on GitHub.
