The big picture for using elm-fluent looks as follows (with some design justifications thrown in). If you think this fits your needs, please see the Installation docs and the Tutorial for the details you’ll need to implement this in your app.

With elm-fluent, all localized text (i.e. text in a specific human language) is stored in .ftl files in a locales directory, and not in Elm source files.


Some tools such as gettext encourage you to put localized text in source code files, at least for ‘source language’, and then extract this text into other files from which translation is done for the other (‘destination’) languages.

That approach has some advantages, but major disadvantages when it comes to Fluent. Fluent is a language in its own right, that puts (just enough) power into the hands of translators to produce good translations, while maintaining proper separation of concerns.

For Fluent, developers need to put localized text into .ftl files, so the full power of that language will be used. This includes:

  • choosing good message IDs, and being aware of the issues surrounding changes to IDs or changes to text without changing IDs
  • adding comments that will help translators - this is often vital to give context, because a single English word could be interpreted as a noun or adjective or verb.
  • using Fluent constructs for things like variant selection, rather than using Elm flow control constructs for variant selection, which would cause the destination languages to suffer.

So, if your web app has a ‘notifications’ component with a title ‘Notifications’ and some intro text, you would have a locales/en/notifications.ftl file that looks something like this:

notifications-title = Notifications

notifications-intro = Hello { $username }, you have unread notifications

You compile this to Elm files using ftl2elm. Normally the generated .elm files, which appear under Ftl by default, should not be stored in your VCS, you store only the .ftl files. You can then use these generated functions from your Elm source code.

Your app first needs some way to determine the user’s current locale. This is usually best done by allowing them to choose from a list, and then saving this in the model somewhere. Let’s assume we have model.locale set up already. Then our Elm source code might look like this:

import Ftl.Translations.Notifications as T

viewNotifications model =
     Html.div []
              [ Html.h2 (T.notificationsTitle model.locale ())
              , Html.p (T.notificationsIntro model.locale { username = model.username })

The generated functions (in this case notificationsTitle and notificationsIntro) all follow the same pattern in terms of signature - they take a locale value and a strongly typed record of substitution parameters, and return a string. (For advanced use cases, the functions return Html and take an additional parameter that allows attributes to be added to the HTML).


Using a strongly typed record type means that we can catch the vast majority of errors at compile time. If a translator includes a parameter in their translation that is not passed by the developer, the code will not compile. We can also ensure that numbers get passed as numbers etc.

Depending on the locale parameter, the generated functions dispatch to the function for the correct language (we just have one so far).

You now need to distribute your .ftl files and get translations for the other languages you support. These are saved into the correct sub folder in your locales directory and committed to VCS. (Mozilla has developed the Pontoon system which provides a GUI for editing .ftl files, but elm-fluent doesn’t have good integration with it yet).

Finally, you can now compile the .ftl files for all the languages, compile your Elm app and deploy.