ember-intl
Internationalization for Ember.js apps, built on ICU message syntax and the standard Intl API.
Repository Health
Technical Analysis
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
IntlServicewitht()/formatMessage(),formatDate(),formatTime(),formatDateRange(),formatNumber(),formatList(),formatRelativeTime(), andformatDisplayName()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
Intlcalls in every component - Formatting currency, percentages, and other locale-sensitive numbers in templates via
{{format-number}} - Writing locale-aware component tests with
setupIntlso translated output can be asserted deterministically - Enforcing translation-key consistency across a large Ember codebase using the
@ember-intl/lintESLint 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.