npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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.

npm version License: MIT


📦 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: title

6. 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 storage
  • JSONFileSaver - Save translations to JSON files
  • ConsoleSaver - Log missing translations to console
  • MustacheFormatter - Simple {{variable}} interpolation
  • ICUMessageFormatter - Full ICU MessageFormat support
  • DefaultKeyParser - Parse hierarchical keys
  • CLDRPluralResolver - CLDR pluralization rules
  • NumberFormatPlugin - Number/currency formatting
  • CapitalizePlugin - Text transformations

Use Cases Layer (Business Logic)

Orchestrates the adapters:

  • I18nService - Main translation engine
  • TranslationFileManager - 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 --coverage

Current 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



📜 License

MIT © Arturo Sáenz


🙏 Support


🥖 The foundation of your internationalization bakery

Made with 🍩 and Clean Architecture