The erli18n library's application callback: the OTP entry point
that brings the process tree up and down.
What it is and what problem it solves
This is the module pointed to by {mod, {erli18n_app, []}} in
erli18n.app.src. When someone runs application:ensure_all_started(erli18n)
(or the release boot), OTP calls start/2 here, and when the application
stops, it calls stop/1. There is nothing erli18n-specific in it beyond
delegating: it exists because the application_controller requires a
callback module, not because there is any i18n logic to run at boot.
Mental model (for the maintainer)
Think of this module as the thinnest possible shell around the supervision tree, with ONE cleanup responsibility on shutdown. The load-bearing complexity lives one hop ahead:
start/2callserli18n_sup:start_link/0and returns the supervisor's{ok, Pid}raw, unwrapped. ThatPidbecomes the pid of the application that theapplication_controllergoes on to monitor.- The real topology lives in
erli18n_sup: aone_for_onestrategy with a single child —erli18n_server(worker/writer). The catalogs live inpersistent_term(seeerli18n_pt_store), which is owned by the runtime, not by any process, so a crash of the worker loses no catalog and the tree needs no table owner, no heir, and no child ordering. Seeerli18n_sup. stop/1has real work: it erases the catalogs.persistent_termis node-global and is NOT cleared when the application stops, so without an explicit erase a stop/start cycle would leave stale catalogs behind (and a fresh start would see them as already loaded).stop/1callserli18n_pt_store:erase_all/0to remove every{erli18n_catalog, _, _}term. This module reads noapplication:get_env/2: theenvdefaults (emit_lookup_telemetry,memory_warning_threshold,memory_warning_rate_limit_seconds) are declared inerli18n.app.srcand consumed byerli18n_telemetry— never here.- The ordered shutdown of the child (including the worker's
terminate/2) is the supervision tree's job, triggered by theapplication_controllerbefore it callsstop/1. By the timestop/1runs the worker is already down, but the catalogs it installed still live inpersistent_term— which is exactly whystop/1must erase them here.
When a dev touches this module
Almost never directly. The normal path is:
- Library consumer: runs
application:ensure_all_started(erli18n)and moves on toerli18n:gettext/3,erli18n_server:ensure_loaded/3, etc. Does not import this module. - Maintainer: touches this only if the shape of the boot changes — for
example, starting to read
Argsfrom{mod, {erli18n_app, Args}}, doing one-shot setup at start, or handlingType(normalvs takeover/failover in a distributed scenario). Today both arguments are ignored on purpose. If you are going to add a new top-level process, the place iserli18n_sup:init/1, not here —start/2should stay a one-liner.
Quickstart
1> {ok, Started} = application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> lists:member(erli18n, Started).
true
3> is_pid(whereis(erli18n_sup)).
true
4> is_pid(whereis(erli18n_server)).
true
5> application:stop(erli18n).
okThe exact list in
{ok, Started}depends on the environment. Sincetelemetryis declared inoptional_applicationsinerli18n.app.src, if it is present but not yet started,ensure_all_started/1brings it up too and includes it in the list (e.g.{ok,[telemetry,erli18n]}). The literal{ok,[erli18n]}above holds whentelemetryis absent or already up; that is why the robust test islists:member(erli18n, Started), not a comparison against the whole list.
Summary
Functions
Callback application:start/2: brings up the root supervision tree.
Callback application:stop/1: erases the loaded catalogs on shutdown.
Functions
Callback application:start/2: brings up the root supervision tree.
Delegates raw to erli18n_sup:start_link/0 and returns the supervisor's
{ok, Pid} without any wrapping. That Pid is the pid that the
application_controller adopts as the pid of the erli18n application and
goes on to monitor; when it dies, the application is considered terminated.
Parameters
Type— the start type that OTP passes (normalon a regular boot;{takeover, Node}/{failover, Node}in distributed applications). Ignored: the erli18n boot is identical in any mode, so there is no branching onType.Args— the term from{mod, {erli18n_app, Args}}inerli18n.app.src, today[]. Ignored: no boot configuration is read here (the runtime defaults live inenvand are read byerli18n_telemetry, not by this callback).
Return
{ok, Pid}— on success, passed through directly fromerli18n_sup:start_link/0.- Any
{error, Reason}coming from below propagates intact: this callback has notry/catchand no fallback. The normal case of a child failing at boot is an{error, {shutdown, Reason}}— the form thatsupervisor:start_link/3returns (and which the Return section oferli18n_sup:start_link/0already documents) when theerli18n_serverchild fails its owninit/1. That is the term the maintainer will see and match on. An error here makesensure_all_started/1fail and the application does not come up — the correct OTP behavior, with no masking.
Example
1> {ok, Started} = application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> lists:member(erli18n, Started).
true
3> SupPid = whereis(erli18n_sup), is_pid(SupPid).
trueThe literal
{ok,[erli18n]}on line 1 holds whentelemetry(declared inoptional_applications) is absent or already started; if it gets brought up now, it also appears in the list. Hence thelists:member/2instead of comparing the whole list.
Sibling function: stop/1. Topology started: erli18n_sup:init/1.
Callback application:stop/1: erases the loaded catalogs on shutdown.
The application_controller calls stop/1 after having already torn
down the supervision tree (terminating erli18n_server, which runs its own
terminate/2). The worker is down by now, but the catalogs it installed
still live in persistent_term, which is node-global and is NOT cleared
when the application stops. So this callback calls
erli18n_pt_store:erase_all/0 to remove every {erli18n_catalog, _, _}
term — otherwise a stop/start cycle would leak stale catalogs (and a fresh
start would treat them as already loaded). It returns ok.
Parameters
State— the value thatstart/2would have returned as the second element of{ok, Pid, State}. Sincestart/2returns the 2-tuple{ok, Pid}(withoutState), it is theapplication_controllerthat substitutes[]for theStatewhen calling this callback: whenstart/2uses the{ok, Pid}form instead of{ok, Pid, State}, the controller normalizes the missing state to[]. That is why the argument here is[]. Ignored: the cleanup target (persistent_term) is node-global, not carried inState.
Return
ok— always.erli18n_pt_store:erase_all/0returns the count of erased catalogs (used only for observability/tests); this callback discards it and returns the OTP-mandatedok. It has no error path:erase_all/0is total.
Example
1> application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> application:stop(erli18n).
ok
3> whereis(erli18n_sup).
undefinedSibling function: start/2. The shutdown of the child belongs to the
supervision tree: see erli18n_sup.