Skip to content

Instantly share code, notes, and snippets.

@sukima
Created September 18, 2022 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sukima/daf280bed5a368431e10fb2ef3f06905 to your computer and use it in GitHub Desktop.
Save sukima/daf280bed5a368431e10fb2ef3f06905 to your computer and use it in GitHub Desktop.
An enhanclement to handle internationalized lists in Ember.

Ember-intl uses the format.js library under the hood which uses the ICU Message Syntax. This syntax has many provisions for handling different types of interpolated data such as plural and select. But it lacks list formatting. Deep in the ICU Documentation it hints at the correct course of action:

Format the parameters separately (recommended)

You can format the parameter as you need before calling MessageFormat, and then passing the resulting string as a parameter to MessageFormat.

ICU Messages § Argument formatting

Luckily, JavaScript has a solution for this in the specs with Intl.ListFormat. All we need to do is integrate this with our use of ember-intl and the ICU message formatting syntax.

For this I’ll use this example translation

favorite-pasta-list: >-
  {count, plural,
    one {My favorite pasta type is {pastas}.}
    other {My favorite pasta types are {pastas}.}
  }

Side note:

It is tempting to cover only a minimal part of a message string with a complex argument (e.g., plural). However, this is difficult for translators for two reasons: 1. They might have trouble understanding how the sentence fragments in the argument sub-messages interact with the rest of the sentence, and 2. They will not know whether and how they can shrink or grow the extent of the part of the sentence that is inside the argument to make the whole message work for their language.

Recommendation: If possible, use complex arguments as the outermost structure of a message, and write full sentences in their sub-messages.

ICU Messages § Complex Argument Types (emphasis theirs)

With this message syntax we will need to control two arguments: count and pastas which will be a number and a string respectively. For this example I will use the following template:

{{#let (array "Fettuccine" "Rotelle" "Festoni") as |favoritePastas|}}
  <p>
    {{t "t-bada55.favorite-pasta-list"
      count=favoritePastas.length
      pastas=(format-list favoritePastas)
    }}
  </p>
{{/let}}

Last is to make the format-list helper:

import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import type IntlService from 'ember-intl/services/intl';

interface ListFormatOptions {
  localeMatcher?: 'best fit' | 'lookup';
  style?: 'long' | 'short' | 'narrow';
  type?: 'conjunction' | 'disjunction' | 'unit';
}

export interface Args {
  Positional: [string[]];
  Named: ListFormatOptions;
}

export function formatList(
  list: string[],
  options: ListFormatOptions,
  intl: IntlService,
) {
  // Prevent TS error: https://github.com/microsoft/TypeScript/issues/46907
  // @ts-ignore
  let formatter = new Intl.ListFormat(intl.locales, options);
  return formatter.format(list);
}

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

  compute([list]: Args['Positional'], options: Args['Named']) {
    return formatList(list, options, this.intl);
  }
}

And this will produce (for en-us anyway)…

<p>My favorite pasta types are Fettuccine, Rotelle, and Festoni.</p>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment