ICU MessageFormat 1 for Localize: arguments, formatted arguments and the plural, select and selectordinal selectors, formatted with Localize's locales, plural rules and formatters. It is a port of the MessageFormat 1 support in ex_cldr_messages, so an application that moves from ex_cldr to Localize keeps its MessageFormat 1 translations and msgids unchanged. Localize itself formats MessageFormat 2.

Features

  • MessageFormat 1 syntax — arguments, formatted arguments, the plural, select and selectordinal selectors with offset: and explicit =n clauses, #, and ICU apostrophe quoting.

  • Localize formatting — numbers, currencies, spelled-out numbers, ordinals, dates, times, units, lists and plural categories from Localize's CLDR data, and money through ex_money 6 when it is a dependency.

  • Compatible with ex_cldr_messages — the same API, options and output, so messages and canonical msgids carry over. The migration guide lists every difference.

  • Gettext interpolation — Localize.Message.MF1.Gettext.Interpolation formats a Gettext backend's messages as MessageFormat 1.

  • Canonical messages — canonical_message/2 and the ~M sigil give a message a canonical form for use as a msgid, and jaro_distance/3 compares two messages.

  • No backend module — the locale is Localize's, and formatting options are arguments.

  • Never raises on invalid input — every function except the ! functions returns {:ok, result} or {:error, exception}.

Supported Elixir and OTP versions

Localize MF1 requires Elixir 1.17+ and Erlang/OTP 26+.

On OTP 26, also add {:json_polyfill, "~> 0.2 or ~> 1.0"} to your dependencies. It provides the OTP 27+ :json module that Localize uses. OTP 27 and later need no extra dependency.

Installation

Add localize_mf1 to your dependencies in mix.exs, and ex_money if your messages format money arguments:

def deps do
  [
    {:localize_mf1, "~> 0.1"},
    {:ex_money, "~> 6.0"}
  ]
end

Localize ships with the en locale. Download the data for any other locale your application uses at build time with mix localize.download_locales, for example mix localize.download_locales fr de. See mix localize.download_locales.

Quick start

Arguments are bound by name, from a keyword list or a map, or by position:

iex> Localize.Message.MF1.format("Hello {name}!", name: "World")
{:ok, "Hello World!"}

iex> Localize.Message.MF1.format("{0} and {1}", ["this", "that"])
{:ok, "this and that"}

Formatted arguments use Localize's formatters in the current locale, or the :locale option:

iex> Localize.Message.MF1.format("{count, number} jelly beans", [count: 1234], locale: :de)
{:ok, "1.234 jelly beans"}

iex> Localize.Message.MF1.format("Due {due, date, long}", due: ~D[2026-10-27])
{:ok, "Due October 27, 2026"}

plural and selectordinal choose a clause by the locale's plural rules, and select by a value:

iex> Localize.Message.MF1.format("{count, plural, =0 {no items} one {# item} other {# items}}", count: 3)
{:ok, "3 items"}

iex> Localize.Message.MF1.format("{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}", place: 22)
{:ok, "22nd"}

iex> Localize.Message.MF1.format("{gender, select, female {She} male {He} other {They}} replied", gender: "female")
{:ok, "She replied"}

A canonical form ignores layout white space, for use as a msgid:

iex> Localize.Message.MF1.canonical_message("{count, plural,\n  other {# items}\n  one {# item}}")
{:ok, "{count, plural, one {# item} other {# items}}"}

Gettext

Name the interpolation module in a Gettext backend, and its messages are formatted as MessageFormat 1:

defmodule MyApp.Gettext do
  use Gettext.Backend,
    otp_app: :my_app,
    interpolation: Localize.Message.MF1.Gettext.Interpolation
end

The ~M sigil of Localize.Message.MF1.Sigils canonicalizes a message at compile time, so a change of layout does not make a new msgid.

Documentation

The full documentation is published on HexDocs.

License

Localize MF1 is released under the Apache License 2.0.