Intl
An Elixir interface to internationalization functions modelled on the JavaScript Intl API.
If you are familiar with the JS Intl API, you already know how to use this library. The module names, function purposes, and option names mirror their JS counterparts — adapted to idiomatic Elixir conventions (snake_case, keyword options, {:ok, result} / {:error, reason} tuples).
Relationship to Localize
Intl is a thin shim over the Localize library which provides the full CLDR-based locale data and formatting engine. Each Intl module translates JS Intl option names and conventions into the corresponding Localize calls:
Intl.NumberFormatdelegates toLocalize.NumberandLocalize.Unit.Intl.DateTimeFormatdelegates toLocalize.DateTime,Localize.Date,Localize.Time, andLocalize.Interval.Intl.ListFormatdelegates toLocalize.List.Intl.DisplayNamesdelegates toLocalize.Territory,Localize.Language,Localize.Currency, andLocalize.Script.Intl.RelativeTimeFormatdelegates toLocalize.DateTime.Relative.Intl.PluralRulesdelegates toLocalize.Number.PluralRule.Intl.Collatordelegates toLocalize.Collation.Intl.DurationFormatdelegates toLocalize.Duration.Intl.SegmenterusesString.graphemes/1for grapheme segmentation and the optionalunicode_stringlibrary for word and sentence segmentation.
If you need lower-level control or access to features beyond the Intl API surface (such as unit conversion, message formatting, or calendar metadata), use Localize directly.
Compatibility and Differences
The full compatibility matrix is in the Compatibility guide. Key points:
Functional, not object-oriented. JS creates formatter instances with
new Intl.NumberFormat(locale, options). Elixir passes options directly:Intl.NumberFormat.format(number, options).snake_case options. JS
minimumFractionDigitsbecomes:minimum_fraction_digits.Tagged return tuples. All functions return
{:ok, result}or{:error, reason}. Bang variants (format!/2) raise on error.Collator returns atoms.
Intl.Collator.compare/3returns:lt,:eq, or:gtinstead of -1, 0, or 1.formatToPartsis not supported in any module. The underlying Localize library does not expose structured format parts.resolvedOptionsandsupportedLocalesOfare not wrapped. UseLocalize.available_locale_id?/1to check locale support directly.Segmenter output is simplified. Returns a flat list of strings rather than the JS iterable of rich segment objects.
Examples
Number Formatting
iex> Intl.NumberFormat.format(1_234_567.89, locale: :en)
{:ok, "1,234,567.89"}
iex> Intl.NumberFormat.format(1_234_567.89, locale: :de)
{:ok, "1.234.567,89"}
iex> Intl.NumberFormat.format(0.56, locale: :en, style: :percent)
{:ok, "56%"}
iex> Intl.NumberFormat.format(1234.5, locale: :en, style: :currency, currency: :USD)
{:ok, "$1,234.50"}Date and Time Formatting
iex> Intl.DateTimeFormat.format(~D[2025-03-15], locale: :en, date_style: :full)
{:ok, "Saturday, March 15, 2025"}
iex> Intl.DateTimeFormat.format(~D[2025-03-15], locale: :en, date_style: :short)
{:ok, "3/15/25"}List Formatting
iex> Intl.ListFormat.format(["Monday", "Tuesday", "Wednesday"], locale: :en)
{:ok, "Monday, Tuesday, and Wednesday"}
iex> Intl.ListFormat.format(["tea", "coffee", "juice"], locale: :en, type: :disjunction)
{:ok, "tea, coffee, or juice"}
iex> Intl.ListFormat.format(["lundi", "mardi", "mercredi"], locale: :fr)
{:ok, "lundi, mardi et mercredi"}Display Names
iex> Intl.DisplayNames.of("US", type: :region, locale: :en)
{:ok, "United States"}
iex> Intl.DisplayNames.of("DE", type: :region, locale: :fr)
{:ok, "Allemagne"}
iex> Intl.DisplayNames.of("fr", type: :language, locale: :en)
{:ok, "French"}
iex> Intl.DisplayNames.of(:Latn, type: :script, locale: :en)
{:ok, "Latin"}
iex> Intl.DisplayNames.of("JPY", type: :currency, locale: :en)
{:ok, "Japanese Yen"}
iex> Intl.DisplayNames.of(:gregorian, type: :calendar, locale: :en)
{:ok, "Gregorian Calendar"}Relative Time
iex> Intl.RelativeTimeFormat.format(-1, :day, locale: :en)
{:ok, "yesterday"}
iex> Intl.RelativeTimeFormat.format(3, :hour, locale: :en)
{:ok, "in 3 hours"}
iex> Intl.RelativeTimeFormat.format(-3, :day, locale: :fr)
{:ok, "il y a 3 jours"}Plural Rules
iex> Intl.PluralRules.select(1, locale: "en")
{:ok, :one}
iex> Intl.PluralRules.select(5, locale: "en")
{:ok, :other}
iex> Intl.PluralRules.select(2, locale: "ar")
{:ok, :two}Collation
iex> Intl.Collator.compare("ä", "z", locale: :de, sensitivity: :base)
:lt
iex> Intl.Collator.sort(["banana", "apple", "cherry"], locale: :en)
["apple", "banana", "cherry"]Duration Formatting
iex> Intl.DurationFormat.format(%{hours: 2, minutes: 30}, locale: :en)
{:ok, "2 hours and 30 minutes"}Text Segmentation
iex> Intl.Segmenter.segment("héllo", granularity: :grapheme)
{:ok, ["h", "é", "l", "l", "o"]}
iex> Intl.Segmenter.segment("Hello world", granularity: :word, trim: true)
{:ok, ["Hello", "world"]}
iex> Intl.Segmenter.segment("Hello. How are you?", granularity: :sentence)
{:ok, ["Hello. ", "How are you?"]}Locale Utilities
iex> Intl.get_canonical_locales(["en-us", "fr-fr"])
{:ok, ["en-US", "fr-FR"]}
iex> {:ok, calendars} = Intl.supported_values_of(:calendar)
iex> :gregorian in calendars
trueInstallation
Add intl to your list of dependencies in mix.exs:
def deps do
[
{:intl, "~> 0.1.0"}
]
end
Word and sentence segmentation require the optional unicode_string dependency:
{:unicode_string, "~> 1.8"}Locale Data
Intl delegates to Localize for locale data. The :en locale is always available. To use other locales, either pre-download their data at build time:
mix localize.download_locales de fr ja
Or enable runtime downloading in config/runtime.exs:
config :localize, :allow_runtime_locale_download, true
Locale data is loaded lazily into :persistent_term on first access. See the Localize documentation for full configuration details.