i18n-js
A TypeScript library that ports Rails-style I18n translation, pluralization, and locale-aware number/date formatting into any JavaScript app.
Repository Health
Technical Analysis
i18n-js provides the same translation, pluralization, and formatting API as Ruby on Rails’ I18n gem, but implemented natively in TypeScript for use in any JavaScript environment. Instantiate an I18n class with a translations object — commonly exported directly from a Rails backend via the companion i18n-js Ruby gem — and call t()/translate() to look up nested keys, with support for interpolation, scoped lookups, and per-call locale overrides.
Beyond translation, the library bundles Rails’ rails-i18n base locale data for dozens of languages, giving out-of-the-box locale-aware number formatting (numberToCurrency, numberToDelimited, numberToHuman), date formatting (strftime), and CLDR-style pluralization rules. Extension points — locale fallback chains, custom pluralizers, and missing-translation behavior — are all implemented as pluggable registries (locales.register, pluralization.register, missingTranslation.register), and a built-in change-event system (onChange, version) makes it straightforward to wire into reactive UI frameworks like React.
What You Get
- An
I18nclass supportingt/translatelookups by dot-notation or array scope, with interpolation and per-call option overrides - Bundled base translations from
rails-i18n(injson/) covering dozens of locales for dates, numbers, and sentence connectors - CLDR-style pluralization with a
pluralization.register()registry for custom rules (Russian, Slavic languages, etc.) and auseMakePluraladapter - Locale fallback chains via
enableFallbackandlocales.register(), cascading from a specific locale down to a default - Rails-parity number/currency/date formatting helpers (
numberToCurrency,numberToHuman,strftime,timeAgoInWords) - A change-event system (
onChange,version) for wiring locale/translation updates into reactive UI re-renders
Common Use Cases
- Sharing one translation source between a Rails backend (via the
i18n-jsRuby gem export) and a JavaScript/React frontend - Re-rendering a React app’s UI immediately on locale switch by subscribing to
i18n.onChangeand keying offi18n.version - Lazy-loading per-locale JSON bundles at runtime and merging them in with
i18n.store()instead of shipping every language upfront - Registering a custom pluralizer for languages with complex plural rules (e.g. Russian one/few/many/other) instead of the default zero/one/other
Under The Hood
Architecture
The library centers on a single I18n class (src/I18n.ts) that owns locale state (_locale, _defaultLocale, _version) and composes three collaborator registries — Locales (fallback-chain resolution), Pluralization (pluralizer functions keyed by locale), and MissingTranslation (pluggable missing-key behaviors: message/guess/error) — each its own class in src/Locales.ts, src/Pluralization.ts, and src/MissingTranslation.ts. Translation lookup and formatting are delegated to a flat set of pure helper functions under src/helpers/ (lookup, interpolate, formatNumber, numberToHuman, strftime, parseDate, pluralize, etc.) that I18n methods call directly rather than subclassing, keeping the core class a thin orchestrator over independently testable functions; src/index.ts re-exports only I18n, Locales, MissingTranslation, Pluralization, and the public types from src/typing.ts, leaving the helper layer as an internal implementation detail. There’s no plugin system or DI container — extension happens by registering handlers on the collaborator registries (locales.register, pluralization.register, missingTranslation.register) — so a change to the core class’s public API would ripple through those registries, though the helpers themselves stay decoupled enough to swap individually (e.g. replacing interpolate with a JSX-aware version) without touching the class.
Tech Stack
Written entirely in TypeScript and compiled three ways via npm scripts — tsc to CommonJS (dist/require) for Node, tsc to ESNext modules (dist/import) for bundlers, and a Webpack UMD/var-global build (dist/browser) for <script> tag consumption — with esbuild used only to pre-bundle a vendored lodash subset (src/lodash.ts re-exports get/has/merge) so consumers don’t need lodash as a runtime peer dependency. Direct dependencies are minimal: lodash (three functions), bignumber.js for precision-safe currency/number math, and make-plural (pinned to 7.5.0) as an optional pluralization backend. Linting runs through @fnando/codestyle’s shared ESLint/TypeScript/Prettier presets rather than a bespoke config, and API docs are generated per release with typedoc into a versioned docs/vX.Y.Z/ tree published via GitHub Pages.
Code Quality
Tests live under a top-level __tests__/ directory (not colocated with src/) using Jest with ts-jest and a custom HUD reporter; test files map one-to-one to behaviors (pluralization.test.ts, pluralization.ru.test.ts, numberToCurrency.test.ts, interpolationPluralization.test.ts, localization.test.ts, and many more), giving broad behavioral coverage of the public API and edge-case locale rules, run via jest --ci --coverage in CI. TypeScript is used throughout with explicit exported types in src/typing.ts for every options object (TranslateOptions, NumberToCurrencyOptions, etc.), and tsc --noEmit plus eslint --max-warnings 0 both gate the CI test script, so type errors and lint warnings fail the build outright. Naming is consistent (camelCase functions, PascalCase classes matching Rails’ own naming), and a GitHub Actions workflow runs the suite on every push — a solid, conventional setup, though error handling favors returning message strings or throwing via the pluggable missingBehavior option rather than typed error classes.
API Design
The public API deliberately mirrors Ruby on Rails’ I18n/ActiveSupport method names and semantics (t/translate, l/localize, numberToCurrency, strftime) so teams already using Rails’ i18n gem on the backend can reuse the same translation JSON and mental model on the frontend with almost no relearning — this is the library’s main differentiator versus locale-agnostic alternatives like i18next, which use their own conventions. Getting started requires only new I18n(translations) with no build step or config file, and every extension point (locale fallback, pluralization, missing-key behavior) is a single .register() call on a typed registry rather than a sprawling config object, keeping the surface area small; the tradeoff is that consumers without existing Rails-shaped translation JSON must hand-roll it or adopt the companion Ruby gem’s export tooling to get the full benefit.
Used by 3 apps in this directory
Chaskiq
CRM · Customer Support
Self-hosted live chat, video calls, help center, and marketing automation — a full-stack Intercom alternative you run on your own infrastructure.
Forem
Community · Blogging
Open source Ruby on Rails platform for building developer communities with articles, discussions, and social profiles—the same software that powers dev.to.
OpenProject
Project Management · Productivity · Collaboration
The open source project management platform that unifies Gantt charts, agile boards, time tracking, and team collaboration under full self-hosted control.