Skip to content

Migration from v7 to v8

Breaking changes

Minimum requirements

Projects with these 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

If you want to standardize formats for ember-intl's helpers, you can define the formats in app/ember-intl.{js,ts}. This file replaces app/formats.js.

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)
formatDatedateformatDate
formatDateRangedateTimeRangeformatDateRange
formatNumbernumberformatNumber
formatRelativeTimerelativeformatRelativeTime
formatTimetimeformatTime

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 actually live in any file. For standardization, app/ember-intl.{js,ts} is recommended.)

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(['de-de']);
  }
}

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

gts
import { setupIntl } from 'ember-intl/test-support';
import { formats } from 'my-app/ember-intl';

module('Integration | Component | example', 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 and translations/en-us.yaml.

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 doing. 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
type BuildOptions = {
  fallbackLocale: string | undefined;
  inputPath: string;
  publicOnly: boolean;  // Only for `@ember-intl/v1-compat`
  wrapTranslationsWithNamespace: boolean;
};
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 Advanced - Configuration file.

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 formatRelative

It was unclear from the name what exactly is being compared (relative to what?). The method and the corresponding helper are now called formatRelativeTime.

diff
- import { formatRelative } from 'ember-intl';
+ import { formatRelativeTime } from 'ember-intl';

<template>
-   {{formatRelative -1}}
+   {{formatRelativeTime -1}}
</template>
diff
- this.intl.formatRelative(-1);
+ this.intl.formatRelativeTime(-1);
diff
- {{format-relative -1}}
+ {{format-relative-time -1}}

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 is Missing translation "${key}" for locale "${locale}".

The problem is, the default 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 understand and 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
- assert.dom().hasText('t:hello.message:()');
+ assert.dom().hasText('t:hello.message');
diff
- 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!');

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.

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

@ember-intl/v1-compat

@ember-intl/v1-compat ensures that v1 apps (classic, 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.

IMPORTANT

After installing @ember-intl/v1-compat, your app should be able to continue to load translations and pass them to the intl service. No configuration changes are needed.

For more information, see subsections titled "v1 apps" in Quickstart (Apps). You can also check @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/vite.
  • V2 addons need it if their test app lives in the same package as the addon (not recommended).

IMPORTANT

After installing @ember-intl/vite, you need to reconfigure your app, in order to load translations and pass them to the intl service.

  • Replace config/ember-intl.js with ember-intl.config.{js,mjs}.
  • Update vite.config.{mjs,mts} and tsconfig.json so that you can import translation files, which are now "virtual."

For more information, see subsections titled "v2 apps" in Quickstart (Apps). You can also check @ember-intl/vite's README.

tKey helper

You can use tKey to mark strings that you know are actually translation keys. This helps programs (e.g. @ember-intl/lint) check how you use translations.

For more information and examples, see Helpers - tKey.