Skip to content

Migration from v7 to v8

Breaking changes

Minimum requirements

The following versions are supported when issues arise.

  • Ember 4.12 and above
  • Node 20 and above
  • @ember/test-helpers 4.x and above

Removed app/formats.js support

If you want reusable formats (these are optional), define them in app/ember-intl.{js,ts}. This file replaces app/formats.js.

ts
/* app/ember-intl.ts */
import type { Formats } from 'ember-intl';

export const formats: Formats = {
  formatTime: {
    hhmmss: {
      hour: 'numeric',
      minute: 'numeric',
      second: 'numeric',
    },
  },
};

TIP

The key names now match the names of the intl service methods (and the names of the helpers in strict mode):

Usage in intl serviceKey (before)Key (after)
formatDate()dateformatDate
formatDateRange()dateTimeRangeformatDateRange
formatNumber()numberformatNumber
formatRelativeTime()relativeformatRelativeTime
formatTime()timeformatTime

Unlike with app/formats.js, ember-intl doesn't automatically (magically) read your formats. You use standard JavaScript to import and consume them. (You may see that formats can then live in any file. For standardization, app/ember-intl.{js,ts} is recommended.)

ts
/* app/routes/application.ts */
import Route from '@ember/routing/route';
import { type Registry as Services, service } from '@ember/service';
import { formats } from 'my-app/ember-intl';

export default class ApplicationRoute extends Route {
  @service declare intl: Services['intl'];

  beforeModel(): void {
    this.setupIntl();
  }

  private setupIntl(): void {
    this.intl.setFormats(formats);
    this.intl.setLocale(['en-us']);
  }
}

Since formats are set in the application route, your rendering and unit tests may need additional setup if they depend on a particular format.

ts
import { formats } from 'my-app/ember-intl';

module('Integration | Component | my-component', function (hooks) {
  setupIntl(hooks, 'en-us');

  hooks.beforeEach(function () {
    const intl = this.owner.lookup('service:intl');
    intl.setFormats(formats);
  });
});

Removed /blueprints

Until now, you could run ember install ember-intl, which caused files in /blueprints to create the files config/ember-intl.js (for overriding build options) and translations/en-us.yaml (for defining translations).

Using ember install to do a post-install is a remnant from classic Ember. It blurs the separation of concerns and isn't what ember-intl as a v2 addon should keep supporting. There are better ways to scaffold files (e.g. allow copy-pastes from the documentation site, run a codemod).

It also made sense to remove /blueprints for a few more reasons:

  • The default values for build options should work well for most projects.
  • Some may want to use *.json or another locale as their default choice.
  • A smaller bundle size can be achieved.

If you have a new project, you will need to create these files manually.

Removed build options

Before v6, the number of build options that can be configured became too much. The more build options ember-intl allows, the harder it is to maintain code, standardize usage, and pave a way forward for Vite apps.

ember-intl@v8 will continue to use the following options for @ember-intl/v1-compat and @ember-intl/vite:

ts
const defaultBuildOptions = {
  fallbackLocale: undefined,
  inputPath: 'translations',
  publicOnly: false,  // Only for `@ember-intl/v1-compat`
  wrapTranslationsWithNamespace: false,
};

Conversely, it no longer considers these options:

  • errorOnMissingTranslations
  • errorOnNamedArgumentMismatch
  • excludeLocales
  • includeLocales
  • outputPath
  • requiresTranslation
  • stripEmptyTranslations
  • verbose

For more information, see Build options.

Removed linting of translations

The ember-intl package is no longer responsible for linting translations. For migration, see @ember-intl/lint below.

Removed loading of translations

The ember-intl package is no longer responsible for loading and merging translations. For migration, see @ember-intl/v1-compat or @ember-intl/vite below.

Renamed {{format-relative}}

It was unclear from the name what exactly is being compared (relative to what?).

The method and the corresponding helper are called formatRelativeTime() and {{format-relative-time}} in v8.

