I currently have great success by using the simple technique described in https://www.gizra.com/content/elm-i18n-type-safety/ (which I found by following the advice in https://github.com/lukewestby/elm-i18n).
What is great with it is that you can’t forget to translate things and you can also have localization of any type, for example date formats, 12/24 hour format, number formats, etc.
I have something like this:
module I10n exposing (Language(..), TranslationId(..), i10n)
import FormatNumber exposing (format)
import FormatNumber.Locales exposing (frenchLocale, usLocale)
import Time.DateTime as DateTime exposing (DateTime)
type Language
= English
| French
type TranslationId
= Hello
| Date DateTime
| FloatValue Float
i10n : Language -> TranslationId -> String
i10n lang id =
case lang of
English ->
case id of
Hello ->
"Hello"
Date datetime ->
String.concat
[ leadingZero (DateTime.month datetime)
, "/"
, leadingZero (DateTime.day datetime)
, "/"
, toString (DateTime.year datetime)
]
FloatValue value ->
format { usLocale | decimals = 2 } value
French ->
case id of
Hello ->
"Bonjour"
Date datetime ->
String.concat
[ leadingZero (DateTime.day datetime)
, "/"
, leadingZero (DateTime.month datetime)
, "/"
, toString (DateTime.year datetime)
]
FloatValue value ->
format { frenchLocale | decimals = 2 } value
For now, I am really satisfied by this method, but I don’t have that many strings in my app, and I don’t mind having all translations embedded, so YMMV. I guess translated strings could still be loaded through HTTP if needed.
Edit: they also have a script to translate i18n key/value JSON files to Elm types: