rebar3_erli18n_common (rebar3_erli18n v0.1.0)

Copy Markdown View Source

Shared plumbing for the extract/merge/check/report providers.

Centralizes the parts that would otherwise be duplicated across the four providers: the common getopt option set, project source discovery + the abstract-form walk, deduplication of extracted call sites into catalog entries (merging #: references), .pot/.po directory resolution, and a uniform format_error/1. Keeping this in one module makes the providers thin wrappers and lets a single suite cover the walk/dedup logic to 100%.

Catalog layout

The .pot templates live in priv/gettext/<Domain>.pot; the translated catalogs in priv/gettext/<Locale>/LC_MESSAGES/<Domain>.po. This mirrors the runtime loader's default path (erli18n:default_po_path/3) so a project's extracted templates and loaded catalogs share one tree.

Summary

Types

A deduplicated catalog entry: one logical {Domain, Context, Msgid} with all the #: references that pointed at it (in first-seen source order). kind/plural come from the first occurrence.

A #: source reference: a relative source path and a 1-based line.

Functions

The getopt option spec shared by the providers.

Collapse a domain's raw extracted entries into deduplicated catalog entries, keyed by {Context, Msgid}, merging each duplicate's reference.

Build a rebar3_erli18n_po_meta:catalog() (.pot template) from a domain's deduplicated entries: an empty header, every msgstr empty, references as #: lines.

Walk every project app's src/ and extract all recognized call sites, grouped and deduplicated by domain.

Render a shared provider error to a human-readable string.

Render a code:which/1 result as a printable string: the .beam path verbatim when loaded, or the special atom (non_existing, preloaded, cover_compiled) spelled out so the diagnostic line is unambiguous about WHY the cross-package module is not a concrete path.

When the ERLI18N_DIAG_LOADPATH OS environment variable is set, log the loaded erli18n_po path through the rebar3 logger at provider-run time, so the cross-package load path can be captured from a real rebar3 erli18n {extract,merge,check,report} run. A no-op (returns ok, emits nothing) when the variable is unset, so it adds no output to ordinary runs.

The .po path for {Domain, Locale}: <pot_dir>/<Locale>/LC_MESSAGES/<Domain>.po.

The .pot template directory: <RootApp>/priv/gettext (or the --pot-dir override). The first project app is treated as the root.

The loaded location of the erli18n_po runtime module, as code:which/1 sees it at the moment of the call — non_existing if the module is not on the code path, preloaded/cover_compiled for those special cases, or the absolute .beam path otherwise.

Types

dedup_entry()

-type dedup_entry() ::
          #{domain := atom(),
            kind := rebar3_erli18n_keywords:kind(),
            context := undefined | binary(),
            msgid := binary(),
            plural := undefined | binary(),
            references := [reference_ref()]}.

A deduplicated catalog entry: one logical {Domain, Context, Msgid} with all the #: references that pointed at it (in first-seen source order). kind/plural come from the first occurrence.

reference_ref()

-type reference_ref() :: {file:filename(), pos_integer()}.

A #: source reference: a relative source path and a 1-based line.

Functions

common_opts()

-spec common_opts() -> [{atom(), char() | undefined, string(), atom() | tuple(), string()}].

The getopt option spec shared by the providers.

--domain restricts the operation to a single domain; --locale selects a target locale (merge/report); --names-only switches check to the laxer msgid-set comparison; --pot-dir overrides the default priv/gettext root.

dedup_entries(Entries)

-spec dedup_entries([rebar3_erli18n_extract_forms:extracted()]) -> [dedup_entry()].

Collapse a domain's raw extracted entries into deduplicated catalog entries, keyed by {Context, Msgid}, merging each duplicate's reference.

References are kept in first-seen order with duplicates removed; the entry list is returned sorted by {Context, Msgid} for deterministic output.

entries_to_pot(Entries)

-spec entries_to_pot([dedup_entry()]) -> rebar3_erli18n_po_meta:catalog().

Build a rebar3_erli18n_po_meta:catalog() (.pot template) from a domain's deduplicated entries: an empty header, every msgstr empty, references as #: lines.

extract_project(State)

-spec extract_project(rebar3_erli18n_host:state()) ->
                         {ok, #{atom() => [dedup_entry()]}} | {error, term()}.

Walk every project app's src/ and extract all recognized call sites, grouped and deduplicated by domain.

Returns {ok, #{Domain => [dedup_entry()]}}, or the first {error, Reason} an epp parse raised. Each domain's entry list is sorted by {Context, Msgid} for deterministic, diff-stable output.

format_error/1

-spec format_error(term()) -> string().

Render a shared provider error to a human-readable string.

format_lib_path/1

-spec format_lib_path(non_existing | cover_compiled | preloaded | file:filename()) -> string().

Render a code:which/1 result as a printable string: the .beam path verbatim when loaded, or the special atom (non_existing, preloaded, cover_compiled) spelled out so the diagnostic line is unambiguous about WHY the cross-package module is not a concrete path.

maybe_log_runtime_lib_path()

-spec maybe_log_runtime_lib_path() -> ok.

When the ERLI18N_DIAG_LOADPATH OS environment variable is set, log the loaded erli18n_po path through the rebar3 logger at provider-run time, so the cross-package load path can be captured from a real rebar3 erli18n {extract,merge,check,report} run. A no-op (returns ok, emits nothing) when the variable is unset, so it adds no output to ordinary runs.

po_path(State, Domain, Locale)

-spec po_path(rebar3_erli18n_host:state(), atom(), string()) -> file:filename().

The .po path for {Domain, Locale}: <pot_dir>/<Locale>/LC_MESSAGES/<Domain>.po.

pot_dir(State)

-spec pot_dir(rebar3_erli18n_host:state()) -> file:filename().

The .pot template directory: <RootApp>/priv/gettext (or the --pot-dir override). The first project app is treated as the root.

runtime_lib_path()

-spec runtime_lib_path() -> non_existing | cover_compiled | preloaded | file:filename().

The loaded location of the erli18n_po runtime module, as code:which/1 sees it at the moment of the call — non_existing if the module is not on the code path, preloaded/cover_compiled for those special cases, or the absolute .beam path otherwise.

This is the structural proof of the plugin -> lib load path. Every provider reaches erli18n_po:parse/1, erli18n_po:dump/1, and erli18n_po:escape_string/1 across the published {deps, [erli18n]} boundary. In a downstream consumer that surfaces the unpublished lib via _checkouts/erli18n, this resolves under the consumer's _build/<profile>/checkouts/erli18n/ebin/erli18n_po.beam, demonstrating that the checkout (not a Hex fetch) backs the cross-package calls. See apps/rebar3_erli18n/README.md ("Proven cross-package load path").