si18n.js

v2.0.0

A simple and lightweight way to integrate internationalization on a website project.

Changelog

Good morning

This is an example.

Cette phrase n'est pas disponible en anglais.

There is one monkey in the tree.
There are 12 monkeys in the tree.


Current language : en
All available languages : fr, en

si18n.js logo demonstration

# Table of content
Filter by package:
si18n.js Browser
Full browser client. Automatic HTML attribute translations (data-si18n-*), automated toggler buttons and <select> pickers, LTR/RTL switching, URL & browser language detection, and localStorage persistence.
si18n.js/core Universal
Platform-agnostic and ultra-lightweight translation core with zero DOM dependencies. Runs in any JS runtime (Browser, Node.js, Deno, Bun, React Native, Edge). Pluggable dependencies and custom storage adapters (StorageAdapter).
si18n.js/node Node.js
Optimized Node.js implementation. Local filesystem file loading via node:fs/promises, automatic OS language detection (LC_ALL, LANG, etc.), and built-in memory or process.env storage adapters.
si18n.js/react React Client
Context provider <Si18nProvider> and useTranslation() hook. Synchronous, tear-free rendering powered by useSyncExternalStore. Exposes t, locale, locales, setLocale, isReady and error.
si18n.js/react/server React Server / RSC
Async createServerTranslator() translator for React Server Components (Next.js App Router), SSR and server rendering pipelines. Zero DOM and lightning-fast execution.
# Installation
npm
npm i si18n.js
yarn add si18n.js
CDN ES6 Modules
You can also use si18n.js from a CDN. Different builds are distributed depending on your import style:
<!-- si18n.js latest version @2.0.0 -->
<script type="module" src="https://si18n.js.bruxelles.dev/si18n.js"></script>
<script type="module" src="https://si18n.js.bruxelles.dev/si18n.min.js"></script>

<!-- UNPKG -->
<script type="module" src="https://unpkg.com/si18n.js"></script>
<script type="module" src="https://unpkg.com/si18n.js@2.0.0/si18n.js"></script>
<script type="module" src="https://unpkg.com/si18n.js@2.0.0/si18n.min.js"></script>

<!-- jsDelivr -->
<script type="module" src="https://cdn.jsdelivr.net/npm/si18n.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/si18n.js@2.0.0"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/si18n.js@2.0.0/si18n.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/si18n.js@2.0.0/si18n.min.js"></script>

<!-- ESM -->
<!-- (?target=es2015|es2022|esnext) -->
<script type="module" src="https://esm.sh/si18n.js@2.0.0"></script>

<script type="module" src="https://cdn.jsdelivr.net/npm/si18n.js@2.0.0/+esm"></script>

<!-- jsDelivr -->
<script type="module">
  import Si18n from "https://cdn.jsdelivr.net/npm/si18n.js@2.0.0/si18n.min.js";
  // ...
</script>
CDN Classic Global Script
Classic global script (non-module) which sets the global class window.Si18n.
<!-- Load the global script (sets window.Si18n) -->
<script src="https://unpkg.com/si18n.js@2.0.0/si18n.es6.min.js"></script>
<script>
  const loc = new window.Si18n();
  // ...
</script>
ES6 Source versions
Untranspiled ES6 source modules. Recommended if your own bundler handles transpiling node_modules.
<!-- Unminified global script -->
<script src="https://unpkg.com/si18n.js@2.0.0/si18n.es6.js"></script>
# Usage
Browser (NPM / ESM)
import Si18n from "si18n.js";
// or import Si18nBrowser from "si18n.js/browser";

const loc = new Si18n();
await loc.init({
  path: "./locales",
  availableLocales: ["fr", "en"],
  fallbackLang: "fr",
  activeClass: "active",
  togglersSelector: ".lang-btn"
});
Universal Core (Any JS runtime without DOM)
import { Si18nCore } from "si18n.js/core";

