What is a good way to internationalize an Elm application?

I need to internationalize user interface strings in my HTML ELM application in 3 different languages.

I think about this:

1) I will get currentLanguage from Javascript and pass it to ProgramWithFlags. I will learn the language in the model

2) I will set some types in my code

type alias Languages = English | French | Spanish -- One of these for each string I want to internationalize type alias InternationalizedStrings = StringHello | StringFoo | StringBar 

3) I will make a function to return each translated phrase for use in my submissions.

 getPhrase: InternationalizationString Languages -> string getPhrase stringId lang = case lang of English -> case stringId of StringHello -> "Hello" StringFoo -> "Foo" StringBar -> "Bar" French -> case stringId of StringHello -> "Bonjour" StringFoo -> "Oui" StringBar -> "Non" ... 

Is there a better way to do this? I have a lot of lines.

+6
source share
3 answers

If you need compiler errors, if you do not provide line feeds, your solution is on the right track.

If you want to either allow untranslated strings so far, or find the right type for each translatable string, you can switch to the Dict solution. redo it, just throw it away at http://elm-lang.org/try :

 import Dict exposing (Dict) import Html exposing (text) type Language = English | French | Spanish type alias Key = String main = text <| translate French "Hello" translate : Language -> Key -> String translate lang key = let dict = case lang of English -> Dict.fromList [ ( "Hello", "in english" ) ] French -> Dict.fromList [ ( "Hello", "salut" ) ] Spanish -> Dict.fromList [ ( "Hello", "hola" ) , ( "someKeyThatOnlyExistsInSpanish", "42" ) ] in Dict.get key dict |> Maybe.withDefault ("can not find translation for " ++ key) 
+4
source

Some time ago, I had a crack in internationalization, and I came up with the following setting:

  • defines a language in a global model
  • have a very simple function that will be used in modules and view functions
  • the function has a signature localString : Language -> String -> String
  • localString basically searches the global dictionary to find the translation from the word you provide in the language you provide.
  • it will always return a String , the default is the original word if it cannot find the word you provide, or if it cannot find a translation in the language you provide.
  • save the global dictionary (and auxiliary) of the NOT function in the model, but in a separate file (this is pretty static data that will not change at run time).
  • The Language type is the Union Type so that we have only "approved" languages.
  • the actual dictionary uses string conversions. The Dict type does not allow the use of strong types as a key.

Thus, the use of internationalization has minimal impact on the rest of the code:

  • You need to add Language to your model (which you can get through the JS port)
  • You can use short and readable code in your views for translation, for example

    p [] [ text <| localString model.language "car" ]

  • All hardcoded strings in your own code remain one simple default language to preserve the rest of your code.

This is the essence of what I worked on, you can copy / paste to elm-lang.org/try (not fully tested functionally or quickly, wise with a lot of lines 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" ] ] 
+2
source

I wrote a blog post about this a couple of months ago. If you have the opportunity, try using ADT over Dict , because Dict cannot give you the same type-level guarantees (which is why Dict.get returns Maybe a ). ADT can also have a data type, which you also execute according to the type also marked MyPhrase Int String , with which you can match pattern matching and use whatever toString method you want (for example, MyPhrase foo bar -> "My phrase contains " ++ toString foo ++ " & " ++ bar ++ "." ). At the same time, existing translation systems / services can make it difficult to use this method without writing a parser from .elm to .json or .po .

+2
source

Source: https://habr.com/ru/post/1011739/


All Articles