# `Localize`
[🔗](https://github.com/elixir-localize/localize/blob/v0.9.0/lib/localize.ex#L1)

Locale-aware formatting, validation, and data access built on the
Unicode CLDR repository.

Localize consolidates the functionality of the `ex_cldr_*` library
family into a single package with no compile-time backend
configuration. All CLDR data is loaded at runtime from ETF and JSON
files and cached in `:persistent_term` on first access.

## Primary usage modules

* `Localize.Number` — format numbers, decimals, percentages, and
  currencies.

* `Localize.Date` — format dates using CLDR calendar patterns.

* `Localize.Time` — format times using CLDR calendar patterns.

* `Localize.DateTime` — format date-times with combined date and
  time patterns.

* `Localize.Interval` — format date, time, and datetime intervals.

* `Localize.Unit` — format units of measure with plural-aware
  patterns (e.g., "3 kilometers", "1.5 hours").

* `Localize.List` — format lists with locale-appropriate
  conjunctions and disjunctions (e.g., "a, b, and c").

* `Localize.Currency` — currency metadata, validation, and
  territory-to-currency mapping.

* `Localize.Territory` — territory display names, containment,
  subdivisions, and emoji flags.

* `Localize.Language` — language display names.

* `Localize.Collation` — locale-sensitive string sorting using the
  Unicode Collation Algorithm.

* `Localize.Locale.LocaleDisplay` — full locale display names
  (e.g., "English (United States)").

* `Localize.Calendar` — calendar era names, day/month names, and
  day period names.

## Locale management

Localize maintains a per-process current locale and an
application-wide default locale:

* `get_locale/0` — returns the current process locale, falling
  back to `default_locale/0`.

* `put_locale/1` — sets the current process locale.

* `with_locale/2` — executes a function with a temporary locale.

* `default_locale/0` — returns the application-wide default,
  resolved from environment variables and application config.

* `put_default_locale/1` — overrides the application-wide default.

All formatting functions default their `:locale` option to
`get_locale/0` when no locale is explicitly provided.

## This module

This module also provides text formatting helpers (`quote/2`,
`ellipsis/2`), validators for locales, territories, scripts,
calendars, number systems, currencies, and measurement systems,
and accessors for known locale names and territory lists.

## Optional NIF

An optional NIF-based implementation of selected algorithms
(currently Unicode normalisation and collation sort-key generation)
can be enabled by setting `LOCALIZE_NIF=true` at compile time. See
`Localize.Nif` for details.

# `locale`

```elixir
@type locale() :: locale_id() | Localize.LanguageTag.t()
```

A locale identifier atom or a language tag struct.

# `locale_id`

```elixir
@type locale_id() :: atom()
```

A locale identifier. That is, known to CLDR

# `all_locale_ids`

```elixir
@spec all_locale_ids() :: [atom()]
```

Returns a list of all known CLDR locale name atoms.

### Returns

* A list of locale name atoms.

### Examples

    iex> locales = Localize.all_locale_ids()
    iex> :en in locales
    true

# `all_locale_ids`

```elixir
@spec all_locale_ids(:basic | :moderate | :modern) :: [atom()]
```

Returns a list of all known CLDR locale ID atoms at or above the
given coverage level.

CLDR assigns each locale a coverage level of `:basic`,
`:moderate`, or `:modern`. A locale at the `:modern` level is
also included when requesting `:moderate` or `:basic`. A locale
at `:moderate` is also included when requesting `:basic`.

### Arguments

* `level` is one of `:basic`, `:moderate`, or `:modern`.

### Returns

* A sorted list of locale ID atoms.

### Examples

    iex> locales = Localize.all_locale_ids(:modern)
    iex> :en in locales
    true

    iex> length(Localize.all_locale_ids(:basic)) >= length(Localize.all_locale_ids(:modern))
    true

# `available_locale_id?`

```elixir
@spec available_locale_id?(atom() | String.t()) :: boolean()
```

Returns whether a locale name is available in the CLDR
repository.

### Arguments

* `locale_name` is a locale identifier atom or string.

### Returns

* `true` if the locale is available in CLDR.

* `false` otherwise.

### Examples

    iex> Localize.available_locale_id?(:en)
    true

    iex> Localize.available_locale_id?(:zzzz)
    false

# `default_locale`

```elixir
@spec default_locale() :: Localize.LanguageTag.t()
```

Returns the application-wide default locale as a
`t:Localize.LanguageTag.t/0`.

The default locale is resolved once on first access using the
following precedence chain:

1. A value previously set via `put_default_locale/1`.

2. The `LOCALIZE_DEFAULT_LOCALE` environment variable.

3. The `:default_locale` key in the `:localize` application
   environment (e.g., `config :localize, default_locale: :fr`).

4. The `LANG` environment variable (e.g., `"en_US.UTF-8"`),
   with the charset suffix stripped.

5. `:en` as a final fallback.

The resolved locale is validated via `validate_locale/1` and
cached in `:persistent_term` so subsequent calls are free. If
any source provides an invalid locale, a warning is logged and
the next source in the chain is tried.

### Returns

* A `t:Localize.LanguageTag.t/0`.

### Examples

    iex> %Localize.LanguageTag{} = Localize.default_locale()
    iex> Localize.default_locale().cldr_locale_id
    :en

# `ellipsis`

```elixir
@spec ellipsis(String.t() | [String.t()], Keyword.t()) ::
  {:ok, String.t()} | {:error, Exception.t()}
```

Adds locale-specific ellipsis characters to a string or between
two strings.

Uses the CLDR ellipsis patterns for the given locale.

### Arguments

* `string` is either a single string or a list of two strings
  to join with an ellipsis between them.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`. The default is `:en`.

* `:location` determines where the ellipsis is placed. Valid
  values are `:after` (append), `:before` (prepend), and
  `:between` (medial, requires a two-element list). The default
  is `:after` for a single string and `:between` for a list.

* `:format` is either `:sentence` or `:word`. The `:word` format
  includes a space between the text and the ellipsis. The default
  is `:sentence`.

### Returns

* `{:ok, ellipsized_string}` with locale-specific ellipsis applied.

* `{:error, exception}` if the locale data cannot be loaded.

### Examples

    iex> Localize.ellipsis("And so on")
    {:ok, "And so on…"}

    iex> Localize.ellipsis("And so on", location: :before)
    {:ok, "…And so on"}

    iex> Localize.ellipsis(["start", "end"])
    {:ok, "start…end"}

    iex> Localize.ellipsis("And so on", format: :word)
    {:ok, "And so on …"}

# `get_locale`

```elixir
@spec get_locale() :: Localize.LanguageTag.t()
```

Returns the locale for the current process as a
`t:Localize.LanguageTag.t/0`.

If no locale has been set for the current process via
`put_locale/1`, returns `default_locale/0`.

### Returns

* A `t:Localize.LanguageTag.t/0`.

### Examples

    iex> %Localize.LanguageTag{} = Localize.get_locale()
    iex> Localize.get_locale().cldr_locale_id
    :en

# `known_calendars`

```elixir
@spec known_calendars() :: [atom(), ...]
```

Returns a list of all known CLDR calendar types
as atoms.

The calendar types are internal CLDR values to
identify localized month and day names, era names
and other calendarical data.

The calendars defined in the [localzie_calendars](https://hex.pm/packages/localize_calendars)
embed the appropriate CLDR calendar type to support
localization.

### Returns

* A list of calendar type atoms.

### Examples

    iex> Localize.known_calendars()
    [:gregorian, :buddhist, :chinese, :coptic, :dangi, :ethiopic,
     :ethiopic_amete_alem, :hebrew, :indian, :islamic, :islamic_civil,
     :islamic_rgsa, :islamic_tbla, :islamic_umalqura, :japanese, :persian, :roc]

# `known_number_systems`

```elixir
@spec known_number_systems() :: [atom()]
```

Returns a list of all known CLDR number system atoms.

### Returns

* A list of number system atoms.

### Examples

    iex> systems = Localize.known_number_systems()
    iex> :latn in systems
    true

# `measurement_systems`

```elixir
@spec measurement_systems() :: [atom()]
```

Returns the list of canonical measurement system atoms.

The canonical names are derived from `bcp47/measure.xml` and
mapped to the short forms `:metric`, `:us`, and `:uk`.

### Returns

* A list of measurement system atoms.

### Examples

    iex> Localize.measurement_systems()
    [:metric, :uk, :us]

# `put_default_locale`

```elixir
@spec put_default_locale(Localize.LanguageTag.t() | atom() | String.t()) ::
  {:ok, Localize.LanguageTag.t()} | {:error, Exception.t()}
```

Sets the application-wide default locale.

The locale is validated via `validate_locale/1` and the
resulting `t:Localize.LanguageTag.t/0` is cached in
`:persistent_term`. This value is used as the fallback when
no process-level locale has been set via `put_locale/1`.

### Arguments

* `locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`.

### Returns

* `{:ok, language_tag}` on success.

* `{:error, exception}` if the locale is not valid.

### Examples

    iex> {:ok, tag} = Localize.put_default_locale(:fr)
    iex> tag.cldr_locale_id
    :fr
    iex> Localize.default_locale().cldr_locale_id
    :fr
    iex> {:ok, _} = Localize.put_default_locale(:en)

# `put_locale`

```elixir
@spec put_locale(Localize.LanguageTag.t() | atom() | String.t()) ::
  {:ok, Localize.LanguageTag.t()} | {:error, Exception.t()}
```

Sets the locale for the current process.

The locale is validated via `validate_locale/1` and stored
in the process dictionary as a `t:Localize.LanguageTag.t/0`.
It is used as the default by all formatting functions in this
process. It does not propagate to spawned processes — use
`with_locale/2` or explicitly pass the locale when spawning
tasks.

### Arguments

* `locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`.

### Returns

* `{:ok, language_tag}` on success. The previous locale (or
  `nil`) can be retrieved from the process dictionary before
  calling this function if needed.

* `{:error, exception}` if the locale is not valid.

### Examples

    iex> {:ok, _} = Localize.put_locale(:de)
    iex> Localize.get_locale().cldr_locale_id
    :de
    iex> {:ok, _} = Localize.put_locale(:en)

# `put_supported_locales`

```elixir
@spec put_supported_locales([atom()]) :: :ok
```

Sets the list of supported locales in `:persistent_term`.

This function does not modify the application configuration.
It directly updates the runtime cache that `supported_locales/0`
reads from.

### Arguments

* `locales` is a list of locale ID atoms.

### Returns

* `:ok`.

### Examples

    iex> original = Localize.supported_locales()
    iex> Localize.put_supported_locales([:en, :fr, :de])
    :ok
    iex> Localize.put_supported_locales(original)
    :ok

    iex> original = Localize.supported_locales()
    iex> Localize.put_supported_locales([:en, :fr, :de])
    :ok
    iex> Localize.supported_locales()
    [:en, :fr, :de]
    iex> Localize.put_supported_locales(original)
    :ok

# `quote`

```elixir
@spec quote(String.t(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}
```

Wraps a string in locale-specific quotation marks.

Uses the CLDR delimiters data for the given locale to apply
the appropriate opening and closing quotation marks.

### Arguments

* `string` is the text to quote.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`. The default is `:en`.

* `:style` is either `:default` or `:variant`. The default
  style uses the primary quotation marks for the locale. The
  `:variant` style uses the alternate (nested) quotation marks.

### Returns

* `{:ok, quoted_string}` where `quoted_string` has locale-specific
  quotation marks added.

* `{:error, exception}` if the locale data cannot be loaded.

### Examples

    iex> Localize.quote("Hello")
    {:ok, "“Hello”"}

    iex> Localize.quote("Hello", style: :variant)
    {:ok, "‘Hello’"}

# `supported_locales`

```elixir
@spec supported_locales() :: [atom()]
```

Returns the list of supported locales configured via
`config :localize, supported_locales: [...]` or
`Localize.all_locale_ids/0`.

The returned list contains canonical CLDR locale ID atoms,
resolved and validated at application startup.

### Returns

* A list of locale ID atoms.

# `to_string`

```elixir
@spec to_string(term()) :: {:ok, String.t()} | {:error, Exception.t()}
```

Formats `value` as a localized string.

Delegates to `Localize.Chars.to_string/1`. Equivalent to
calling `Localize.to_string(value, [])`.

Built-in locale-aware implementations exist for `Integer`,
`Float`, `Decimal`, `Date`, `Time`, `DateTime`, `NaiveDateTime`,
`Range`, `BitString`, `List`, `Localize.Unit`, `Localize.Duration`,
`Localize.LanguageTag`, and `Localize.Currency`. Any other type
falls through to `Kernel.to_string/1`, so atoms, charlists,
booleans, and `nil` produce the same output they would from
`Kernel.to_string/1`. Types with no `String.Chars` implementation
either (tuples, plain maps, PIDs, references, anonymous functions)
raise `Protocol.UndefinedError`.

See `Localize.Chars` for the full list and instructions on
adding implementations for your own types.

> #### `Kernel.to_string/1` shadowing {: .info}
>
> `Kernel.to_string/1` is auto-imported into every module. If
> you `import Localize` in your own code, the import shadows
> the kernel function inside that module. Use the qualified
> form `Localize.to_string/1` (recommended) or
> `import Localize, except: [to_string: 1, to_string: 2]`.

### Returns

* `{:ok, formatted_string}` on success.

* `{:error, exception}` on failure.

### Examples

    iex> Localize.to_string(1234.5, locale: :de)
    {:ok, "1.234,5"}

    iex> Localize.to_string(~D[2025-07-10], locale: :en)
    {:ok, "Jul 10, 2025"}

# `to_string`

```elixir
@spec to_string(term(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}
```

Formats `value` as a localized string with the given options.

Delegates to `Localize.Chars.to_string/2`. See `to_string/1`
for the list of supported types and the `Kernel.to_string/1`
shadowing note.

### Arguments

* `value` is any term that has a `Localize.Chars` implementation.

* `options` is a keyword list of options forwarded to the
  underlying formatter. Every implementation accepts at least
  `:locale`.

### Returns

* `{:ok, formatted_string}` on success.

* `{:error, exception}` on failure.

### Examples

    iex> Localize.to_string(1234.5, locale: :en)
    {:ok, "1,234.5"}

    iex> {:ok, unit} = Localize.Unit.new(42, "kilometer")
    iex> Localize.to_string(unit, format: :short, locale: :en)
    {:ok, "42 km"}

# `to_string!`

```elixir
@spec to_string!(term()) :: String.t()
```

Same as `to_string/1` but returns the formatted string directly
or raises on error.

### Examples

    iex> Localize.to_string!(1234.5, locale: :de)
    "1.234,5"

# `to_string!`

```elixir
@spec to_string!(term(), Keyword.t()) :: String.t()
```

Same as `to_string/2` but returns the formatted string directly
or raises on error.

### Examples

    iex> Localize.to_string!(~D[2025-07-10], locale: :de, format: :long)
    "10. Juli 2025"

# `validate_calendar`

```elixir
@spec validate_calendar(atom() | String.t()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Validates a calendar name.

Checks the calendar against the list of known CLDR calendars.
The string `"gregory"` is accepted as an alias for `:gregorian`.

### Arguments

* `calendar` is a calendar name atom or string.

### Returns

* `{:ok, calendar_atom}` where `calendar_atom` is the
  normalised calendar atom.

* `{:error, exception}` if the calendar is not known.

### Examples

    iex> Localize.validate_calendar(:gregorian)
    {:ok, :gregorian}

    iex> Localize.validate_calendar("persian")
    {:ok, :persian}

    iex> Localize.validate_calendar(:unknown)
    {:error, %Localize.UnknownCalendarError{calendar: :unknown}}

# `validate_locale`

```elixir
@spec validate_locale(Localize.LanguageTag.t() | String.t() | atom()) ::
  {:ok, Localize.LanguageTag.t()} | {:error, Exception.t()}
```

Validates a locale identifier or language tag.

Ensures that the given locale can be resolved to a known CLDR
locale. When given a binary locale identifier, it is parsed into
a `t:Localize.LanguageTag.t/0`. When given an existing language tag
whose `:cldr_locale_id` is not yet populated, a best-match
resolution is attempted using `Localize.LanguageTag.best_match/3`.

POSIX-style locale names (e.g. `"pt_BR"`, `"zh_Hans"`) are
accepted — underscores are normalized to hyphens before parsing.

## Locale resolution

The `:cldr_locale_id` field on the returned language tag is
derived by matching the parsed tag against a list of candidate
locale IDs:

* If `config :localize, supported_locales: [...]` is
  configured, the candidate list is the resolved supported
  locales. This restricts matching to only the locales your
  application explicitly supports.

* If `:supported_locales` is not configured, the candidate
  list is all CLDR locale IDs.

Validated locale results are cached in an ETS table so
repeated calls with the same identifier are fast (~1µs).

> #### Always returns a result {: .warning}
>
> This function uses the CLDR locale matching algorithm, which
> is designed to **always return a result** when the candidate
> list is non-empty — even if the match is very distant. For
> example, `validate_locale("xyzzy")` will succeed and return
> some CLDR locale (typically the first candidate), not an error.
> This is the correct CLDR behaviour for user-facing locale
> negotiation (a distant match is better than no match), but it
> means the returned locale may not be what the caller expected.
>
> For strict validation (e.g. resolving configuration values),
> use `Localize.LanguageTag.best_match/3` with a threshold of
> `0` to accept only exact matches after likely-subtag
> resolution.

### Arguments

* `locale` is a locale identifier binary, an atom, or a
  `t:Localize.LanguageTag.t/0`.

### Returns

* `{:ok, language_tag}` where `language_tag` is a
  `t:Localize.LanguageTag.t/0` with a populated `:cldr_locale_id`.

* `{:error, Localize.InvalidLocaleError.t()}` if the locale
  identifier cannot be parsed into a valid language tag.

* `{:error, Localize.UnknownLocaleError.t()}` if the locale
  parses successfully but does not match any known CLDR locale
  (or any supported locale, when configured).

### Examples

    iex> {:ok, tag} = Localize.validate_locale("en")
    iex> tag.cldr_locale_id
    :en

    iex> {:ok, tag} = Localize.validate_locale("pt_BR")
    iex> tag.cldr_locale_id
    :pt

# `validate_measurement_system`

```elixir
@spec validate_measurement_system(atom() | String.t()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Validates a measurement system type.

Accepts canonical names (`:metric`, `:us`, `:uk`) as well as
aliases defined in CLDR (`:imperial`, `:ussystem`, `:uksystem`).
Aliases are resolved to the canonical short name.

### Arguments

* `system` is a measurement system atom or string.

### Returns

* `{:ok, canonical_atom}` where `canonical_atom` is the
  canonical measurement system atom.

* `{:error, exception}` if the measurement system is not known.

### Examples

    iex> Localize.validate_measurement_system(:metric)
    {:ok, :metric}

    iex> Localize.validate_measurement_system("us")
    {:ok, :us}

    iex> Localize.validate_measurement_system(:imperial)
    {:ok, :uk}

    iex> Localize.validate_measurement_system(:ussystem)
    {:ok, :us}

    iex> Localize.validate_measurement_system(:klingon)
    {:error, %Localize.UnknownMeasurementSystemError{measurement_system: :klingon}}

# `validate_number_system`

```elixir
@spec validate_number_system(atom() | String.t()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Validates a number system name.

Checks the number system against the list of known CLDR
number systems.

### Arguments

* `number_system` is a number system name atom or string.

### Returns

* `{:ok, number_system_atom}` where `number_system_atom` is the
  normalised number system atom.

* `{:error, exception}` if the number system is not known.

### Examples

    iex> Localize.validate_number_system(:latn)
    {:ok, :latn}

    iex> Localize.validate_number_system("arab")
    {:ok, :arab}

    iex> Localize.validate_number_system(:unknown)
    {:error, %Localize.UnknownNumberSystemError{number_system: :unknown}}

# `validate_script`

```elixir
@spec validate_script(atom() | String.t()) :: {:ok, atom()} | {:error, Exception.t()}
```

Validates a script code.

Normalises the script code (capitalised form, e.g., `"Latn"`)
and checks it against the CLDR validity data.

### Arguments

* `script` is a script code atom or string.

### Returns

* `{:ok, script_atom}` where `script_atom` is the normalised
  script atom.

* `{:error, exception}` if the script is not known.

### Examples

    iex> Localize.validate_script(:Latn)
    {:ok, :Latn}

    iex> Localize.validate_script("latn")
    {:ok, :Latn}

    iex> Localize.validate_script(:Xyzq)
    {:error, %Localize.UnknownScriptError{script: :Xyzq}}

# `validate_territory`

```elixir
@spec validate_territory(atom() | String.t() | integer()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Validates a territory code.

Normalises the territory code and checks it against the CLDR
validity data. Integer codes are zero-padded (e.g., `1` becomes
`"001"`). String codes are uppercased.

### Arguments

* `territory` is a territory code atom, string, or integer.

### Returns

* `{:ok, territory_atom}` where `territory_atom` is the
  normalised territory atom.

* `{:error, exception}` if the territory is not known.

### Examples

    iex> Localize.validate_territory(:US)
    {:ok, :US}

    iex> Localize.validate_territory("us")
    {:ok, :US}

    iex> Localize.validate_territory(:ZZZZ)
    {:error, %Localize.UnknownTerritoryError{territory: :ZZZZ}}

# `validate_territory_subdivision`

```elixir
@spec validate_territory_subdivision(atom() | String.t()) ::
  {:ok, atom()} | {:error, Exception.t()}
```

Validates a territory subdivision code.

Normalises the subdivision code (lowercased) and checks it
against the CLDR validity data.

### Arguments

* `subdivision` is a subdivision code atom or string.

### Returns

* `{:ok, subdivision_atom}` where `subdivision_atom` is the
  normalised subdivision atom.

* `{:error, exception}` if the subdivision is not known.

### Examples

    iex> Localize.validate_territory_subdivision(:usca)
    {:ok, :usca}

    iex> Localize.validate_territory_subdivision("gbeng")
    {:ok, :gbeng}

    iex> Localize.validate_territory_subdivision(:zzzzz)
    {:error, %Localize.UnknownSubdivisionError{subdivision: :zzzzz}}

# `version`

```elixir
@spec version() :: Version.t()
```

Returns the CLDR version this build of Localize targets.

The version is a `t:Version.t/0` whose major and minor components
come from `priv/localize/version` (the CLDR release version) and
whose patch component comes from `priv/localize/localize_patch_version`
(Localize's per-release patch counter).

The value is read once on first access and cached in
`:persistent_term`.

### Returns

* A `t:Version.t/0` representing the CLDR version.

### Examples

    iex> %Version{} = Localize.version()

# `with_locale`

```elixir
@spec with_locale(Localize.LanguageTag.t() | atom() | String.t(), (-&gt; result)) ::
  result
when result: any()
```

Executes a function with a temporary process locale.

Sets the process locale to `locale`, executes `fun`, then
restores the previous locale regardless of whether `fun`
raises or throws.

### Arguments

* `locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`.

* `fun` is a zero-arity function to execute.

### Returns

* The return value of `fun`.

* Raises if the locale is not valid.

### Examples

    iex> Localize.with_locale(:ja, fn -> Localize.get_locale().cldr_locale_id end)
    :ja

    iex> Localize.get_locale().cldr_locale_id
    :en

---

*Consult [api-reference.md](api-reference.md) for complete listing*