const loc = new Si18nCore();
await loc.init({
  locales: {
    en: { greeting: "Hello" },
    fr: { greeting: "Bonjour" }
  },
  lang: "fr",
  fallbackLang: "en"
});

console.log(loc.t("greeting")); // "Bonjour"
Node.js (CommonJS & ESM)
// CommonJS
const { Si18nNode, createMemoryStorage } = require("si18n.js/node");

// or ESM : import { Si18nNode } from "si18n.js/node";

const loc = new Si18nNode({
  path: "./locales",
  availableLocales: ["en", "fr"],
  fallbackLang: "en"
});

console.log(loc.t("greeting"));
TypeScript (All packages & Types)
// Targeted imports of classes and utilities
import Si18nBrowser, { type Si18nBrowserInitOptions } from "si18n.js/browser";
import { Si18nCore, type Si18nInitOptions, type StorageAdapter } from "si18n.js/core";
import { Si18nNode, createMemoryStorage, createProcessEnvStorage } from "si18n.js/node";
import { Si18nProvider, useTranslation, type TranslationContextValue } from "si18n.js/react";
import { createServerTranslator, type ServerTranslator } from "si18n.js/react/server";
React Client (<Si18nProvider> & useTranslation)
"use client";

import Si18nBrowser from "si18n.js/browser";
import { Si18nProvider, useTranslation } from "si18n.js/react";

const i18n = new Si18nBrowser();

function LanguagePicker() {
  const { t, locale, locales, setLocale, isReady, error } = useTranslation();

  if (error) return <p>Erreur : {error.message}</p>;
  if (!isReady) return <p>Chargement des traductions...</p>;

  return (
    <div>
      <h1>{t("welcome")}</h1>
      <select value={locale} onChange={(e) => void setLocale(e.target.value)}>
        {locales.map((lang) => (
          <option key={lang} value={lang}>{lang.toUpperCase()}</option>
        ))}
      </select>
    </div>
  );
}

export default function App() {
  return (
    <Si18nProvider
      i18n={i18n}
      options={{
        path: "./locales",
        availableLocales: ["fr", "en"],
        fallbackLang: "fr"
      }}
    >
      <LanguagePicker />
    </Si18nProvider>
  );
}
React Server Components (Next.js App Router / SSR)
// app/page.tsx (Server Component without DOM)
import { createServerTranslator } from "si18n.js/react/server";

export default async function Page() {
  const { t, locale } = await createServerTranslator({
    path: "./locales",
    availableLocales: ["fr", "en"],
    lang: "fr",
    fallbackLang: "fr"
  });

  return (
    <main>
      <h1>{t("welcome")}</h1>
      <p>Locale active sur le serveur : {locale}</p>
    </main>
  );
}
Basic example
let loc = new Si18n();
loc.init({
  locales: {}, // { "en": {...}, "fr": {...}}
  lang: "fr",
  fallbackLang: "en",
  translate() {
    document.querySelector(".lang").innerText = loc.getLocale();
    document.querySelector(".title").innerText = loc.t("nested.title");
    // ...
  },
  onLocaleChanged() {
    // ...
  }
});
Automatic detection & HTML attributes
In browser environments, si18n.js automatically inspects and translates elements bearing these attributes:
<div data-si18n="site.title"></div>
data-si18n-default attribute
When data-si18n-default="false" is set, the translation is not inserted into the element's textContent. Useful when only translating HTML attributes (title, placeholder, alt, etc.).
<div data-si18n="site.title" data-si18n-default="false" data-si18n-title="true"></div>
Complete list of supported attributes:

