ember-intl

Internationalization for Ember.js apps, built on ICU message syntax and the standard Intl API.

Library
npm
v9.0.1
439stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
89/100Excellent
Development Activity96
Maintenance96
Community84
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture88
Code Quality92
Innovation62
Learning Curve80

ember-intl is the standard internationalization addon for Ember.js applications and addons, providing an injectable intl service, a set of Handlebars helpers, and test support utilities for locale-aware apps. It is built on top of FormatJS’s @formatjs/intl, so translations are authored using ICU MessageFormat syntax and formatted using the browser’s native Intl API rather than a custom formatting engine.

The addon supports Ember apps, classic (v1) addons and engines, and modern v2 addons, making it usable across almost any Ember codebase regardless of how it was built. Beyond runtime formatting, the ember-intl project ships companion packages for linting translation usage (@ember-intl/lint), automating codemods when upgrading (@ember-intl/update), and bridging older v1-style translation setups (@ember-intl/v1-compat).

With over 90,000 weekly npm downloads and a decade of history (first published in 2015), ember-intl has become the de facto choice for Ember teams that need to ship translated UI, format dates/times/numbers per-locale, and enforce translation-key hygiene via lint rules and test helpers.

What You Get

  • An injectable IntlService with t()/formatMessage(), formatDate(), formatTime(), formatDateRange(), formatNumber(), formatList(), formatRelativeTime(), and formatDisplayName() methods
  • Template helpers ({{t}}, {{format-message}}, {{format-date}}, {{format-number}}, etc.) that call the same formatting logic directly from Handlebars templates
  • Test-support utilities (setupIntl, addTranslations, setLocale) for stubbing locales and translations in QUnit tests
  • A companion ESLint-based linter (@ember-intl/lint) that catches missing or malformed translation keys before they ship
  • A codemod/upgrade tool (@ember-intl/update) for migrating translation usage across major versions
  • A v1-compat package for classic addons and engines still using the pre-v2 translation setup

Common Use Cases

  • Translating user-facing strings in an Ember app across multiple locales using ICU MessageFormat (plurals, gender, interpolation)
  • Formatting dates, times, and date ranges consistently per-locale without hand-rolling Intl calls in every component
  • Formatting currency, percentages, and other locale-sensitive numbers in templates via {{format-number}}
  • Writing locale-aware component tests with setupIntl so translated output can be asserted deterministically
  • Enforcing translation-key consistency across a large Ember codebase using the @ember-intl/lint ESLint rules

Under The Hood

Architecture The public surface (src/index.ts) re-exports a set of stateless helper functions plus the IntlService type. The core IntlService (src/services/intl.ts) is a Glimmer-tracked Ember Service that owns per-locale IntlShape instances cached in a private _intls map, delegating all actual message/date/number formatting to a private -private/formatjs/ adapter layer that wraps @formatjs/intl’s createIntl/formatMessage/formatDate primitives. Template helpers under src/helpers/ are thin classic Ember Helper subclasses that inject the intl service and forward their arguments to the matching service method, so templates and JS call sites share one implementation. Test support (src/test-support/) hooks into @ember/test-helpers’s getContext()/settled() to stub locale and translations per test. The v2 addon shim (@embroider/addon-shim, configured via ember-addon.app-js in package.json) remaps the helper and service files into the consuming app’s namespace.

Tech Stack Written almost entirely in TypeScript, built with Rollup (rollup.config.mjs) via @embroider/addon-dev’s v2-addon pipeline, using @babel/plugin-transform-typescript and decorator-transforms for native decorator support against the Ember octane edition and Glimmer components/tracking. The sole runtime dependency is @formatjs/intl for ICU parsing and Intl-API-backed formatting. Type-checking runs through @glint/ember-tsc for template-aware TypeScript, and tests run under ember-qunit/@ember/test-helpers against ember-source. The wider repo is a pnpm workspace using Changesets for release automation and GitHub Actions for CI/CD.

Code Quality Every IntlService method has its own dedicated unit test file under tests/ember-intl/tests/unit/services/intl/ (formatDate, formatNumber, formatMessage, t, setLocale, setFormats, setOnFormatjsError, setOnMissingTranslation, exists, getTranslation, and more), plus integration tests for the template helpers. Strict typing is enforced via ember-tsc --noEmit against a shared @shared-configs/typescript config, and linting runs through a shared @shared-configs/eslint-config-ember v2-addon preset with Prettier enforced in CI. Errors are handled explicitly through overridable callbacks (_onFormatjsError, _onMissingTranslation) rather than swallowed silently, and assert() guards invariants such as reading primaryLocale before setLocale() has been called. CI runs a lint-and-test matrix across all 20+ workspace packages, docs apps, and test apps on every push and PR.

API Design The public API is deliberately narrow: one service class whose methods mirror each ICU formatter (formatMessage, formatDate, formatNumber, formatList, formatRelativeTime, formatDisplayName, formatTime) plus a single t() shortcut, with template helpers that are pure pass-throughs to those same methods — one code path for both JS and template call sites. Configuration hooks (setOnMissingTranslation, setOnFormatjsError, setFormats) are opt-in overrides with sensible defaults, so a project can start with zero configuration and layer in custom missing-translation handling later without extra boilerplate. This is intentionally a Facade over @formatjs/intl rather than a novel formatting engine, which is why its innovation score is modest relative to its ergonomics — exists(), getTranslation(), and locale getters (locales, primaryLocale) give consumers straightforward introspection without reaching into private state.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search