@safekit/i18n
v0.5.0
Published
A lightweight type-safe i18n library with interpolation support and namespace scoping
Downloads
285
Maintainers
Readme
@safekit/i18n
⚠️ This package is in active development. Expect breaking changes between versions.
A lightweight, type-safe internationalization (i18n) library for TypeScript applications with support for interpolation and namespace scoping.
Features
- 🔒 Type-safe: Full TypeScript support with compile-time key validation
- 🔧 Interpolation: Support for parameterized translations with type checking
- 📦 Namespace scoping: Organize translations by namespace for better maintainability
- 🪶 Lightweight: Zero dependencies and minimal runtime overhead
- 🎯 IntelliSense: Full autocomplete support for translation keys and parameters
- 📁 TypeScript-first: Works with TypeScript (
as const) for maximum type safety - 🌍 Multi-language: Dynamic locale loading with preserved type safety
Installation
npm install @safekit/i18n
yarn add @safekit/i18n
bun add @safekit/i18ncreateTranslator
The main function for creating a translator with type-safe translation keys and interpolation.
import { createTranslator } from '@safekit/i18n';
const translations = {
"welcome": "Welcome!",
"greeting": "Hello {{name}}!",
"tasks.count": "You have {{count}} tasks"
} as const;
const t = createTranslator(translations);
// ✅ Type-safe usage
t("welcome"); // "Welcome!"
t("greeting", { name: "Alice" }); // "Hello Alice!"
t("tasks.count", { count: 5 }); // "You have 5 tasks"
// ❌ Compile-time errors (with TypeScript translations)
t("invalid.key"); // Error: invalid key
t("greeting"); // Error: missing required parameter 'name'
t("greeting", { wrongParam: "test" }); // Error: invalid parametergetFixedT
Create a scoped translator for a specific namespace to avoid repeating prefixes.
import { getFixedT } from '@safekit/i18n';
const translations = {
"user.greeting": "Hello {{name}}!",
"user.welcome": "Welcome {{firstName}} {{lastName}}!",
"form.validation.required": "{{field}} is required",
"form.validation.email": "Please enter a valid email"
} as const;
// Create scoped translators
const tUser = getFixedT(translations, "user");
const tForm = getFixedT(translations, "form.validation");
// Use without namespace prefix
tUser("greeting", { name: "Sarah" }); // "Hello Sarah!"
tUser("welcome", { firstName: "Emma", lastName: "Smith" }); // "Welcome Emma Smith!"
tForm("required", { field: "Password" }); // "Password is required"
tForm("email"); // "Please enter a valid email"Multi-language Support
For dynamic translation loading with multiple locales:
// Define the schema that all translations must satisfy
export type TranslationSchema = {
"app.title": string;
"user.greeting": string;
"user.welcome": string;
"tasks.count": string;
};
// Define each translation
export const enUS = {
"app.title": "Task Manager",
"user.greeting": "Hello {{name}}!",
"user.welcome": "Welcome {{firstName}} {{lastName}}!",
"tasks.count": "You have {{count}} tasks"
} as const satisfies TranslationSchema;
export const esES = {
"app.title": "Gestor de Tareas",
"user.greeting": "¡Hola {{name}}!",
"user.welcome": "¡Bienvenido {{firstName}} {{lastName}}!",
"tasks.count": "Tienes {{count}} tareas"
} as const satisfies TranslationSchema;
// Supported locales
export type SupportedLocale = 'en-US' | 'es-ES' | 'fr-FR' | 'zh-CN';
// Async loader functions (simulating dynamic imports)
const translationLoaders = {
'en-US': async () => enUS,
'es-ES': async () => esES,
'fr-FR': async () => frFR,
'zh-CN': async () => zhCN,
} as const;
/**
* Dynamically loads translations for the specified locale with preserved literal types
*/
export const getTranslations = async (locale: SupportedLocale) => {
const loader = translationLoaders[locale];
return await loader();
};
// Export the type for static usage
export type Translation = typeof enUS;
// Usage
async function setupI18n(locale: SupportedLocale) {
const translations = await getTranslations(locale);
const t = createTranslator(translations);
t("user.greeting", { name: "Maria" });
// en-US: "Hello Maria!"
// es-ES: "¡Hola Maria!"
}API Reference
createTranslator(translations, options?)
Creates a translator function with type-safe key validation and interpolation support.
Options:
silent?: boolean- Disable warning logs (default: auto-detect based on NODE_ENV)
getFixedT(translations, namespace, options?)
Creates a scoped translator for a specific namespace, allowing shorter key names.
Options:
- Same as
createTranslator
Fallback Behavior
- Missing keys: Returns the key itself as a string (never undefined/null)
- With
$defaultValue: Returns the provided default value - Warnings: Logged in development, silent in production (unless overridden)
// @ts-expect-error - runtime behavior
t("missing.key"); // Returns "missing.key" (with warning in dev)
t("missing.key", { $defaultValue: "Fallback" }); // Returns "Fallback"
// Disable warnings
const t = createTranslator(translations, { silent: true });Workflows
For comprehensive guides on translation workflows, see Translation Workflows.
Examples
See the examples directory for comprehensive usage examples:
examples/ts/- TypeScript translations (recommended when starting out)examples/json-to-ts-codegen/- JSON → TypeScript code generation workflow (for large teams)examples/ts-to-json-codegen/- TypeScript → JSON code generation workflowexamples/json-direct/- Direct JSON imports (reference only - not recommended)
Code Generation
For teams using different translation workflows:
- JSON → TypeScript Code Generation - Convert JSON translations to TypeScript
- TypeScript → JSON Code Generation - Convert TypeScript translations to JSON
Contributing
See CONTRIBUTING.md for contribution guidelines.
License
MIT © safekit