The following attributes are boolean flags ("true" or "false") targeting specific element properties:

  • data-si18n: translation key to apply (e.g. "site.title").
  • data-si18n-default: if set to "false", does not overwrite the element's text content.
  • data-si18n-html: if set to "true", injects the translated string as HTML (innerHTML). Default: false.
  • data-si18n-vars: JSON object of replacement variables passed to t() (e.g. data-si18n-vars='{"0": 12}').
  • data-si18n-placeholder: translates the placeholder attribute of <input> and <textarea> elements.
  • data-si18n-alt: translates the alt attribute of <img> elements for accessibility.
  • data-si18n-title: translates the element's native tooltip title attribute.
  • data-si18n-aria-label: translates the aria-label attribute for screen readers.
  • data-si18n-label: translates the label attribute of tags like <optgroup>.
  • data-si18n-value: translates the value attribute of tags like <option> or <input>.
  • data-si18n-content: translates the content attribute of tags like <meta> (SEO / prerendering).
The lang and dir attributes of the <html> tag are set automatically. Set "rtl": true in your locale file to activate RTL direction automatically. Default: ltr.
Replacement of values
Besides simple text replacements, si18n.js can insert dynamic values into placeholders with %{name} or %{0} syntax. Simply pass a key-value object as the second argument to t():
<!-- 1. HTML -->
<p id="intro"></p>
// 2. JAVASCRIPT
// 2.1.
let data = {
  "fr": {
    "intro": "%{name} a eu %{age} ans cette année.",
    "test": "Ceci est un test."
  },
  "en": {
    "intro": "%{name} turned %{age} this year.",
    "test": "This is a test."
  }
};

loc.init({
  // ...
  translate() {
    document.querySelector("#intro").innerText = loc.t("intro", {
      age: (new Date().getFullYear()) - birthYear,
      name: "John Doe"
    });

    console.log(loc.t("test")) // "Ceci est un test."
    // ...
  }
});
or
// 2.2.
let data = {
  "fr": { "intro": "Mon ami s'appelle %{0} et travaille chez %{1}." },
  "en": { "intro": "My friend's name is %{0} and he works at %{1}." }
};

loc.init({
  // ...
  translate() {
    document.querySelector("#intro").innerText = loc.t("intro", {
      0: "Georges",
      1: "CRFEV"
    });
    // ...
  }
});
# More demos
Nested keys (dot-notation)
Keys can be nested to any depth and accessed with dot-notation:
const loc = new Si18n();

await loc.init({
  locales: {
    en: {
      site: {
        title: "My Website",
        nav: { home: "Home", about: "About" }
      }
    }
  },
  lang: "en",
  fallbackLang: "en"
});

console.log(loc.t("site.title"));    // "My Website"
console.log(loc.t("site.nav.home")); // "Home"
Lazy-loading via path
Place your locale JSON files in a directory and si18n.js will fetch them on demand. Note: Only the fallback and active locales are loaded at startup.
// locales/en.json, locales/fr.json, locales/de.json ...
const loc = new Si18n();

await loc.init({
  path: "./locales",
  availableLocales: ["en", "fr", "de"],
  lang: "en",
  fallbackLang: "fr" // loaded
});

console.log(loc.t("greeting")); // value from en.json
await loc.setLocale("fr");      // fetches fr.json on demand
console.log(loc.t("greeting")); // value from fr.json
Lazy-loading via localeLoader
Use a custom async function — ideal for bundler code-splitting or custom server loaders:
const loc = new Si18n();

await loc.init({
  availableLocales: ["en", "fr"],
  lang: "en",
  fallbackLang: "en",
  localeLoader: async (locale) => {
    // Dynamic import — bundler will code-split automatically
    const module = await import(`./locales/${locale}.js`);
    return module.default;
  }
});
Subscribing to locale changes
subscribe() lets you react to locale changes anywhere in your code:
// Subscribe — returns an unsubscribe function
const unsubscribe = loc.subscribe((newLocale) => {
  console.log("Locale changed to:", newLocale);
  document.title = loc.t("pageTitle");
});

await loc.setLocale("fr"); // triggers the listener

