Internationalization (i18n)

Copy Markdown

Aurora UIX ships with built-in internationalization support through the Gettext library. All labels, error messages, and UI strings generated by the framework pass through a configurable Gettext backend, so your application can be translated into any language without touching library source code.

The default backend is Aurora.Uix.GettextBackend. See its module documentation for the full list of compile-time options and callback details.

How it works

Every module that needs to translate a string uses Aurora.Uix.Gettext, a thin __using__ macro that wires the configured backend into the calling module:

defmodule MyAppWeb.SomeComponent do
  use Aurora.Uix.Gettext        # injects gettext/1, dgettext/2, dt/1, …
end

At translation time Gettext looks up the locale and domain in the compiled .po files. When no translation is found, the call is forwarded to the backend's handle_missing_* callbacks, which Aurora UIX uses to:

  1. Optionally emit a Logger.warning so missing strings are visible during development.
  2. Optionally write a stub entry to the matching .pot file so translators always have an up-to-date template.
  3. Return the English source string as a safe fallback so the UI never shows an empty field.

Configuration

It is strongly recommended to set :gettext_domain so that Aurora UIX strings are stored in their own Gettext domain, separate from your application's default domain. Without this, running mix gettext.merge can intermix or overwrite Aurora UIX translations with your own strings.

# config/config.exs
config :aurora_uix, gettext_domain: "aurora_uix"

This setting takes effect at compile time — it affects both the runtime dt/1 macro (which emits dgettext("aurora_uix", msgid) instead of gettext(msgid)) and the compile-time form label generation, so all Aurora UIX strings end up in priv/gettext/aurora_uix.po rather than the default default.po.

Development settings

The following keys are read by Aurora.Uix.GettextBackend at compile time. They have no effect if you replace the backend with a custom module — a custom backend must implement the equivalent behaviour itself.

# config/dev.exs — recommended settings for local development
config :aurora_uix,
  # Write missing-translation stubs to .pot files at runtime.
  gettext_pot_path: "priv/gettext",
  # Emit Logger.warning for every missing translation (off by default).
  gettext_show_warnings?: true
KeyTypeDefaultDescription
:gettext_pot_pathString.t() | nilnilDirectory where .pot files are written. When nil or omitted, no file I/O occurs.
:gettext_show_warnings?boolean()falseEmit Logger.warning for missing translations. Warnings are suppressed by default.

For production, omit both keys so no file I/O nor any gettext message happens at runtime.

Custom backend

You can replace the default backend with your own module. Be aware that doing so loses the POT file generation and warning emission — those are implemented inside Aurora.Uix.GettextBackend. A custom backend must re-implement any behaviour it needs.

Configure the replacement via the application environment:

config :aurora_uix, gettext_backend: MyApp.CustomGettextBackend

Or per-module via the :backend option:

use Aurora.Uix.Gettext, backend: MyApp.CustomGettextBackend

See Aurora.Uix.Gettext for the full backend resolution order.

Example — CI-only strict backend (raises on any missing translation):

defmodule MyApp.StrictGettextBackend do
  use Gettext.Backend, otp_app: :my_app

  @impl true
  def handle_missing_translation(_locale, _domain, _msgctxt, msgid, _bindings) do
    raise "Missing translation for: #{msgid}"
  end

  @impl true
  def handle_missing_plural_translation(_locale, _domain, _msgctxt, msgid, _msgid_plural, _n, _bindings) do
    raise "Missing plural translation for: #{msgid}"
  end

  @impl true
  def handle_missing_bindings(exception, _incomplete) do
    raise exception
  end
end

This pattern is useful in CI to catch untranslated strings early. In development and production, prefer the default backend so the UI degrades gracefully.

Automatic .pot file generation

When gettext_pot_path is configured, Aurora.Uix.GettextBackend appends a stub entry to the appropriate .pot file the first time a missing translation is encountered at runtime. This means you can run your dev server, exercise the UI, and then merge the stubs into your .po files — no manual editing required.

The generated stubs follow standard .pot format:

# Generated by Aurora Uix

msgid "First name"
msgstr ""

msgid "Items"
msgid_plural "Items"
msgstr[0] ""
msgstr[1] ""

The backend deduplicates: if an msgid already appears in the file it is not appended again.

Once you have exercised the UI, merge the generated stubs into your locale's .po file with:

mix gettext.merge priv/gettext --locale es

See the Gettext merge documentation for the full set of options (locale filtering, fuzzy matching, etc.).

Translation workflow

A typical development workflow:

  1. Configure gettext_domain, gettext_pot_path, and gettext_show_warnings?: true in config/dev.exs.
  2. Start the dev server and browse the pages that use Aurora UIX-generated UI.
  3. Run mix gettext.merge priv/gettext --locale <locale> to pull the generated stubs into your .po files.
  4. Fill in msgstr values for each locale.
  5. Recompile — translations are now active.

Locale selection

Aurora UIX does not manage the active locale — that is the responsibility of the host application. Use the standard Gettext locale mechanism in your Plug pipeline or LiveView on_mount:

# In a Plug / router pipeline
plug :put_locale

defp put_locale(conn, _opts) do
  locale = get_session(conn, :locale) || "en"
  Gettext.put_locale(locale)
  conn
end

See also