A while ago, I had a crack at internationalisation, and came up with the following setup:
- define the language in a global
model
- have a very simple function, to be used in
view
modules and functions
- the function has a signature of
localString : Language -> String -> String
localString
basically does a lookup in a global dictionary to find a translation from the word you provide to the language you provide.
- it will always give back a
String
, defaulting to the original word, if it cannot find the word you provide, or if it cannot find the translation to the language you provide.
- keep the global dictionary (and helper) functions NOT in the model, but in a separate file (it is pretty static data, which won't change in runtime).
- the
Language
type is a Union Type, to ensure we only have 'approved' languages.
- the actual dictionary uses conversions to string. The
Dict
type does not allow strong types as the key.
That way, using internationalisation has minimal impact on the rest of the code:
- You need to add a
Language
to your Model
(which you could get through JS port)
You can still use short and readable code in your views to translate, like
p [] [ text <| localString model.language "car" ]
All hardcoded strings in your own code remain in one simple default language, to keep the rest of your code readable.
Here is the gist of what I was working on, you can copy/ paste to elm-lang.org/try (not fully tested functionally or performance-wise with large numbers of strings and translations)
import Html exposing (div, p, text)
import Dict exposing (Dict)
-- Manage your languages below
type Language = English | Spanish | French
defaultLanguage : Language
defaultLanguage = English
languageToKey : Language -> LanguageKey
languageToKey language =
case language of
English -> "English"
Spanish -> "Spanish"
French -> "French"
keyToLanguage : LanguageKey -> Language
keyToLanguage key =
case key of
"English" -> English
"Spanish"-> Spanish
"French" -> French
_ -> defaultLanguage
english : LocalWord -> (Language, LocalWord)
english word =
(English, word)
spanish : LocalWord -> (Language, LocalWord)
spanish word =
(Spanish, word)
french : LocalWord -> (Language, LocalWord)
french word =
(French, word)
-- Internal stuff
type alias Word = String
type alias LocalWord = String
type alias LanguageKey = String
type alias Dictionary = Dict Word WordDict
type alias WordDict = Dict LanguageKey LocalWord
init : Dictionary
init =
Dict.fromList []
newLocalWord : Word -> (Language, LocalWord) -> Maybe WordDict -> Maybe WordDict
newLocalWord word (localLanguage, localWord) wordDict =
wordDict
|> Maybe.withDefault (Dict.fromList [])
|> Dict.insert (languageToKey defaultLanguage) word
|> Dict.insert (languageToKey localLanguage) localWord
|> Just
addTranslation : Word -> (Language, LocalWord) -> Dictionary -> Dictionary
addTranslation word newTranslation dictionary =
dictionary
|> Dict.update word (newLocalWord word newTranslation)
localString : Language -> Word -> LocalWord
localString language word =
let
wordEntry =
Dict.get word globalDictionary
localLanguage =
languageToKey language
in
case wordEntry of
Just wordDict ->
Dict.get localLanguage wordDict
|> Maybe.withDefault word
Nothing ->
word
add : Word -> List (Language, LocalWord) -> Dictionary -> Dictionary
add word translationList dictionary =
List.foldl (addTranslation word) dictionary translationList
-- BUILD DICTIONARY BELOW
globalDictionary : Dictionary
globalDictionary =
init
|> add "Hello" [ spanish "Hola", french "Bonjour" ]
|> add "Man" [ spanish "Hombre", french "Homme" ]
|> add "Child" [ french "Enfant" ]
-- For Elm-lang Try only
localModel =
{ language = Spanish }
main =
div []
[ p []
[ text <| "Hello in Spanish: "
++ localString localModel.language "Hello"
]
, p []
[ text <| "In dictionary, but not in Spanish: "
++ localString localModel.language "Child"
]
, p []
[ text <| "Is not in dictionary: "
++ localString localModel.language "Car"
]
]