unsubscribe(); // stop listening
RTL language support
Set rtl: true inside a locale object. si18n.js will automatically set dir="rtl" on the <html> element when that locale is active:
await loc.init({
  locales: {
    en: { greeting: "Hello" },
    ar: { rtl: true, greeting: "\u0645\u0631\u062d\u0628\u0627" }
  },
  lang: "en",
  fallbackLang: "en"
});

await loc.setLocale("ar");
// <html lang="ar" dir="rtl"> is set automatically
onLocaleChanged callback
Run side-effects after a locale switch (update page title, refresh dynamic content, …):
await loc.init({
  locales: {
    en: { pageTitle: "My App" },
    fr: { pageTitle: "Mon App" }
  },
  lang: "en",
  fallbackLang: "en",
  onLocaleChanged(newLocale, instance) {
    document.title = instance.t("pageTitle");
    console.log("Switched to:", newLocale);
  }
});
Node.js (Filesystem & System Detection)
Use the dedicated Si18nNode class to load local files and detect system locale:
const { Si18nNode } = require("si18n.js/node");

const loc = new Si18nNode({
  path: "./locales",
  availableLocales: ["en", "fr"],
  fallbackLang: "en"
});

// Automatically detects system language (LC_ALL, LANG, etc.)
console.log(loc.getLocale());
console.log(loc.t("greeting"));
Node.js (Storage Adapters)
Choose between volatile in-memory storage or environment variable persistence:
const { Si18nNode, createMemoryStorage, createProcessEnvStorage } = require("si18n.js/node");

// 1. In-memory volatile storage (default, does not pollute process.env)
const locMemory = new Si18nNode(
  { locales: { en: { hi: "Hi" } }, lang: "en" },
  { storage: createMemoryStorage() }
);

// 2. Persistence in process.env (e.g. process.env.APP_LANG)
const locEnv = new Si18nNode(
  { locales: { en: { hi: "Hi" } }, lang: "en", saveAs: "APP_LANG" },
  { storage: createProcessEnvStorage() }
);
React Client (<Si18nProvider> & useTranslation)
Production-ready example handling ready state (isReady), errors, and available locales:
import { Si18nProvider, useTranslation } from "si18n.js/react";
import Si18nBrowser from "si18n.js/browser";

const i18n = new Si18nBrowser();

function UserBadge() {
  const { t, locale, locales, setLocale } = useTranslation();
  return (
    <header>
      <span>{t("greeting", { name: "Alex" })}</span>
      {locales.map((code) => (
        <button
          key={code}
          disabled={code === locale}
          onClick={() => void setLocale(code)}
        >
          {code}
        </button>
      ))}
    </header>
  );
}
React Server Components (Next.js App Router / SSR)
Translate pages server-side asynchronously without a DOM using createServerTranslator():
// app/[lang]/page.tsx
import { createServerTranslator } from "si18n.js/react/server";

export default async function Page({ params }: { params: Promise<{ lang: string }> }) {
  const { lang } = await params;
  const { t, locale } = await createServerTranslator({
    path: "./locales",
    availableLocales: ["fr", "en"],
    lang,
    fallbackLang: "fr"
  });

  return (
    <section>
      <h1>{t("home.title")}</h1>
      <p>Langue rendue côté serveur : {locale}</p>
    </section>
  );
}
Universal Core & Custom Storage Adapter
Create an instance with a custom StorageAdapter (AsyncStorage, cookie, Redis, etc.):
import { Si18nCore, type StorageAdapter } from "si18n.js/core";

// Custom storage adapter (e.g. AsyncStorage, Redis, cookies...)
const customStorage: StorageAdapter = {
  getItem: (key) => window.sessionStorage.getItem(key),
  setItem: (key, val) => { window.sessionStorage.setItem(key, val); }
};

const loc = new Si18nCore(undefined, {
  storage: customStorage,
  detectLanguage: () => navigator.languages?.[0] ?? null
});

