Localize.Message.Sigils (Localize v0.36.0)

Copy Markdown View Source

Implements sigils for ICU MessageFormat 2 messages.

Two sigils are provided:

  • ~M — validates and canonicalises an MF2 message at compile time. Useful for static messages that need a stable canonical form (for example, as keys in a translation table or seed data).

  • ~t — compile-time translation sigil. Combines Gettext lookup with MF2 interpolation. Elixir-style #{expr} interpolations become MF2 {$name} placeholders with bindings derived automatically from the interpolated expression. Requires the calling module to opt in via use Localize.Message.Sigils, backend: MyApp.Gettext.

Compile-time validation

Both sigils parse the message at compile time. A syntax error fails compilation and the raised CompileError points at the exact line and column of the error inside the sigil body, adjusted to the sigil's source location so editors can jump directly to the offending character (including inside multi-line heredoc sigils).

Using ~t

defmodule MyAppWeb.HomeLive do
  use Localize.Message.Sigils,
    backend: MyApp.Gettext,
    sigils: [domain: "messages"]

  def render(assigns) do
    ~H"""
    <h1>{~t"Hello, #{@user.name}!"}</h1>
    """
  end
end

At compile time, ~t"Hello, #{@user.name}!" becomes:

Gettext.Macros.dpgettext_with_backend(
  MyApp.Gettext,
  "messages",
  nil,
  "Hello, {$user_name}!",
  %{user_name: @user.name}
)

The msgid stored in the .po file is the MF2-canonical form with {$name} placeholders, so translators can use MF2 features (selectors, formatters, markup) per-locale.

Gettext backend requirements

The configured backend MUST use MF2-aware interpolation:

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

Without this, {$name} placeholders are returned literally because the default Gettext interpolation only recognises %{name}.

Binding key derivation

Binding names are derived from the interpolated expression:

  • #{name} — variable → name.

  • #{@count} — Phoenix assign → count.

  • #{fruit.name} — dot access → fruit_name.

  • #{String.upcase(x)} — remote call → string_upcase.

  • #{key = expr} — explicit key. Always overrides automatic derivation. Use this for collisions or complex expressions.

Identical expressions interpolated twice share a single binding. Different expressions that derive the same key raise a compile error.

Summary

Functions

Configures the calling module to use the ~t sigil.

Handles the sigil ~M for ICU MessageFormat 2 message strings.

Handles the sigil ~t for compile-time MF2 translation.

Functions

__using__(opts)

(macro)

Configures the calling module to use the ~t sigil.

Options

  • :backend — the Gettext backend module. Required.

  • :sigils — a keyword list of sigil-level options:

    • :domain — default Gettext domain. The default is :default, which resolves to the backend's configured default domain.

    • :context — default Gettext message context. The default is nil.

Examples

use Localize.Message.Sigils,
  backend: MyApp.Gettext,
  sigils: [domain: "messages"]

sigil_M(arg, modifiers)

(macro)

Handles the sigil ~M for ICU MessageFormat 2 message strings.

It returns a canonically formatted string without interpolations and without escape characters, except for the escaping of the closing sigil character itself.

A canonically formatted string is pretty-printed by default returning a potentially multi-line string. This is intended to produce a result which is easier to comprehend for translators.

Modifiers

  • p (default) — pretty-print the message with indentation.

  • u — return a non-pretty-printed (compact) string.

Examples

iex> import Localize.Message.Sigils
iex> ~M(An ICU message)
"An ICU message"

sigil_t(arg, modifiers)

(macro)

Handles the sigil ~t for compile-time MF2 translation.

Rewrites Elixir #{expr} interpolations as MF2 {$name} placeholders with bindings derived from the interpolated expressions. The resulting msgid is canonicalised and routed through Gettext, which performs both the translation lookup and the MF2 interpolation (via the configured Localize.Gettext.Interpolation module).

The calling module must opt in with:

use Localize.Message.Sigils, backend: MyApp.Gettext

Modifiers are reserved for a future release and are rejected at compile time.

See the Localize.Message.Sigils moduledoc for binding-derivation rules and a full example.