Have you been able to found a formal grammar for the ICU message format? I’ve only found informal descriptions and a bunch of slightly incomplete implementations. And more important: is there anyone using ICU messages in practice instead of something simpler like gettext?
Another big problem with MessageFormat is that although I can find the informal description for the message format, I can’t find a standardized file format to store translations. Gettext has one, which means that although the .po
/.pot
files are generated by Elixir itself, they can be edited with some easy-to-use external tools.
I’ve been thinking of a very simple API, consisting of a single (although a little verbose) translate/2
macro such as this:
use SuperTranslator
# This imports the `translate/2` macro.
# You need to `use` the SuperTranslator because my next idea requires
# registering a custom module attribute
message =
translate("At {time,time} on {date,date}, there was {what} on planet {planet,number,integer}.",
domain: "war_of_the_starse",
context: "A famous event that transpired near Alderaan",
variables: [
time: Time.utc_now(),
date: Date.utc_today(),
what: "a great disturbance in the force",
planet: 7
]
)
The :domain
key is the same as the one in gettext
and the :context
key helps supplying some context to the translators. The variables are inserted into the string according to ICU’s rules.
Unrelated to the use of ICU vs Gettext or some other standard, I’ve stumbled upon and idea, which could probably be incorporated into Gettext. Currently, Gettext doesn’t let you internationalize strings in dependencies, because it needs to gather strings while the application is compiled, store them in a GenServer (actually an Agent), and then compile the translations. This doesn’t work with dependencies because those have already been compiled when the main application is compiled.
I’ve built a proof of concept which bypasses this: whenever SuperTranslator
(just a name I’m using currently) is use
d, it registers a module attribute as an accumulator, which is set by the translate
macro. That attribute is then stored inside a function by a @before_compile
hook. This persists the translate
calls in a way that can be found by other applications. Then, the calls can be “consolidated”, for example by doing this:
defmodule MyTranslator do
use SuperTranslator.Translator, default_locale: "..."
end
After this “consolidation” (I’m deliberately hiding the actual technical details of how this works), the translate calls in the dependencies would be translated too.
I could advance into a full library very easily. Even parsing MessageFormat doesn’t seem too hard. The only thing that’s holding me back is the lack of a a file format to store the translations (so that they can be edited by translators)…
EDIT: this page seems to be a good resource for MessageFormat - https://messageformat.github.io/messageformat/page-about