defmodule Intl do @moduledoc """ An Elixir interface to internationalization functions modelled on the [JavaScript Intl API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). This module delegates to the `Localize` library for locale-aware formatting and provides the top-level `Intl` static methods: `get_canonical_locales/1` and `supported_values_of/1`. The sub-modules mirror the JS Intl constructors adapted to idiomatic Elixir: options are passed as keyword lists, return values use `{:ok, result}` / `{:error, reason}` tuples, and bang variants are provided for convenience. ## Sub-modules * `Intl.NumberFormat` — locale-aware number, currency, percent, and unit formatting. * `Intl.DateTimeFormat` — locale-aware date and time formatting. * `Intl.ListFormat` — locale-aware list joining with conjunctions, disjunctions, or units. * `Intl.DisplayNames` — localized display names for regions, languages, and currencies. * `Intl.RelativeTimeFormat` — relative time strings such as "3 days ago" or "in 2 hours". * `Intl.PluralRules` — plural category selection (zero, one, two, few, many, other). * `Intl.Collator` — locale-aware string comparison and sorting. * `Intl.DurationFormat` — locale-aware duration formatting. * `Intl.Segmenter` — text segmentation into graphemes, words, or sentences. """ @doc """ Returns canonical locale name strings for the given locale identifiers. Parses and canonicalizes each locale string according to BCP 47 / RFC 5646 rules. ### Arguments * `locales` is a locale identifier string or a list of locale identifier strings. ### Returns * `{:ok, list}` where `list` is a list of canonical locale name strings. * `{:error, reason}` if any locale identifier cannot be parsed. ### Examples iex> Intl.get_canonical_locales("en-us") {:ok, ["en-US"]} iex> Intl.get_canonical_locales(["en-us", "fr-FR"]) {:ok, ["en-US", "fr-FR"]} """ @spec get_canonical_locales(String.t() | [String.t()]) :: {:ok, [String.t()]} | {:error, term()} def get_canonical_locales(locales) when is_binary(locales) do get_canonical_locales([locales]) end def get_canonical_locales(locales) when is_list(locales) do results = Enum.reduce_while(locales, [], fn locale, accumulator -> with {:ok, tag} <- Localize.LanguageTag.parse(locale), {:ok, canonical} <- Localize.LanguageTag.canonicalize(tag) do {:cont, [Localize.LanguageTag.to_string(canonical) | accumulator]} else {:error, _} = error -> {:halt, error} end end) case results do {:error, _} = error -> error list when is_list(list) -> {:ok, Enum.reverse(list)} end end @doc """ Returns canonical locale name strings, raising on error. Same as `get_canonical_locales/1` but returns the list directly or raises an exception. ### Arguments * `locales` is a locale identifier string or a list of locale identifier strings. ### Returns * A list of canonical locale name strings. ### Examples iex> Intl.get_canonical_locales!("en-us") ["en-US"] """ @spec get_canonical_locales!(String.t() | [String.t()]) :: [String.t()] | no_return() def get_canonical_locales!(locales) do case get_canonical_locales(locales) do {:ok, list} -> list {:error, exception} -> raise exception end end @doc """ Returns the supported values for a given internationalization key. ### Arguments * `key` is one of `:calendar`, `:currency`, `:numbering_system`, or `:unit`. ### Returns * `{:ok, list}` where `list` is a list of supported value atoms or strings. * `{:error, reason}` if the key is not recognized. ### Examples iex> {:ok, calendars} = Intl.supported_values_of(:calendar) iex> :gregorian in calendars true iex> {:ok, systems} = Intl.supported_values_of(:numbering_system) iex> :latn in systems true """ @spec supported_values_of(:calendar | :currency | :numbering_system | :unit) :: {:ok, list()} | {:error, term()} def supported_values_of(:calendar) do {:ok, Localize.known_calendars()} end def supported_values_of(:numbering_system) do {:ok, Localize.known_number_systems()} end def supported_values_of(:currency) do {:ok, Localize.Currency.known_currency_codes()} end def supported_values_of(:unit) do {:ok, Localize.Unit.known_units_by_category()} end def supported_values_of(key) do {:error, ArgumentError.exception( "Unsupported key: #{inspect(key)}. " <> "Expected one of :calendar, :currency, :numbering_system, or :unit" )} end end