AshGettext.Installer (AshGettext v0.1.0)

Copy Markdown View Source

The source rewrites mix ash_gettext.install performs, as pure functions.

Opting a domain into translation is mechanical: add the extension, add a gettext do block naming the host's backend. Doing that by hand across a real app is a dozen near-identical edits, each a chance to miss one — and a domain that gets missed keeps emitting untranslated errors with nothing to notice.

The work lives here rather than in the Mix task so it can be tested as string in, string out: no igniter, no project, no filesystem. The task is a thin shell that reads files, calls these, and writes them back.

What cannot be generated

The resolver module itself. locale_resolver is written into every domain, but reading a user's language needs to know where the host keeps it on the actor, which a generic library must not assume. A generated stub would have to return nil, and every translation would silently fall back to the default language. So the host writes the module, and the build fails until it exists.

Summary

Types

What happened to one domain.

Functions

Whether this source already opts into AshGettext.Domain.

The resolver module name used when none is given: the backend's parent namespace plus LocaleResolver, so MyApp.Gettext gives MyApp.LocaleResolver.

The gettext do block for a domain, as source text.

Add the AshGettext.Domain extension and a gettext do block to a domain.

Add the AshGettext.Resource extension to a resource.

Types

result()

@type result() :: %{content: String.t(), changed?: boolean(), status: status()}

status()

@type status() :: :installed | :already_installed | :unrecognised

What happened to one domain.

:unrecognised matters as much as the other two: a domain whose use call this cannot find comes back byte-for-byte unchanged, and without a status that is indistinguishable from "already done". A caller must be able to say so out loud rather than report a silent success.

Functions

already_installed?(content)

@spec already_installed?(String.t()) :: boolean()

Whether this source already opts into AshGettext.Domain.

default_resolver(backend)

@spec default_resolver(String.t()) :: String.t()

The resolver module name used when none is given: the backend's parent namespace plus LocaleResolver, so MyApp.Gettext gives MyApp.LocaleResolver.

gettext_block(opts)

@spec gettext_block(keyword()) :: String.t()

The gettext do block for a domain, as source text.

Only what differs from the DSL defaults is written: backend always, locale_resolver always, and text_domain when it is not "ash". Repeating a default would pin it, so a later change to the library's default would silently skip every installed domain.

install_domain(content, opts)

@spec install_domain(String.t(), keyword()) :: result()

Add the AshGettext.Domain extension and a gettext do block to a domain.

Returns the source unchanged when the domain already has the extension, so running the installer twice is safe.

Options

  • :backend — required; the host's Gettext backend module, as a string.
  • :text_domain — the gettext text domain. Defaults to "ash", and is then left out of the block.
  • :resolver — a locale resolver module name. Defaults to default_resolver/1 of the backend.

install_resource(content)

@spec install_resource(String.t()) :: result()

Add the AshGettext.Resource extension to a resource.

A domain's gettext do block does nothing on its own: the error handler is installed by the resource extension, so a migration that wires only domains changes no behaviour at all. This is the other half.

Resources are never otherwise touched — no attribute, action or policy.