Translate labels and messages
v0.0.1
Every word Gervaz draws goes through Gettext: the library's own wording, such as "Load More" and "Retry", and the labels, prompts and messages you write in the DSL. This guide points Gervaz at your Gettext backend, marks the strings that only the DSL holds so that mix gettext.extract finds them, and carries the locale into work that runs in another process. The examples come from the tags, redirects and layouts resources of Mishka CMS.
Before you begin
- Have a Gettext backend in your application, with its own
priv/gettextfolder. - Know the two domains. Gervaz uses the
mishka_gervazdomain for everything, except the messages a form field type refuses a value with and the Ash error messages it shows, which use theerrorsdomain.
Point Gervaz at your backend
Gervaz ships its own backend, MishkaGervaz.Gettext, with templates in priv/gettext. To translate through your backend instead, set it in the config:
config :mishka_gervaz, :gettext_backend, MyApp.GettextThen give your translators both templates of the library, priv/gettext/mishka_gervaz.pot and priv/gettext/errors.pot, merged into the templates of your backend. The files for a locale are priv/gettext/<locale>/LC_MESSAGES/mishka_gervaz.po and errors.po. Mishka CMS does the merge with a Mix task that reads the .pot files from the Gervaz dependency and adds their messages to its own, after mix gettext.extract.
Write labels as functions
Label options such as label, placeholder and description accept a string or a zero-arity function. Prefer the function, because it runs in the locale of the page when the label is drawn:
use MishkaGervaz.Messages, backend: MishkaCmsCore.Gettext
column :name do
label fn -> dgettext("mishka_gervaz", "Name") end
enduse MishkaGervaz.Messages injects the Gettext macros into the module. With backend: it uses that backend, which lets mix gettext.extract find the strings. Without it, the macros use the :gettext_backend of the config, or MishkaGervaz.Gettext.
Mark strings that only the DSL holds
Gervaz translates every plain string the DSL holds when it draws it, in the locale of the caller. That covers labels, placeholders, prompts, {label, value} option labels, confirm messages, empty and error states, notices, headers and footers. A function value is called as it is and is not translated again.
A plain string is not found by mix gettext.extract unless it is marked. Use dgettext_noop, which returns the English string and lets Gervaz translate it at draw time:
empty_state message: dgettext_noop("mishka_gervaz", "No redirects yet"), icon: "hero-link"
filter :status, :select do
ui do
prompt dgettext_noop("mishka_gervaz", "Any status")
end
end
bulk_actions do
action :archive,
handler: :destroy,
confirm: dgettext_noop("mishka_gervaz", "Archive selected redirects?")
endA string with %{...} in it is returned as it is. Fill its values with a function: fn -> dgettext("mishka_gervaz", "...", name: name) end.
Translate option lists
An option list can hold labels as plain strings or as translated ones. The list may be a function that runs when the options load, so it can call dgettext/2 directly:
field :status do
options fn ->
[
{dgettext("mishka_gervaz", "Draft"), :draft},
{dgettext("mishka_gervaz", "Published"), :published}
]
end
endKeep the locale in work that runs elsewhere
A process you start has no Gettext locale of its own. A table or form loads in the locale of its page, but your own asynchronous work that returns words starts in the default locale. Use these functions in place of the plain ones:
MishkaGervaz.Messages.start_async/4in place ofPhoenix.LiveView.start_async/4.MishkaGervaz.Messages.assign_async/4in place ofPhoenix.LiveView.assign_async/4.MishkaGervaz.Messages.in_caller_locale/1to wrap a function of arity 0 or 1 that you give toTask.async/1orTask.async_stream/3.
The tags page loads the version history this way:
socket
|> assign(:versions_loading, true)
|> MishkaGervaz.Messages.start_async(:load_versions, fn ->
...
end)All three also carry the values of the process dictionary keys that you register with MishkaGervaz.Messages.carry_process_keys/1, for work that reads, say, the tenant its caller put in the process dictionary. No key is registered by default.
Next steps
- Build an admin table and Build an admin form: the labels these guides translate.
- React to table and form events with hooks: the wording of default error flashes.