Protocol for locale-aware string formatting.
Localize.Chars provides a single dispatch point for formatting
any supported value as a localized string. It mirrors the
String.Chars protocol from Elixir core, but every implementation
is locale-aware and returns the standard Localize result tuple
{:ok, formatted} / {:error, exception}.
Examples
iex> Localize.Chars.to_string(1234.5, locale: :de)
{:ok, "1.234,5"}
iex> Localize.Chars.to_string(1234.5, locale: :en)
{:ok, "1,234.5"}
iex> Localize.Chars.to_string(~D[2025-07-10], locale: :en)
{:ok, "Jul 10, 2025"}
iex> {:ok, unit} = Localize.Unit.new(42, "kilometer")
iex> Localize.Chars.to_string(unit, format: :short, locale: :en)
{:ok, "42 km"}Built-in implementations
Types without a Localize-specific implementation fall through to
Kernel.to_string/1, which dispatches via String.Chars. This
mirrors the relationship between Localize.Chars and
String.Chars: where Localize knows how to format the type
in a locale-aware way it does so; otherwise it returns
{:ok, Kernel.to_string(value)}. Types with no String.Chars
implementation either (tuples, maps without an explicit impl,
PIDs, references, anonymous functions) raise the same
Protocol.UndefinedError they would from Kernel.to_string/1
— Localize.Chars does not invent a representation for them.
Caveats
The
Listimplementation formats the list as a locale-aware conjunction ("a, b, and c") by delegating toLocalize.List.to_string/2, which itself recursively formats each element viaLocalize.to_string/2so dates, numbers, units, etc. inside a list pick up the outer locale and other options (e.g.currency: :USD). Charlists are special-cased: a printable list of integer codepoints (~c"hello") is converted viaKernel.to_string/1rather than being joined digit-by-digit, mirroring howString.Chars'sListimpl handles them.The
Localize.LanguageTagimplementation produces the localized display name ("English (United States)"), not the canonical BCP-47 string. The canonical form is still available viaKernel.to_string/1, which uses the internalString.Charsprotocol.
Adding implementations for your own types
Implement the protocol for any struct you want to support
through Localize.to_string/1 and Localize.to_string/2:
defimpl Localize.Chars, for: MyApp.Money do
def to_string(money), do: Localize.Chars.to_string(money, [])
def to_string(%MyApp.Money{amount: amount, currency: currency}, options) do
options = Keyword.put_new(options, :currency, currency)
Localize.Number.to_string(amount, options)
end
endAfter this, Localize.to_string(%MyApp.Money{...}, locale: :de)
works exactly like the built-in implementations.
Summary
Functions
Formats value as a localized string with default options.
Formats value as a localized string with the given options.
Types
@type t() :: term()
All the types that implement this protocol.
Functions
@spec to_string(t()) :: {:ok, String.t()} | {:error, Exception.t()}
Formats value as a localized string with default options.
Equivalent to to_string(value, []).
Returns
{:ok, formatted_string}on success.{:error, exception}if formatting fails. The exception is a struct (e.g.%Localize.UnknownLocaleError{}).
@spec to_string(t(), Keyword.t()) :: {:ok, String.t()} | {:error, Exception.t()}
Formats value as a localized string with the given options.
Each implementation accepts the option set of its underlying
formatter. Every implementation accepts at least :locale.
Arguments
valueis any term that has aLocalize.Charsimplementation.optionsis a keyword list of options forwarded to the underlying formatter.
Returns
{:ok, formatted_string}on success.{:error, exception}if formatting fails.