-module(telega_i18n).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega_i18n.gleam").
-export([new/1, add_locale/3, with_fallback/3, default_locale/1, locales/1, with_command_translations/3, add_toml/3, add_json/3, load_toml_dir/2, load_json_dir/2, translate/4, plural_category/2, translate_count/5, resolve_locale/3, user_language_code/1, enter/2, leave/0, current_locale/0, translate_current/2, t/3, tn/4, middleware/2]).
-export_type([catalog/0, i18n_error/0, state/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Internationalization (i18n) for the [Telega](https://hexdocs.pm/telega)\n"
" Telegram Bot Library.\n"
"\n"
" Translations live in a [`Catalog`](#Catalog) — one flat table of dotted\n"
" keys per locale. Load them from TOML or JSON, install the\n"
" [`middleware`](#middleware) to resolve the active locale per update, then\n"
" call [`t`](#t) inside handlers.\n"
"\n"
" ## Quick start\n"
"\n"
" `locales/en.toml`:\n"
"\n"
" ```toml\n"
" greeting = \"Hello, {name}!\"\n"
"\n"
" [cart]\n"
" title = \"Your cart\"\n"
" ```\n"
"\n"
" `locales/ru.toml`:\n"
"\n"
" ```toml\n"
" greeting = \"Привет, {name}!\"\n"
"\n"
" [cart]\n"
" title = \"Ваша корзина\"\n"
" ```\n"
"\n"
" ```gleam\n"
" import gleam/option.{None}\n"
" import telega/router\n"
" import telega_i18n\n"
"\n"
" pub fn build_router(catalog) {\n"
" // `catalog` loaded once at startup, e.g. with `load_toml_dir`.\n"
" router.new(\"bot\")\n"
" |> router.use_middleware(telega_i18n.middleware(\n"
" catalog:,\n"
" // Optional per-user override stored in the session. Return `None`\n"
" // to fall back to the user's Telegram `language_code`.\n"
" from: fn(_session) { None },\n"
" ))\n"
" |> router.on_command(\"start\", greet)\n"
" }\n"
"\n"
" fn greet(ctx, _command) {\n"
" let msg = telega_i18n.t(ctx, \"greeting\", [#(\"name\", \"Lucy\")])\n"
" // -> \"Hello, Lucy!\" or \"Привет, Lucy!\" depending on the user's locale\n"
" reply.with_text(ctx, msg)\n"
" }\n"
" ```\n"
"\n"
" ## Locale resolution\n"
"\n"
" For every update the middleware picks the first available of:\n"
"\n"
" 1. the session override returned by your `from` resolver,\n"
" 2. the sender's Telegram `language_code`,\n"
" 3. the catalog's default locale.\n"
"\n"
" The resolved locale is stored in the process dictionary of the chat\n"
" instance handling the update, so [`t`](#t) needs only the key.\n"
"\n"
" ## Fallback chains\n"
"\n"
" Lookups walk a chain: the active locale, its base language (`\"en-US\"` →\n"
" `\"en\"`), any explicit [`with_fallback`](#with_fallback) entries, and finally\n"
" the default locale. A missing key returns the key itself, so nothing ever\n"
" crashes on a typo.\n"
"\n"
" ## Pluralization\n"
"\n"
" [`tn`](#tn) selects a CLDR plural category (`one`/`few`/`many`/`other`) and\n"
" looks up `\"<key>.<category>\"`. The `count` is injected as `{count}`\n"
" automatically. English and Russian rules are built in.\n"
"\n"
" ```toml\n"
" [items]\n"
" one = \"{count} item\"\n"
" other = \"{count} items\"\n"
" ```\n"
"\n"
" ```gleam\n"
" telega_i18n.tn(ctx, \"items\", 5, [])\n"
" // -> \"5 items\"\n"
" ```\n"
).
-opaque catalog() :: {catalog,
binary(),
gleam@dict:dict(binary(), gleam@dict:dict(binary(), binary())),
gleam@dict:dict(binary(), list(binary()))}.
-type i18n_error() :: {parse_error, binary()} | {file_error, binary()}.
-type state() :: {state, catalog(), binary()}.
-file("src/telega_i18n.gleam", 132).
?DOC(
" Create an empty catalog with the given default locale. The default is the\n"
" last link of every fallback chain.\n"
).
-spec new(binary()) -> catalog().
new(Default_locale) ->
{catalog, Default_locale, maps:new(), maps:new()}.
-file("src/telega_i18n.gleam", 138).
?DOC(
" Add (or merge into) a locale from an already-flattened map of dotted keys\n"
" to templates. Later entries win on conflict.\n"
).
-spec add_locale(catalog(), binary(), gleam@dict:dict(binary(), binary())) -> catalog().
add_locale(Catalog, Locale, Translations) ->
Existing = begin
_pipe = gleam_stdlib:map_get(erlang:element(3, Catalog), Locale),
gleam@result:unwrap(_pipe, maps:new())
end,
Merged = maps:merge(Existing, Translations),
{catalog,
erlang:element(2, Catalog),
gleam@dict:insert(erlang:element(3, Catalog), Locale, Merged),
erlang:element(4, Catalog)}.
-file("src/telega_i18n.gleam", 153).
?DOC(
" Register an explicit fallback chain for a locale. These locales are tried\n"
" (in order) after the active locale and its base language but before the\n"
" default locale.\n"
).
-spec with_fallback(catalog(), binary(), list(binary())) -> catalog().
with_fallback(Catalog, Locale, Chain) ->
{catalog,
erlang:element(2, Catalog),
erlang:element(3, Catalog),
gleam@dict:insert(erlang:element(4, Catalog), Locale, Chain)}.
-file("src/telega_i18n.gleam", 162).
?DOC(" The catalog's default locale.\n").
-spec default_locale(catalog()) -> binary().
default_locale(Catalog) ->
erlang:element(2, Catalog).
-file("src/telega_i18n.gleam", 167).
?DOC(" The list of locales the catalog knows about.\n").
-spec locales(catalog()) -> list(binary()).
locales(Catalog) ->
maps:keys(erlang:element(3, Catalog)).
-file("src/telega_i18n.gleam", 364).
-spec interpolate(binary(), list({binary(), binary()})) -> binary().
interpolate(Template, Args) ->
gleam@list:fold(
Args,
Template,
fun(Acc, Pair) ->
gleam@string:replace(
Acc,
<<<<"{"/utf8, (erlang:element(1, Pair))/binary>>/binary,
"}"/utf8>>,
erlang:element(2, Pair)
)
end
).
-file("src/telega_i18n.gleam", 348).
-spec locale_chain(catalog(), binary()) -> list(binary()).
locale_chain(Catalog, Locale) ->
Base = case gleam@string:split_once(Locale, <<"-"/utf8>>) of
{ok, {Language, _}} ->
[Language];
{error, _} ->
[]
end,
Extra = begin
_pipe = gleam_stdlib:map_get(erlang:element(4, Catalog), Locale),
gleam@result:unwrap(_pipe, [])
end,
_pipe@1 = [Locale],
_pipe@2 = lists:append(_pipe@1, Base),
_pipe@3 = lists:append(_pipe@2, Extra),
_pipe@4 = lists:append(_pipe@3, [erlang:element(2, Catalog)]),
gleam@list:unique(_pipe@4).
-file("src/telega_i18n.gleam", 334).
-spec lookup(catalog(), binary(), binary()) -> {ok, binary()} | {error, nil}.
lookup(Catalog, Locale, Key) ->
_pipe = locale_chain(Catalog, Locale),
gleam@list:find_map(
_pipe,
fun(Loc) ->
case gleam_stdlib:map_get(erlang:element(3, Catalog), Loc) of
{ok, Table} ->
gleam_stdlib:map_get(Table, Key);
{error, _} ->
{error, nil}
end
end
).
-file("src/telega_i18n.gleam", 194).
?DOC(
" Wire localized command descriptions from this catalog into a `telega`\n"
" builder. Implies `telega.with_auto_commands`: the bot publishes its commands\n"
" on start (default language first, then one `setMyCommands(language_code:)`\n"
" per catalog locale).\n"
"\n"
" Each command's description is looked up at `prefix <> command` — command\n"
" `\"start\"` with `prefix: \"commands.\"` reads catalog key `\"commands.start\"`,\n"
" honoring the catalog's fallback chains. A missing key keeps the description\n"
" the command was registered with in the router.\n"
"\n"
" ```gleam\n"
" let catalog =\n"
" i18n.new(\"en\")\n"
" |> i18n.add_toml(\"en\", en_toml)\n"
" |> i18n.add_toml(\"ru\", ru_toml)\n"
"\n"
" telega.new_for_polling(api_client:)\n"
" |> telega.with_router(router)\n"
" |> i18n.with_command_translations(catalog, prefix: \"commands.\")\n"
" |> telega.init_for_polling()\n"
" ```\n"
).
-spec with_command_translations(
telega:telega_builder(BGGF, BGGG, BGGH),
catalog(),
binary()
) -> telega:telega_builder(BGGF, BGGG, BGGH).
with_command_translations(Builder, Catalog, Prefix) ->
telega:with_command_translations(
Builder,
locales(Catalog),
fun(Command, Locale) ->
case lookup(Catalog, Locale, <<Prefix/binary, Command/binary>>) of
{ok, Template} ->
{some, interpolate(Template, [])};
{error, _} ->
none
end
end
).
-file("src/telega_i18n.gleam", 626).
-spec bool_to_string(boolean()) -> binary().
bool_to_string(Value) ->
case Value of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end.
-file("src/telega_i18n.gleam", 570).
-spec toml_leaf(tom:toml()) -> {ok, binary()} | {error, nil}.
toml_leaf(Value) ->
case tom:as_string(Value) of
{ok, Text} ->
{ok, Text};
{error, _} ->
case tom:as_int(Value) of
{ok, I} ->
{ok, erlang:integer_to_binary(I)};
{error, _} ->
case tom:as_float(Value) of
{ok, F} ->
{ok, gleam_stdlib:float_to_string(F)};
{error, _} ->
case tom:as_bool(Value) of
{ok, B} ->
{ok, bool_to_string(B)};
{error, _} ->
{error, nil}
end
end
end
end.
-file("src/telega_i18n.gleam", 619).
-spec join_key(binary(), binary()) -> binary().
join_key(Prefix, Key) ->
case Prefix of
<<""/utf8>> ->
Key;
_ ->
<<<<Prefix/binary, "."/utf8>>/binary, Key/binary>>
end.
-file("src/telega_i18n.gleam", 552).
-spec flatten_toml(
binary(),
gleam@dict:dict(binary(), tom:toml()),
list({binary(), binary()})
) -> list({binary(), binary()}).
flatten_toml(Prefix, Table, Acc) ->
gleam@dict:fold(
Table,
Acc,
fun(Acc@1, Key, Value) ->
Path = join_key(Prefix, Key),
case tom:as_table(Value) of
{ok, Sub} ->
flatten_toml(Path, Sub, Acc@1);
{error, _} ->
case toml_leaf(Value) of
{ok, Text} ->
[{Path, Text} | Acc@1];
{error, _} ->
Acc@1
end
end
end
).
-file("src/telega_i18n.gleam", 215).
?DOC(
" Parse a TOML document and merge it into the catalog under `locale`. Nested\n"
" tables become dotted keys (`[cart] title = \"...\"` → `cart.title`).\n"
).
-spec add_toml(catalog(), binary(), binary()) -> {ok, catalog()} |
{error, i18n_error()}.
add_toml(Catalog, Locale, Content) ->
case tom:parse(Content) of
{ok, Parsed} ->
Flat = begin
_pipe = flatten_toml(<<""/utf8>>, Parsed, []),
maps:from_list(_pipe)
end,
{ok, add_locale(Catalog, Locale, Flat)};
{error, Error} ->
{error, {parse_error, gleam@string:inspect(Error)}}
end.
-file("src/telega_i18n.gleam", 589).
-spec flatten_dynamic(
binary(),
gleam@dynamic:dynamic_(),
list({binary(), binary()})
) -> list({binary(), binary()}).
flatten_dynamic(Prefix, Value, Acc) ->
case gleam@dynamic@decode:run(
Value,
{decoder, fun gleam@dynamic@decode:decode_string/1}
) of
{ok, Text} ->
[{Prefix, Text} | Acc];
{error, _} ->
case gleam@dynamic@decode:run(
Value,
gleam@dynamic@decode:dict(
{decoder, fun gleam@dynamic@decode:decode_string/1},
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
)
) of
{ok, Sub} ->
gleam@dict:fold(
Sub,
Acc,
fun(Acc@1, Key, Value@1) ->
flatten_dynamic(
join_key(Prefix, Key),
Value@1,
Acc@1
)
end
);
{error, _} ->
case gleam@dynamic@decode:run(
Value,
{decoder, fun gleam@dynamic@decode:decode_int/1}
) of
{ok, I} ->
[{Prefix, erlang:integer_to_binary(I)} | Acc];
{error, _} ->
case gleam@dynamic@decode:run(
Value,
{decoder,
fun gleam@dynamic@decode:decode_float/1}
) of
{ok, F} ->
[{Prefix, gleam_stdlib:float_to_string(F)} |
Acc];
{error, _} ->
case gleam@dynamic@decode:run(
Value,
{decoder,
fun gleam@dynamic@decode:decode_bool/1}
) of
{ok, B} ->
[{Prefix, bool_to_string(B)} | Acc];
{error, _} ->
Acc
end
end
end
end
end.
-file("src/telega_i18n.gleam", 231).
?DOC(
" Parse a JSON document and merge it into the catalog under `locale`. Nested\n"
" objects become dotted keys.\n"
).
-spec add_json(catalog(), binary(), binary()) -> {ok, catalog()} |
{error, i18n_error()}.
add_json(Catalog, Locale, Content) ->
case gleam@json:parse(
Content,
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
) of
{ok, Value} ->
Flat = begin
_pipe = flatten_dynamic(<<""/utf8>>, Value, []),
maps:from_list(_pipe)
end,
{ok, add_locale(Catalog, Locale, Flat)};
{error, Error} ->
{error, {parse_error, gleam@string:inspect(Error)}}
end.
-file("src/telega_i18n.gleam", 247).
?DOC(
" Load every `*.toml` file in `dir` as a locale named after the file\n"
" (`en.toml` → `\"en\"`), merging them into `catalog`.\n"
).
-spec load_toml_dir(catalog(), binary()) -> {ok, catalog()} |
{error, i18n_error()}.
load_toml_dir(Catalog, Dir) ->
gleam@result:'try'(
begin
_pipe = simplifile_erl:read_directory(Dir),
gleam@result:map_error(
_pipe,
fun(E) -> {file_error, gleam@string:inspect(E)} end
)
end,
fun(Files) -> _pipe@1 = Files,
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(_capture) ->
gleam_stdlib:string_ends_with(_capture, <<".toml"/utf8>>)
end
),
gleam@list:fold(
_pipe@2,
{ok, Catalog},
fun(Acc, File) ->
gleam@result:'try'(
Acc,
fun(Cat) ->
gleam@result:'try'(
begin
_pipe@3 = simplifile:read(
<<<<Dir/binary, "/"/utf8>>/binary,
File/binary>>
),
gleam@result:map_error(
_pipe@3,
fun(E@1) ->
{file_error,
gleam@string:inspect(E@1)}
end
)
end,
fun(Content) ->
Locale = gleam@string:replace(
File,
<<".toml"/utf8>>,
<<""/utf8>>
),
add_toml(Cat, Locale, Content)
end
)
end
)
end
) end
).
-file("src/telega_i18n.gleam", 271).
?DOC(
" Load every `*.json` file in `dir` as a locale named after the file\n"
" (`en.json` → `\"en\"`), merging them into `catalog`.\n"
).
-spec load_json_dir(catalog(), binary()) -> {ok, catalog()} |
{error, i18n_error()}.
load_json_dir(Catalog, Dir) ->
gleam@result:'try'(
begin
_pipe = simplifile_erl:read_directory(Dir),
gleam@result:map_error(
_pipe,
fun(E) -> {file_error, gleam@string:inspect(E)} end
)
end,
fun(Files) -> _pipe@1 = Files,
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(_capture) ->
gleam_stdlib:string_ends_with(_capture, <<".json"/utf8>>)
end
),
gleam@list:fold(
_pipe@2,
{ok, Catalog},
fun(Acc, File) ->
gleam@result:'try'(
Acc,
fun(Cat) ->
gleam@result:'try'(
begin
_pipe@3 = simplifile:read(
<<<<Dir/binary, "/"/utf8>>/binary,
File/binary>>
),
gleam@result:map_error(
_pipe@3,
fun(E@1) ->
{file_error,
gleam@string:inspect(E@1)}
end
)
end,
fun(Content) ->
Locale = gleam@string:replace(
File,
<<".json"/utf8>>,
<<""/utf8>>
),
add_json(Cat, Locale, Content)
end
)
end
)
end
) end
).
-file("src/telega_i18n.gleam", 300).
?DOC(
" Translate `key` in an explicit `locale`, interpolating `{placeholder}`\n"
" values from `args`. Missing keys return the key unchanged.\n"
"\n"
" Prefer [`t`](#t) inside handlers; this is the pure building block, handy\n"
" for tests and locale-agnostic call sites.\n"
).
-spec translate(catalog(), binary(), binary(), list({binary(), binary()})) -> binary().
translate(Catalog, Locale, Key, Args) ->
case lookup(Catalog, Locale, Key) of
{ok, Template} ->
interpolate(Template, Args);
{error, _} ->
Key
end.
-file("src/telega_i18n.gleam", 389).
-spec english_category(integer()) -> binary().
english_category(Count) ->
case Count of
1 ->
<<"one"/utf8>>;
_ ->
<<"other"/utf8>>
end.
-file("src/telega_i18n.gleam", 396).
-spec east_slavic_category(integer()) -> binary().
east_slavic_category(Count) ->
N = gleam@int:absolute_value(Count),
Mod10 = N rem 10,
Mod100 = N rem 100,
case nil of
_ when (Mod10 =:= 1) andalso (Mod100 =/= 11) ->
<<"one"/utf8>>;
_ when ((Mod10 >= 2) andalso (Mod10 =< 4)) andalso ((Mod100 < 12) orelse (Mod100 > 14)) ->
<<"few"/utf8>>;
_ ->
<<"many"/utf8>>
end.
-file("src/telega_i18n.gleam", 382).
-spec base_language(binary()) -> binary().
base_language(Locale) ->
case gleam@string:split_once(Locale, <<"-"/utf8>>) of
{ok, {Language, _}} ->
Language;
{error, _} ->
Locale
end.
-file("src/telega_i18n.gleam", 375).
?DOC(
" Return the CLDR plural category (`\"one\"`, `\"few\"`, `\"many\"`, `\"other\"`) for\n"
" `count` in `locale`. Russian and English have dedicated rules; every other\n"
" locale uses the English rule.\n"
).
-spec plural_category(binary(), integer()) -> binary().
plural_category(Locale, Count) ->
case base_language(Locale) of
<<"ru"/utf8>> ->
east_slavic_category(Count);
<<"uk"/utf8>> ->
east_slavic_category(Count);
<<"be"/utf8>> ->
east_slavic_category(Count);
_ ->
english_category(Count)
end.
-file("src/telega_i18n.gleam", 315).
?DOC(
" Pluralizing variant of [`translate`](#translate). Picks the CLDR category\n"
" for `count`, looks up `\"<key>.<category>\"` (falling back to `\"<key>.other\"`),\n"
" and injects `count` as `{count}`.\n"
).
-spec translate_count(
catalog(),
binary(),
binary(),
integer(),
list({binary(), binary()})
) -> binary().
translate_count(Catalog, Locale, Key, Count, Args) ->
Args@1 = [{<<"count"/utf8>>, erlang:integer_to_binary(Count)} | Args],
Category = plural_category(Locale, Count),
case lookup(
Catalog,
Locale,
<<<<Key/binary, "."/utf8>>/binary, Category/binary>>
) of
{ok, Template} ->
interpolate(Template, Args@1);
{error, _} ->
case lookup(Catalog, Locale, <<Key/binary, ".other"/utf8>>) of
{ok, Template@1} ->
interpolate(Template@1, Args@1);
{error, _} ->
interpolate(Key, Args@1)
end
end.
-file("src/telega_i18n.gleam", 411).
?DOC(
" Resolve the active locale from a session override and the sender's Telegram\n"
" `language_code`, falling back to the catalog default.\n"
).
-spec resolve_locale(
catalog(),
gleam@option:option(binary()),
gleam@option:option(binary())
) -> binary().
resolve_locale(Catalog, Session_locale, Update_locale) ->
_pipe = Session_locale,
_pipe@1 = gleam@option:'or'(_pipe, Update_locale),
gleam@option:unwrap(_pipe@1, erlang:element(2, Catalog)).
-file("src/telega_i18n.gleam", 440).
-spec first_user(list(gleam@option:option(telega@model@types:user()))) -> gleam@option:option(telega@model@types:user()).
first_user(Candidates) ->
_pipe = Candidates,
_pipe@1 = gleam@list:find_map(
_pipe,
fun(Candidate) -> gleam@option:to_result(Candidate, nil) end
),
gleam@option:from_result(_pipe@1).
-file("src/telega_i18n.gleam", 422).
?DOC(" Extract the sender's `language_code` from a raw update, if present.\n").
-spec user_language_code(telega@model@types:update()) -> gleam@option:option(binary()).
user_language_code(Raw) ->
_pipe = [gleam@option:then(
erlang:element(3, Raw),
fun(M) -> erlang:element(5, M) end
),
gleam@option:then(
erlang:element(4, Raw),
fun(M@1) -> erlang:element(5, M@1) end
),
gleam@option:then(
erlang:element(8, Raw),
fun(M@2) -> erlang:element(5, M@2) end
),
gleam@option:map(
erlang:element(16, Raw),
fun(C) -> erlang:element(3, C) end
),
gleam@option:map(
erlang:element(14, Raw),
fun(Q) -> erlang:element(3, Q) end
),
gleam@option:map(
erlang:element(15, Raw),
fun(R) -> erlang:element(3, R) end
),
gleam@option:map(
erlang:element(17, Raw),
fun(Q@1) -> erlang:element(3, Q@1) end
),
gleam@option:map(
erlang:element(18, Raw),
fun(Q@2) -> erlang:element(3, Q@2) end
),
gleam@option:map(
erlang:element(22, Raw),
fun(M@3) -> erlang:element(3, M@3) end
),
gleam@option:map(
erlang:element(23, Raw),
fun(M@4) -> erlang:element(3, M@4) end
),
gleam@option:map(
erlang:element(24, Raw),
fun(R@1) -> erlang:element(3, R@1) end
)],
_pipe@1 = first_user(_pipe),
gleam@option:then(_pipe@1, fun(User) -> erlang:element(7, User) end).
-file("src/telega_i18n.gleam", 484).
-spec state_key() -> gleam@erlang@atom:atom_().
state_key() ->
erlang:binary_to_atom(<<"telega_i18n_state"/utf8>>).
-file("src/telega_i18n.gleam", 455).
?DOC(
" Store the active catalog and locale for the current process. The\n"
" [`middleware`](#middleware) calls this before each handler; call it yourself\n"
" only if you resolve locales outside the router.\n"
).
-spec enter(catalog(), binary()) -> nil.
enter(Catalog, Locale) ->
_ = erlang:put(state_key(), gleam_stdlib:identity({state, Catalog, Locale})),
nil.
-file("src/telega_i18n.gleam", 461).
?DOC(" Clear the i18n state for the current process.\n").
-spec leave() -> nil.
leave() ->
_ = erlang:erase(state_key()),
nil.
-file("src/telega_i18n.gleam", 475).
-spec read_state() -> {ok, state()} | {error, nil}.
read_state() ->
Value = erlang:get(state_key()),
case Value =:= gleam_stdlib:identity(
erlang:binary_to_atom(<<"undefined"/utf8>>)
) of
true ->
{error, nil};
false ->
{ok, gleam_stdlib:identity(Value)}
end.
-file("src/telega_i18n.gleam", 468).
?DOC(
" The locale active in the current process, if [`enter`](#enter) (or the\n"
" middleware) has run.\n"
).
-spec current_locale() -> gleam@option:option(binary()).
current_locale() ->
case read_state() of
{ok, {state, _, Locale}} ->
{some, Locale};
{error, _} ->
none
end.
-file("src/telega_i18n.gleam", 517).
?DOC(
" Translate using the active process locale without a context. [`t`](#t)\n"
" delegates here.\n"
).
-spec translate_current(binary(), list({binary(), binary()})) -> binary().
translate_current(Key, Args) ->
case read_state() of
{ok, {state, Catalog, Locale}} ->
translate(Catalog, Locale, Key, Args);
{error, _} ->
Key
end.
-file("src/telega_i18n.gleam", 493).
?DOC(
" Translate `key` for the locale active in the current handler, interpolating\n"
" `{placeholder}` values from `args`. Requires the [`middleware`](#middleware)\n"
" (or a manual [`enter`](#enter)); otherwise returns the key unchanged.\n"
).
-spec t(
telega@bot:context(any(), any(), any()),
binary(),
list({binary(), binary()})
) -> binary().
t(_, Key, Args) ->
translate_current(Key, Args).
-file("src/telega_i18n.gleam", 502).
?DOC(" Pluralizing variant of [`t`](#t). See [`translate_count`](#translate_count).\n").
-spec tn(
telega@bot:context(any(), any(), any()),
binary(),
integer(),
list({binary(), binary()})
) -> binary().
tn(_, Key, Count, Args) ->
case read_state() of
{ok, {state, Catalog, Locale}} ->
translate_count(Catalog, Locale, Key, Count, Args);
{error, _} ->
Key
end.
-file("src/telega_i18n.gleam", 535).
?DOC(
" Router middleware that resolves the active locale for every update and makes\n"
" it available to [`t`](#t)/[`tn`](#tn).\n"
"\n"
" `from` reads an optional per-user override out of the session (e.g. a\n"
" language the user chose in settings); return `None` to fall back to the\n"
" sender's Telegram `language_code` and then the catalog default.\n"
).
-spec middleware(catalog(), fun((BGIA) -> gleam@option:option(binary()))) -> fun((fun((telega@bot:context(BGIA, BGIC, BGID), telega@update:update()) -> {ok,
telega@bot:context(BGIA, BGIC, BGID)} |
{error, BGIC})) -> fun((telega@bot:context(BGIA, BGIC, BGID), telega@update:update()) -> {ok,
telega@bot:context(BGIA, BGIC, BGID)} |
{error, BGIC})).
middleware(Catalog, From) ->
fun(Handler) ->
fun(Ctx, Update) ->
Session_locale = From(erlang:element(5, Ctx)),
Update_locale = user_language_code(
telega@update:raw(erlang:element(3, Ctx))
),
Locale = resolve_locale(Catalog, Session_locale, Update_locale),
enter(Catalog, Locale),
Handler(Ctx, Update)
end
end.