Ember Intl Versions Save

Internationalization for Ember projects

v6.5.3

1 month ago

[!IMPORTANT] If your ember-intl is currently between versions 6.3.0 and 6.5.2, please update it to 6.5.3 or higher.

Thanks to @johanrd for investigating the issue and providing the fix quickly.

v6.5.2

1 month ago

Updating ember-cli-typescript will help remove warnings that ember-intl (along with other addons) might have produced.

WARNING: ember-cli-typescript requires ember-cli-babel ^7.17.0, but you have version 8.0.0 installed; your TypeScript files may not be transpiled correctly

[!IMPORTANT] The blueprint files, which make ember g translation <locale> currently possible, will be removed in v7.0.0. You can manually create the translation file (more accurately).

v6.5.1

1 month ago

In the docs folder, I created 3 additional projects so that there's a living documentation (tested in CI) of how apps, v1 addons, and v2 addons can provide translations.

I also updated the documentation site. You will find new content in:

  • Getting Started > Overview
  • Getting Started > Quickstart (Apps)
  • Getting Started > Quickstart (Addons)

v6.5.0

1 month ago

[!WARNING] To lower the maintenance cost, I removed code that should be unused, and refactored code that should be private.

While existing tests continued to pass, it's possible that one of the refactors is a soft breaking change for your project (e.g. because you overwrote a method from the intl service). To be safe, I went with a minor release.

If you find a regression, please open an issue and provide me context, so that we can see if a revert is needed.

Thanks to @bertdeblock for fixing a URL typo in the blueprints.

v6.4.1

1 month ago

The dependencies of ember-intl (in particular, @types/ember__runloop and @types/ember__template) have been updated to the latest version. This may fix the error messages shown below:

[lint:types] node_modules/ember-intl/-private/formatters/-base.d.ts:1:33 - error TS2307: Cannot find module '@ember/template/-private/handlebars' or its corresponding type declarations.
[lint:types] 
[lint:types] 1 import type { SafeString } from '@ember/template/-private/handlebars';
[lint:types]                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[lint:types] 
[lint:types] node_modules/ember-intl/-private/formatters/format-message.d.ts:1:33 - error TS2307: Cannot find module '@ember/template/-private/handlebars' or its corresponding type declarations.
[lint:types] 
[lint:types] 1 import type { SafeString } from '@ember/template/-private/handlebars';
[lint:types]                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[lint:types] 
[lint:types] node_modules/ember-intl/services/intl.d.ts:1:36 - error TS2307: Cannot find module '@ember/runloop/types' or its corresponding type declarations.
[lint:types] 
[lint:types] 1 import type { EmberRunTimer } from '@ember/runloop/types';
[lint:types]                                      ~~~~~~~~~~~~~~~~~~~~~~
[lint:types] 
[lint:types] node_modules/ember-intl/services/intl.d.ts:3:33 - error TS2307: Cannot find module '@ember/template/-private/handlebars' or its corresponding type declarations.
[lint:types] 
[lint:types] 3 import type { SafeString } from '@ember/template/-private/handlebars';
[lint:types]                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Thanks to @jelhan.

v6.4.0

4 months ago

What changed?

  • Allowed test helpers setLocale() and addTranslation() to call settled()
  • Updated documentation for testing
  • Fixed type errors caused by macros
  • Deprecated macros (to be removed in v7.0.0)
  • Improved <template>-tag support (allowed importing helpers from index)

Migration guide

Test helpers

Before v6.4.0, calling setLocale() and addTranslations() wouldn't have an effect on the application in tests. You might have had to call await settled() or use something from @ember/runloop to trigger an update.

Now, the test helpers handle the update and mean the following in tests:

  • setLocale() - update the locale as if the user had somehow changed their preferred language
  • addTranslations() - update the translations as if you had somehow added them (e.g. via lazy loading)