await loc.init({
  locales: { en: { ok: "OK" }, fr: { ok: "D'accord" } },
  lang: "fr"
});
# Methods & Functions (10)
Method / Function Argument Description
constructor options?:object,
dependencies?:object
Instantiates the si18n object. Accepts initial options and an optional dependencies object.
async init options:object Recommended - Asynchronously initializes the si18n object with given options. Returns a Promise<void>.
async setLocale lang:string Sets the active language, loads translation data if needed, and applies changes. Returns a Promise<void>.
getLocale void Returns the current language code (e.g.: 'en').
getLocales void Returns a copy of all available languages (e.g.: ['fr', 'en']).
t JSONPath:string,
values?:object
Returns the translation string for the given key (supports dot notation: site.nav.link). Accepts an optional replacement object %{key}.
subscribe listener:(locale) => void Subscribes to locale changes and returns an unsubscribe function () => void.
isInitialized void Returns true if the instance is initialized, otherwise false.
static async getJSON urlOrPath: string,
callback?(res: object): fn
Static async method fetching JSON from a URL (browser via fetch) or file path (Node.js via fs). Returns Promise<T> or passes result to optional callback.
toJSON void Returns a serialized snapshot of the instance configuration options.
createMemoryStorage void Creates an in-memory volatile storage adapter (StorageAdapter) based on a Map.
createProcessEnvStorage void Creates a storage adapter persisting active locale in process.env[saveAs].
async createServerTranslator options:object,
instance?:Si18nLike
Async function creating a translator { t, locale, locales, i18n } for React Server Components (RSC / Next.js / SSR).
<Si18nProvider> props:{ i18n, options, children } Context component <Si18nProvider> synchronizing locale changes tear-free via useSyncExternalStore.
useTranslation void React hook returning { t, locale, locales, setLocale, isReady, error, i18n }.
# Default options (15)
Argument Default value Description
locales {} Object containing your language translations. Required only if path or localeLoader is not defined.
path null Access path to the directory containing your translation JSON files. Files will be loaded automatically according to the active language (the fallback language will always be loaded). Priority over the locales option. Required only if locales is not defined. e.g.: ./locales.
localeLoader undefined Custom async loader function (locale) => Promise<object> to load locale messages dynamically. Ideal for bundler code-splitting or server-side loaders.
availableLocales [] List containing the names of available languages as defined in the translation files. Required only if path is defined.
lang '' Active language to use. If omitted, si18n.js detects language from URL, storage, or environment (browser / system variables). If not set, the fallbackLang option becomes required.
fallbackLang '' Fallback language. The language to use when the requested language or a requested string in a non-fully translated language is missing. If omitted, it falls back to the value of lang.
activeClass '' CSS class to add to the active button for the current language. Used with togglersSelector.
togglersSelector '' CSS selector for toggler elements. si18n.js automatically binds click listeners. Elements must have the data-lang="XX" attribute.
isTogglerSelect false Set to true if the toggler selector targets a <select> element. Unavailable options are automatically disabled.
sanitizeHtml undefined Optional (html: string) => string function called to sanitize HTML before injection when data-si18n-html="true" is used (e.g., DOMPurify.sanitize).
saveLang true Whether to save the language in persistent storage (localStorage for browser, memory or process.env for Node.js).
saveAs 'lang' Key name for persistent storage and URL parameter (Default: 'lang').
reloadPage false If set to true, the page will automatically refresh after changing language.
translate function() {} Callback function called to apply manual translations.
Note: For browsers, the <html> tag automatically receives lang and dir attributes.
onLocaleChanged function() {} Central callback triggered on locale changes (on init and setLocale). Receives (locale, instance).
dependencies {} Pluggable dependency injection object for Si18nCore and Si18nNode: storage (custom StorageAdapter), detectLanguage, getLanguageFromUrl, createPathLocaleLoader, reload, logError.
By José
dBruxelles