@i18n-bakery/core
v1.0.9
Published
The Head Chef. The mastermind managing logic, recipes (keys), and ensuring consistency across every dish. Framework-agnostic excellence.
Maintainers
Readme
🥖 @i18n-bakery/core
"The Head Chef" - The mastermind managing logic, recipes (keys), and ensuring consistency across every dish. Framework-agnostic excellence.
The Core Package is the kitchen line. It orchestrates the entire translation process, handles pluralization logic (counting loaves), processes message formats, and manages the menu (keys). Zero dependencies, pure TypeScript, and built with Clean Architecture principles.
📦 Installation
npm install @i18n-bakery/core
# or
pnpm add @i18n-bakery/core
# or
yarn add @i18n-bakery/core🚀 Quick Start
Basic Setup (The Starter Recipe)
import { initI18n, t } from "@i18n-bakery/core";
// Initialize the bakery
initI18n({
locale: "en",
fallbackLocale: "en",
loader: async (locale, namespace) => {
// Load your translation files
return import(`./locales/${locale}/${namespace}.json`);
},
});
// Start translating!
const greeting = t("home.welcome", "Welcome to our bakery!");
console.log(greeting); // → "Welcome to our bakery!"With Auto-Save (Self-Rising Dough)
import { initI18n, t, JSONFileSaver } from "@i18n-bakery/core";
initI18n({
locale: "en",
fallbackLocale: "en",
saveMissing: true, // Enable auto-save
saver: new JSONFileSaver("./locales"), // Where to save
loader: async (locale, namespace) => {
return import(`./locales/${locale}/${namespace}.json`);
},
});
// This will automatically create the file if it doesn't exist
t("profile.greeting", "Hello, {{name}}!", { name: "Baker" });
// → Creates: ./locales/en/profile.json🎯 Core Features
1. Translation Engine (The Oven)
The heart of i18n-bakery - handles all translation logic:
import { t } from "@i18n-bakery/core";
// Simple translation
t("home.title", "Welcome");
// With variables
t("greeting", "Hello, {{name}}!", { name: "World" });
// With nested variables
t("user.info", "User: {{user.name}} ({{user.email}})", {
user: { name: "John", email: "[email protected]" },
});
// With namespace
t("auth:login.button", "Sign In");2. Pluralization (Counting Loaves)
Support for multiple pluralization strategies:
i18next-Style (Suffix)
import { initI18n, t, addTranslations } from "@i18n-bakery/core";
initI18n({
locale: "en",
pluralizationStrategy: "suffix", // default
});
addTranslations("en", "common", {
apple: "apple",
apple_plural: "apples",
apple_0: "no apples",
});
t("apple", { count: 0 }); // → "no apples"
t("apple", { count: 1 }); // → "apple"
t("apple", { count: 5 }); // → "apples"CLDR-Style (100+ Languages)
initI18n({
locale: "ar", // Arabic
pluralizationStrategy: "cldr",
});
addTranslations("ar", "common", {
apple_zero: "لا توجد تفاحات",
apple_one: "تفاحة واحدة",
apple_two: "تفاحتان",
apple_few: "{{count}} تفاحات",
apple_many: "{{count}} تفاحة",
apple_other: "{{count}} تفاحة",
});
t("apple", { count: 0 }); // → "لا توجد تفاحات"
t("apple", { count: 1 }); // → "تفاحة واحدة"
t("apple", { count: 2 }); // → "تفاحتان"
t("apple", { count: 5 }); // → "5 تفاحات"3. ICU MessageFormat (The Artisan Touch)
Industry-standard message formatting:
initI18n({
locale: "en",
messageFormat: "icu", // Enable ICU
});
// Plural
t("cart.items", "{count, plural, =0 {no items} one {# item} other {# items}}", {
count: 3,
}); // → "3 items"
// Select (gender)
t(
"notification",
"{gender, select, male {He} female {She} other {They}} liked your post",
{ gender: "female" }
); // → "She liked your post"
// Selectordinal (1st, 2nd, 3rd)
t(
"ranking",
"You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}",
{ place: 1 }
); // → "You finished 1st"
// Nested patterns
t(
"complex",
"{gender, select, male {He has {count, plural, one {# item} other {# items}}} female {She has {count, plural, one {# item} other {# items}}}}",
{ gender: "male", count: 5 }
); // → "He has 5 items"4. Plugin System (Custom Recipes)
Extend functionality with plugins:
import {
initI18n,
t,
NumberFormatPlugin,
CapitalizePlugin,
} from "@i18n-bakery/core";
initI18n({
locale: "en-US",
plugins: [new NumberFormatPlugin(), new CapitalizePlugin()],
});
// Number formatting
t("price", "Total: {amount|currency:USD}", { amount: 1234.56 });
// → "Total: $1,234.56"
t("views", "{count|compact} views", { count: 1500000 });
// → "1.5M views"
t("discount", "Save {percent|percent}!", { percent: 25 });
// → "Save 25.00%!"
// Text transformations
addTranslations("en", "common", {
greeting: "hello world",
});
t("greeting_upper"); // → "HELLO WORLD"
t("greeting_capitalize"); // → "Hello world"
t("greeting_title"); // → "Hello World"
t("greeting_lower"); // → "hello world"5. Advanced Key Parsing (The Filing System)
Hierarchical key organization:
// Simple: namespace.key
t("home.title", "Welcome");
// → File: locales/en/home.json
// → Property: title
// With directory: namespace:directory.key
t("orders:meal.title", "Pizza Menu");
// → File: locales/en/orders/meal.json
// → Property: title
// Deep nesting: app:features:orders:meal.component.title
t("app:features:orders:meal.component.title", "Order Pizza");
// → File: locales/en/app/features/orders/meal/component.json
// → Property: title6. Translation Variants (Multiple Recipes)
Same key, different variable signatures:
// Variant 1: Just the bread
t('bread.title', '{{bread}}', { bread: 'Croissant' });
// Variant 2: Bread with price
t('bread.title', '{{bread}} - ${{price}}', { bread: 'Croissant', price: 4.50 });
// Both stored in the same file:
{
"title": {
"variants": {
"bread": {
"value": "{{bread}}",
"variables": ["bread"],
"autoGenerated": true,
"timestamp": 1733532610000
},
"bread_price": {
"value": "{{bread}} - ${{price}}",
"variables": ["bread", "price"],
"autoGenerated": true,
"timestamp": 1733532615000
}
}
}
}7. File Structure Options (Nested or Flat)
Choose your preferred JSON structure:
import { JSONFileSaver } from "@i18n-bakery/core";
// Nested structure (default) - hierarchical objects
const nestedSaver = new JSONFileSaver("./locales", "nested");
// Result: { "home": { "title": "Welcome" } }
// Flat structure - dot notation keys
const flatSaver = new JSONFileSaver("./locales", "flat");
// Result: { "home.title": "Welcome" }🏗️ Architecture
Built with Clean Architecture principles:
Domain Layer (Interfaces/Ports)
Pure TypeScript interfaces that define contracts:
// Translation loading
interface Loader {
load(locale: Locale, namespace: Namespace): Promise<TranslationMap | null>;
}
// Translation saving
interface TranslationSaver {
save(
locale: Locale,
namespace: Namespace,
key: Key,
value: string
): Promise<void>;
}
// Variable formatting
interface Formatter {
interpolate(text: string, vars?: Record<string, any>): string;
}
// And many more...Adapters Layer (Implementations)
Concrete implementations of the interfaces:
MemoryStore- In-memory translation storageJSONFileSaver- Save translations to JSON filesConsoleSaver- Log missing translations to consoleMustacheFormatter- Simple{{variable}}interpolationICUMessageFormatter- Full ICU MessageFormat supportDefaultKeyParser- Parse hierarchical keysCLDRPluralResolver- CLDR pluralization rulesNumberFormatPlugin- Number/currency formattingCapitalizePlugin- Text transformations
Use Cases Layer (Business Logic)
Orchestrates the adapters:
I18nService- Main translation engineTranslationFileManager- File operations and auto-creation
📚 API Reference
Initialization
initI18n(config: I18nConfig): I18nService
Initialize the i18n system.
interface I18nConfig {
locale: Locale; // Current locale
fallbackLocale?: Locale; // Fallback locale
loader?: Loader; // Translation loader
saver?: TranslationSaver; // Translation saver
saveMissing?: boolean; // Auto-save missing keys
debug?: boolean; // Debug mode
outputFormat?: OutputFormat; // 'json' | 'yml' | 'yaml' | 'toml'
pluralizationStrategy?: "suffix" | "cldr"; // Pluralization strategy
messageFormat?: "mustache" | "icu"; // Message format syntax
fileStructure?: "nested" | "flat"; // File structure
plugins?: Plugin[]; // Plugins to register
}Translation
t(key: string, defaultText?: string, vars?: Record<string, any>): string
Translate a key.
// Simple
t("home.title", "Welcome");
// With variables
t("greeting", "Hello, {{name}}!", { name: "World" });
// With count (pluralization)
t("apple", { count: 5 });
// With namespace
t("auth:login.button", "Sign In");Locale Management
setLocale(locale: string): Promise<void>
Change the current locale.
await setLocale("es");getI18n(): I18nService
Get the i18n instance.
const i18n = getI18n();
const currentLocale = i18n.getCurrentLocale();Manual Translation Management
addTranslations(locale: string, namespace: string, data: Record<string, string>): void
Manually add translations.
addTranslations("en", "common", {
hello: "Hello",
goodbye: "Goodbye",
});🔌 Built-in Plugins
NumberFormatPlugin
Format numbers, currencies, percentages, and compact numbers:
import { NumberFormatPlugin } from "@i18n-bakery/core";
new NumberFormatPlugin();
// Usage in translations:
t("price", "{amount|currency:USD}", { amount: 1234.56 });
// → "$1,234.56"
t("count", "{value|number}", { value: 1234567.89 });
// → "1,234,567.89"
t("discount", "{value|percent}", { value: 0.25 });
// → "25%"
t("views", "{count|compact}", { count: 1500000 });
// → "1.5M"CapitalizePlugin
Transform text case:
import { CapitalizePlugin } from "@i18n-bakery/core";
new CapitalizePlugin();
// Add base translation
addTranslations("en", "common", {
greeting: "hello world",
});
// Use suffixes:
t("greeting_upper"); // → "HELLO WORLD"
t("greeting_lower"); // → "hello world"
t("greeting_capitalize"); // → "Hello world"
t("greeting_title"); // → "Hello World"🔧 Creating Custom Plugins
import { Plugin, PluginMetadata, PluginContext } from "@i18n-bakery/core";
class MyCustomPlugin implements Plugin {
readonly metadata: PluginMetadata = {
name: "my-custom-plugin",
version: "1.0.0",
type: "processor",
description: "My custom plugin",
author: "Your Name",
};
config = {
enabled: true,
options: {},
};
// Lifecycle hooks
init?(context: PluginContext): void {
console.log("Plugin initialized");
}
beforeTranslate?(context: PluginContext): void {
console.log("Before translate:", context.key);
}
afterTranslate?(context: PluginContext): string | void {
// Modify the result
if (context.result) {
return context.result + " [processed]";
}
}
onMissing?(context: PluginContext): string | void {
console.log("Missing key:", context.key);
}
}
// Register the plugin
initI18n({
locale: "en",
plugins: [new MyCustomPlugin()],
});🧪 Testing
# Run tests
pnpm test
# Watch mode
pnpm test --watch
# Coverage
pnpm test --coverageCurrent Stats:
- ✅ 197 tests passing
- ✅ 100% coverage on critical paths
- ✅ 14 test suites
📖 Examples
Node.js Backend
import { initI18n, t, JSONFileSaver } from "@i18n-bakery/core";
import path from "path";
initI18n({
locale: "en",
fallbackLocale: "en",
saveMissing: true,
saver: new JSONFileSaver(path.join(__dirname, "locales")),
loader: async (locale, namespace) => {
const filePath = path.join(
__dirname,
"locales",
locale,
`${namespace}.json`
);
return require(filePath);
},
});
// Use in your API
app.get("/api/greeting", (req, res) => {
const locale = req.headers["accept-language"] || "en";
await setLocale(locale);
res.json({
message: t("api.greeting", "Hello from the API!"),
});
});With TypeScript
import { initI18n, t } from "@i18n-bakery/core";
import type { I18nConfig } from "@i18n-bakery/core";
const config: I18nConfig = {
locale: "en",
fallbackLocale: "en",
messageFormat: "icu",
pluralizationStrategy: "cldr",
};
initI18n(config);
// Type-safe translation
const greeting: string = t("home.welcome", "Welcome!");🔗 Related Packages
- @i18n-bakery/react - React bindings with hooks and providers
- @i18n-bakery/cli - Command-line tools for extraction and compilation
📜 License
MIT © Arturo Sáenz
🙏 Support
🥖 The foundation of your internationalization bakery
Made with 🍩 and Clean Architecture
