Packages
gettext
1.0.1
1.0.2
1.0.1
retired
1.0.0
0.26.2
0.26.1
0.26.0
retired
0.25.0
0.24.0
0.23.1
0.23.0
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.0
Internationalization and localization through gettext
Retired package: Release invalid - mix gettext.extract is broken in older Elixir versions
Current section
Files
Jump to
Current section
Files
lib/gettext/extractor_agent.ex
defmodule Gettext.ExtractorAgent do
@moduledoc false
use Agent
require Logger
alias Expo.Message
@name __MODULE__
# :messages is a map where keys are Gettext backends and values
# are maps. In these maps, keys are domains and values are maps of
# message_id => message.
# :backends is just a list of backends that call `use Gettext`.
@initial_state %{
messages: %{},
backends: [],
extracting?: false
}
@spec start_link(keyword()) :: Agent.on_start()
def start_link([] = _opts) do
Agent.start_link(fn -> @initial_state end, name: @name)
end
@spec enable() :: :ok
def enable() do
Agent.update(@name, &put_in(&1.extracting?, true))
end
@spec disable() :: :ok
def disable() do
Agent.update(@name, &put_in(&1.extracting?, false))
end
@spec extracting?() :: boolean()
def extracting?() do
Agent.get(@name, & &1.extracting?)
end
@spec add_message(backend :: module(), domain :: String.t(), Message.t()) :: :ok
def add_message(backend, domain, message) do
key = Message.key(message)
Agent.cast(@name, fn state ->
# Initialize the given backend to an empty map if it wasn't there.
state = update_in(state.messages, &Map.put_new(&1, backend, %{}))
update_in(state.messages[backend][domain], fn messages ->
Map.update(messages || %{}, key, message, &merge_messages(&1, message))
end)
end)
end
def add_backend(backend) do
Agent.cast(@name, fn state ->
update_in(state.backends, &[backend | &1])
end)
end
def stop() do
Agent.stop(@name)
end
def pop_message(backends) do
Agent.get_and_update(@name, fn state ->
get_and_update_in(state.messages, &Map.split(&1, backends))
end)
end
def pop_backends(app) do
Agent.get_and_update(@name, fn state ->
get_and_update_in(state.backends, fn backends ->
Enum.split_with(backends, &(&1.__gettext__(:otp_app) == app))
end)
end)
end
defp merge_messages(%Message.Singular{} = message_1, %Message.Plural{} = message_2) do
# Flipping the arguments to make sure that the pluaral message (more information) is used as the base message
merge_messages(message_2, message_1)
end
defp merge_messages(%Message.Plural{} = message_1, %Message.Plural{} = message_2) do
# Make sure message choice is deterministic
[message_1, message_2] =
Enum.sort_by([message_1, message_2], &IO.iodata_to_binary(&1.msgid_plural))
if IO.iodata_to_binary(message_1.msgid_plural) != IO.iodata_to_binary(message_2.msgid_plural) do
Logger.warning("""
Plural message for '#{IO.iodata_to_binary(message_1.msgid)}' is not matching:
Using '#{IO.iodata_to_binary(message_2.msgid_plural)}' instead of '#{IO.iodata_to_binary(message_1.msgid_plural)}'.
References: #{dump_references(message_1.references ++ message_2.references)}\
""")
end
merge_messages_after_checks(message_1, message_2)
end
defp merge_messages(message_1, message_2), do: merge_messages_after_checks(message_1, message_2)
defp merge_messages_after_checks(message_1, message_2) do
message_1
|> Map.put(:references, message_1.references ++ message_2.references)
|> Map.put(
:extracted_comments,
Enum.uniq(message_1.extracted_comments ++ message_2.extracted_comments)
)
end
defp dump_references(references) do
references
|> List.flatten()
|> Enum.map(fn
{file, line} -> [file, ":", Integer.to_string(line)]
file -> file
end)
|> Enum.intersperse(", ")
end
end