You will want to search your test files for setLocale(, addTranslations(, and ember-intl/test-support, then migrate code as follows:

Example: setLocale()
- import { render, settled } from '@ember/test-helpers';
+ import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setLocale, setupIntl } from 'ember-intl/test-support';
import { setupRenderingTest } from 'ember-qunit';
import { module, test } from 'qunit';

module('Integration | Component | hello', function (hooks) {
  setupRenderingTest(hooks);
  setupIntl(hooks);

  test('it renders', async function (assert) {
    await render(hbs`
      <Hello @name="Zoey" />
    `);

    assert.dom('[data-test-message]').hasText('Hello, Zoey!');

-     setLocale('de-de');
-     await settled();
+     await setLocale('de-de');

    assert.dom('[data-test-message]').hasText('Hallo, Zoey!');
  });
});
Example: addTranslations()
- import { render, settled } from '@ember/test-helpers';
+ import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { addTranslations, setupIntl, t } from 'ember-intl/test-support';
import { setupRenderingTest } from 'ember-qunit';
import { module, test } from 'qunit';

module('Integration | Component | lazy-hello', function (hooks) {
  setupRenderingTest(hooks);
  setupIntl(hooks);

  test('it renders', async function (assert) {
    await render(hbs`
      <LazyHello @name="Zoey" />
    `);

    assert
      .dom('[data-test-message]')
      .doesNotIncludeText(
        'Hello, Zoey!',
        'Before translations are loaded, we cannot write assertions against a string.',
      )
      .hasText(
        't:lazy-hello.message:("name":"Zoey")',
        'Before translations are loaded, we should see the missing-message string.',
      )
      .hasText(
        t('lazy-hello.message', { name: 'Zoey' }),
        'Before translations are loaded, we can write assertions against the test helper t().',
      );

-     addTranslations({
-       'lazy-hello': {
-         message: 'Hello, {name}!',
-       },
-     });
-     await settled();
+     await addTranslations({
+       'lazy-hello': {
+         message: 'Hello, {name}!',
+       },
+     });

    assert
      .dom('[data-test-message]')
      .hasText(
        'Hello, Zoey!',
        'After translations are loaded, we can write assertions against a string.',
      )
      .doesNotIncludeText(
        't:lazy-hello.message:("name":"Zoey")',
        'After translations are loaded, we should not see the missing-message string.',
      )
      .hasText(
        t('lazy-hello.message', { name: 'Zoey' }),
        'After translations are loaded, we can write assertions against the test helper t().',
      );
  });
});

Macros

[!IMPORTANT] The macros, which are a remnant of classic components and ember-i18n, will be removed in v7.0.0. I updated the documentation for macros to warn developers.

[!IMPORTANT] You can no longer import the macros from the index file, because the t() macro would conflict with the t() helper.

Until the macros are removed in v7.0.0, you can name-import them from 'ember-intl/macros' or refer to the full path (e.g. import t from 'ember-intl/macros/t';).

In classic and Glimmer components, inject the intl service to access its methods. If you want to create a value that depends on other things, you can use the @computed decorator (i.e. create a computed property) in classic and the native getter in Glimmer components. Alternatively, you can use ember-intl's helpers in the template.

Before: A classic component with macros
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, ember/no-classic-classes, ember/no-classic-components, ember/no-computed-properties-in-native-classes, import/export */
import Component from '@ember/component';
import type { Registry as Services } from '@ember/service';
import { intl, raw, t } from 'ember-intl/macros';

export default class MyComponent extends Component {
  tagName = '';

  @intl('fruits', function (_intl: Services['intl']) {
    // @ts-expect-error: 'this' implicitly has type 'any' because it does not have a type annotation.
    return _intl.formatList(this.fruits);
  })
  declare outputForIntl: string;

  @t('hello.message', {
    name: 'name',
  })
  declare outputForT: string;

  @t('hello.message', {
    name: raw('name'),
  })
  declare outputForTWithRaw: string;
}
After: A Glimmer component with getters
import { inject as service, type Registry as Services } from '@ember/service';
import Component from '@glimmer/component';

export default class MyComponent extends Component {
  @service declare intl: Services['intl'];

  get outputForIntl(): string {
    return this.intl.formatList(this.args.fruits);
  }

  get outputForT(): string {
    return this.intl.t('hello.message', {
      name: this.args.name,
    });
  }

  get outputForTWithRaw(): string {
    return this.intl.t('hello.message', {
      name: 'name',
    });
  }
}

Helpers in <template>-tag components

Before v6.4.0, you had to remember and write the full path to use one of ember-intl's helpers in a <template>-tag component. Now, you can import all helpers from the index file.

Example: t()
import type { TOC } from '@ember/component/template-only';
- import t from 'ember-intl/helpers/t';
+ import { t } from 'ember-intl';

interface HelloSignature {
  Args: {
    name: string;
  };
}

const HelloComponent: TOC<HelloSignature> =
  <template>
    <div data-test-message>
      {{t "hello.message" name=@name}}
    </div>
  </template>

export default HelloComponent;

v6.3.2

4 months ago

What changed?

Reverted the removal of the class property allowEmpty

If you happened to overwrite an ember-intl's helper so that, in your app, the helper allows "empty" values by default, then you may continue to use the following syntax:

/* my-addon/addon/helpers/t.ts */
import THelper from 'ember-intl/helpers/t';

export default class extends THelper {
  allowEmpty = true;
}

However, this (overwriting the addon, especially through inheritance) is not recommended, as implementation details can change in the future.

[!NOTE] Going forward, I'd like to see if all helpers can return an empty string when value is either undefined or null. You can visit https://github.com/ember-intl/ember-intl/issues/1813 and Discord thread to provide your feedback.

Allowed the intl service to handle removing event listeners

In addition to separating concerns better, the pull request fixes an error that you might have seen in your tests after installing [email protected] or 6.3.1.

actual: >
  null
stack: >
  Error: Can not call `.lookup` after the owner has been destroyed
    at Container.lookup
    at Class.lookup
    at THelper.getInjection
    at untrack
    at ComputedProperty.get
    at THelper.getter [as intl]

v6.3.1

4 months ago

I fixed the type issues introduced in 6.3.0. All helpers have a return type of string once again.

[!IMPORTANT] To resolve long-existing type issues, I had to change an implementation detail: In certain error cases, the helpers now return an empty string '' instead of undefined.

Since both values are considered falsy in *.hbs as well as *.{js,ts}, I'm hoping that changing the return value will be non-breaking in most cases.

Going forward, we'll try to limit the API and be more strict about types.

v6.3.0

4 months ago

I removed the base helper -format-base.js so that the code for each helper is easier to understand and maintain.

[!IMPORTANT] If you happened to create a helper that extends -format-base.js (private implementation), please extend the Helper class from @ember/component/helper and inject the intl service instead. For reference, please check the {{t}} helper.

Example of a migration
/* addon/helpers/legacy-format-money.ts */
import BaseHelper from 'ember-intl/helpers/-format-base';

import { legacyFormatMoney } from '../utils/index';

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Type 'typeof AbstractHelper' is not a constructor function type.
export default class LegacyFormatMoneyHelper extends BaseHelper {
  format(money, options): string {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore: Property 'intl' does not exist on type 'LegacyFormatMoneyHelper'
    return legacyFormatMoney(this.intl, money, options);
  }
}
/* addon/helpers/legacy-format-money.ts */
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import type { IntlService } from 'ember-intl';

import { legacyFormatMoney } from '../utils/index';

export default class LegacyFormatMoneyHelper extends Helper {
  @service declare intl: IntlService;

  constructor() {
    // eslint-disable-next-line prefer-rest-params
    super(...arguments);

    // @ts-expect-error: Property 'onLocaleChanged' is private and only accessible within class 'IntlService'.
    this.intl.onLocaleChanged(this.recompute, this);
  }

  compute([money], options): string {
    return legacyFormatMoney(this.intl, money, options);
  }
}

Compared to v6.2.2, the unpacked size may be slightly larger (~2 kB). On the plus side, a few type errors and hidden bugs (runtime errors) should be fixed now. Since the code change is large, I went with a minor release to be on the safe side.

[!WARNING] There is a known issue for Glint users, caused by the {{t}} helper returning SafeString and undefined as possible types. This will be addressed soon.

app/templates/form.hbs:1:14 - error TS2345: Argument of type 'string | SafeString | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

1 {{page-title (t "routes.form.title")}}
               ~~~~~~~~~~~~~~~~~~~~~~~

app/templates/form.hbs:6:6 - error TS2322: Type 'string | SafeString | undefined' is not assignable to type 'string | undefined'.
  Type 'SafeString' is not assignable to type 'string'.

6     @instructions={{t
       ~~~~~~~~~~~~

v6.2.2

4 months ago

If you encountered TypeScript errors of the following form after installing 6.2.0 or 6.2.1,

tests/integration/components/my-component.ts:59:23 - error TS2345: Argument of type 'string | SafeString' is not assignable to parameter of type 'string'.
  Type 'SafeString' is not assignable to type 'string'.

59   .hasText(t('some.key'));

please try installing 6.2.2 instead. As a temporary fix, I cast the return type of the test helper t() to be string.