Iām slightly hesitant to show what the generated code looks like when I havenāt actually written the implementation yet! Also, many of the details shouldnāt be important because you wonāt need to edit this code yourself.
I can show you want the current Python code that Iām generating looks like: https://github.com/django-ftl/python-fluent/blob/compiling_message_context/tests/test_compiler.py#L56
The basic idea is that each message gets turned into a function. The actual code obviously depends on how complex the messages are. In Elm, we can eliminate the errors
argument in the code (we can detect all errors at compile time). The simplest case would look like this:
a-message-id = This is a message
becomes:
aMessageId : Locale -> a -> String
aMessageId locale args = "This is a message"
locale
is unused in this case (it would be passed to Intl.NumberFormat
etc. if they were used), a
is a record type here that is also unused, but still included so that these functions all have the same parity.
This would be the English function. There would be a similar one for each language, and another āmasterā function which is the function you actually import and call from your own Elm code, passing in a locale and args. This function then dispatches to the correct one for the locale.
For a message that takes substitutions, we would find all the args in use for that message and replace a
with a record type that refers to each parameter. We assume args are strings, unless we can see they are something else (e.g. they are passed to the NUMBER
function, or perhaps weāll be able to make use of hints from semantic comments about variables once that addition to the spec is done).
Example:
a-message-with-an-arg = Hello { $name }, welcome to our website.
becomes:
aMessageWithAnArg : Locale -> { a | name : String } -> String
aMessageWithAnArg locale args =
String.concat
[ "Hello "
, args.name
, ", welcome to our website"
]
The important thing here is that you cannot forget to include the name
argument for this message, or get the wrong type.
More complex constructions (e.g. Fluent select expressions) will obviously get more complex than this, but it is all manageable (see the Python code for examples). The generated code will usually be a lot simpler than the Python version, because we can know the type of every argument, and can ensure it is present, so the error handling can go away.
The harder case is embedded HTML. My ideas so far are these:
We use a naming convention, or some semantic comments to identify messages that need HTML embedding. For example, a -html
suffix on the name would be enough:
my-message-html = You have <b>not registered</b>, please remember to do so.
The compiler then parses this message as an HTML fragment, and generates code that outputs Elm Html
values, with a different type signature for this kind of message:
myMessageHtml : Locale -> a -> List (ExtraAttrs msg) -> List (Html.Html msg)
myMessageHtml = [ Html.text "You have "
, Html.b []
[ Html.text "not registered" ]
, Html.text ", please remember to do so."
]
Obviously cases with substitutions will be harder to handle and more complex, and require different handling for substitutions into attributes, but still possible.
The ExtraAttrs
argument needs some explaining. This is a mechanism for attaching additional attributes to the generate Html nodes, which is necessary especially if you need some events attaching, or you have other attributes that you want to generate dynamically. I can go into detail about how this will work if you would like, but we should probably take this discussion to another place for that. Also there are many more details e.g. handling of numbers etc.
The main points are:
- translators should only have to edit the
.ftl
files, and preferably with some helpers like the Pontoon tool that Mozilla has developed - Elm is far too complex to be edited by translators, and we donāt want developers manually copying translations into Elm files.
- the developers should just be able to call a simple function, passing a locale and substitution args.
- that function should be very efficient - for the simple case of a static string it should just return a static string.
- that function should statically require any required parameters to be present and correctly typed.
- the other details of how that function works shouldnāt be an issue for the developer, because the generated code is never edited, always compiled from
.ftl
files.
Responding to some of your other points:
some sort of checking if pluralized translations have a value for (exactly?) all plural forms needed in the language
This could be done by some generic FTL checking tools I think, since the plural form handling is done within select expressions in the FTL code. It wouldnāt be that hard to build it into a compiler probably.
also having an export from Elm to FTL (so the generated translations are actually serializable data and not functions. Or maybe there is another way this can be achieved?)
My idea is that the āserializable dataā is just FTL files, from which you generate Elm code. Trying to parse Elm and extract parts is going to be extremely fragile and limiting, but the other way around will work fine.