diff
// For *.{gjs,gts,js,ts} files
- this.intl.formatRelative(-1);
+ this.intl.formatRelativeTime(-1);
diff
{{! For *.hbs files }}
<div>
-   Past: {{format-relative -1}}
+   Past: {{format-relative-time -1}}
</div>
diff
// For *.{gjs,gts} files
- import { formatRelative } from 'ember-intl';
+ import { formatRelativeTime } from 'ember-intl';

<template>
  <div>
-     Past: {{formatRelative -1}}
+     Past: {{formatRelativeTime -1}}
  </div>
</template>

Simplified message for setOnMissingTranslation() in tests

When a translation is missing, the intl service uses setOnMissingTranslation() to display some string. The default value for development and production has been Missing translation "${key}" for locale "${locale}". The value is different for testing.

Until now, setupIntl() replaced the default value with a string that weakly asserts the translation key and data. The code that converts data (an arbitrary object) to a string wasn't easy to maintain. It increased the package size while providing limited value, since we can't know from weak assertions what our end-users see in reality.

In v8, setupIntl() uses a simpler value, one that only asserts the translation key.

diff
// Without data
- assert.dom().hasText('t:hello.message:()');
+ assert.dom().hasText('t:hello.message');

// With data
- assert.dom().hasText('t:hello.message:("name":"Zoey")');
+ assert.dom().hasText('t:hello.message');

Again, the best way is to ensure that translations are loaded in tests, so that you can write assertions that are strong and don't leak implementation details.

diff
- assert.dom().hasText('t:hello.message:("name":"Zoey")');
+ assert.dom().hasText('Hello, Zoey!');

If you can't load translations and want to weakly assert data, call setOnMissingTranslation() in your custom setupRenderingTest().

ts
/* tests/helpers/index.ts */
import { setupIntl } from 'ember-intl/test-support';
import {
  setupRenderingTest as upstreamSetupRenderingTest,
  type SetupTestOptions,
} from 'ember-qunit';

function setupRenderingTest(hooks: NestedHooks, options?: SetupTestOptions) {
  upstreamSetupRenderingTest(hooks, options);
  setupIntl(hooks, 'en-us');

  hooks.beforeEach(function () {
    const intl = this.owner.lookup('service:intl');

    intl.setOnMissingTranslation((key, _locale, data) => {
      // Return some string
    });
  });
}

export { setupRenderingTest };

Features

V2 addon

ember-intl is now a v2 addon. It continues to provide the following, now pre-built:

  • Helpers
  • intl service
  • Test helpers
  • Native types for Glint
  • Barrel file for templates in strict mode
  • Template registry for templates in loose mode

It delegates linting to @ember-intl/lint, and loading and merging translations to @ember-intl/v1-compat and @ember-intl/vite. Therefore, when you update ember-intl to v8, you may want to install these packages as development dependencies.

@ember-intl/lint

@ember-intl/lint is the official linter for ember-intl. It removes the need for ember-intl-analyzer, separates linting and building translations, and strives to be "zero config."

For more information, see @ember-intl/lint's README.

@ember-intl/v1-compat

@ember-intl/v1-compat ensures that v1 apps (classic build, Embroider + Webpack) can use ember-intl as a v2 addon.

  • V1 apps need @ember-intl/v1-compat.
  • V1 addons need it if their dummy app needs translations for documentation or testing.

For more information, see @ember-intl/v1-compat's README.

@ember-intl/vite

@ember-intl/vite ensures that v2 apps (Embroider + Vite) can use ember-intl as a v2 addon.

  • V2 apps need @ember-intl/v1-compat.
  • V2 addons need it if their test app lives in the same package as the addon (not recommended).

For more information, see @ember-intl/vite's README.

{{t-key}} helper

In JavaScript files (*.{gjs,gts,js,ts}), you can use tKey() to mark strings that are actually translation keys. This will help programs (e.g. linters like @ember-intl/lint, codemods) check how you use translations.

For more information and examples, see {{t-key}}.