Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Runtime I18n in Elm

Runtime I18n in Elm

A presentation outlining a sample app implementation of runtime internationalisation (i18n), and a little bit of type-safe i18n, in Elm.

Presented at the Sydney Elm meetup on 21 June 2018.

Presentation slide deck markdown and speaker notes (useable in Deckset 2):
https://github.com/paulfioravanti/presentations/blob/master/runtime_i18n_in_elm

Abda861707b1e78e0fce47ced55f84ee?s=128

Paul Fioravanti

June 21, 2018
Tweet

Transcript

  1. Runtime I18n in Elm

  2. Deceptively COMPLEX @paulfioravanti 2

  3. Pre-build Phase elm-i18n @paulfioravanti 3

  4. Dynamically Loaded elm-i18next @paulfioravanti 4

  5. @paulfioravanti 5

  6. None
  7. None
  8. None
  9. Back End 4 Provide translations in JSON @paulfioravanti 9

  10. Back End 4 Provide translations in JSON 4 Store selected

    language in localStorage @paulfioravanti 10
  11. Back End 4 Provide translations in JSON 4 Store selected

    language in localStorage 4 Explore type safety options @paulfioravanti 11
  12. None
  13. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 13
  14. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 14
  15. module Styles exposing (..) main_ : String main_ = [

    "bg-dark-pink" , "overflow-container" , "pt3" , "sans-serif" , "vh-100" , "white" ] |> String.join " " @paulfioravanti 15
  16. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 16
  17. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 17
  18. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 18
  19. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ content ] content : Html Msg content = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading ] ] heading : Html Msg heading = h1 [ class Styles.heading ] [ text "Vertically centering things in css is easy!" ] @paulfioravanti 19
  20. LANGUAGE Dropdown @paulfioravanti 20

  21. Language Dropdown 4 Show current language on menu @paulfioravanti 21

  22. Language Dropdown 4 Show current language on menu 4 Reveal

    available languages @paulfioravanti 22
  23. Language Dropdown 4 Show current language on menu 4 Reveal

    available languages 4 Change language @paulfioravanti 23
  24. Language Dropdown 4 Show current language on menu 4 Reveal

    available languages 4 Change language 4 Close on "blur" @paulfioravanti 24
  25. module LanguageDropdown exposing (view) view : Html msg view =

    div [ class Styles.dropdownContainer ] [ currentSelection ] currentSelection : Html msg currentSelection = p [ class Styles.currentSelection ] [ span [] [ text "English" ] , span [ Styles.caret ] [ text "▾" ] ] @paulfioravanti 25
  26. module LanguageDropdown exposing (view) view : Html msg view =

    div [ class Styles.dropdownContainer ] [ currentSelection ] currentSelection : Html msg currentSelection = p [ class Styles.currentSelection ] [ span [] [ text "English" ] , span [ Styles.caret ] [ text "▾" ] ] @paulfioravanti 26
  27. module LanguageDropdown exposing (view) view : Html msg view =

    div [ class Styles.dropdownContainer ] [ currentSelection ] currentSelection : Html msg currentSelection = p [ class Styles.currentSelection ] [ span [] [ text "English" ] , span [ Styles.caret ] [ text "▾" ] ] @paulfioravanti 27
  28. module LanguageDropdown exposing (view) view : Html msg view =

    div [ class Styles.dropdownContainer ] [ currentSelection ] currentSelection : Html msg currentSelection = p [ class Styles.currentSelection ] [ span [] [ text "English" ] , span [ Styles.caret ] [ text "▾" ] ] @paulfioravanti 28
  29. module Main exposing (main) import LanguageDropdown view : Model ->

    Html Msg view model = main_ [ class Styles.main_ ] [ LanguageDropdown.view , content ] @paulfioravanti 29
  30. module Main exposing (main) import LanguageDropdown view : Model ->

    Html Msg view model = main_ [ class Styles.main_ ] [ LanguageDropdown.view , content ] @paulfioravanti 30
  31. None
  32. Language DROPDOWN LIST @paulfioravanti 32

  33. view : Html msg view = div [ class Styles.dropdownContainer

    ] [ currentSelection , dropdownList ] dropdownList : Html msg dropdownList = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class Styles.dropdownList ] (List.map dropdownListItem selectableLanguages) dropdownListItem : String -> Html msg dropdownListItem language = li [ class Styles.dropdownListItem ] [ span [] [ text language ] ] @paulfioravanti 33
  34. view : Html msg view = div [ class Styles.dropdownContainer

    ] [ currentSelection , dropdownList ] dropdownList : Html msg dropdownList = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class Styles.dropdownList ] (List.map dropdownListItem selectableLanguages) dropdownListItem : String -> Html msg dropdownListItem language = li [ class Styles.dropdownListItem ] [ span [] [ text language ] ] @paulfioravanti 34
  35. view : Html msg view = div [ class Styles.dropdownContainer

    ] [ currentSelection , dropdownList ] dropdownList : Html msg dropdownList = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class Styles.dropdownList ] (List.map dropdownListItem selectableLanguages) dropdownListItem : String -> Html msg dropdownListItem language = li [ class Styles.dropdownListItem ] [ span [] [ text language ] ] @paulfioravanti 35
  36. view : Html msg view = div [ class Styles.dropdownContainer

    ] [ currentSelection , dropdownList ] dropdownList : Html msg dropdownList = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class Styles.dropdownList ] (List.map dropdownListItem selectableLanguages) dropdownListItem : String -> Html msg dropdownListItem language = li [ class Styles.dropdownListItem ] [ span [] [ text language ] ] @paulfioravanti 36
  37. view : Html msg view = div [ class Styles.dropdownContainer

    ] [ currentSelection , dropdownList ] dropdownList : Html msg dropdownList = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class Styles.dropdownList ] (List.map dropdownListItem selectableLanguages) dropdownListItem : String -> Html msg dropdownListItem language = li [ class Styles.dropdownListItem ] [ span [] [ text language ] ] @paulfioravanti 37
  38. None
  39. SHOW/HIDE Available Languages @paulfioravanti 39

  40. Show/Hide Available Languages 4 Flag to showAvailableLanguages in model @paulfioravanti

    40
  41. Show/Hide Available Languages 4 Flag to showAvailableLanguages in model 4

    Toggle dropdown list visibility (ShowAvailableLanguages) @paulfioravanti 41
  42. Show/Hide Available Languages 4 Flag to showAvailableLanguages in model 4

    Toggle dropdown list visibility (ShowAvailableLanguages) 4 Hide dropdown list on "blur" (CloseAvailableLanguages) @paulfioravanti 42
  43. type alias Model = { showAvailableLanguages : Bool } init

    : ( Model, Cmd Msg ) init = ( { showAvailableLanguages = False }, Cmd.none ) @paulfioravanti 43
  44. type Msg = CloseAvailableLanguages | ShowAvailableLanguages @paulfioravanti 44

  45. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of CloseAvailableLanguages -> ( { model | showAvailableLanguages = False } , Cmd.none ) ShowAvailableLanguages -> ( { model | showAvailableLanguages = not model.showAvailableLanguages } , Cmd.none ) @paulfioravanti 45
  46. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of CloseAvailableLanguages -> ( { model | showAvailableLanguages = False } , Cmd.none ) ShowAvailableLanguages -> ( { model | showAvailableLanguages = not model.showAvailableLanguages } , Cmd.none ) @paulfioravanti 46
  47. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of CloseAvailableLanguages -> ( { model | showAvailableLanguages = False } , Cmd.none ) ShowAvailableLanguages -> ( { model | showAvailableLanguages = not model.showAvailableLanguages } , Cmd.none ) @paulfioravanti 47
  48. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of CloseAvailableLanguages -> ( { model | showAvailableLanguages = False } , Cmd.none ) ShowAvailableLanguages -> ( { model | showAvailableLanguages = not model.showAvailableLanguages } , Cmd.none ) @paulfioravanti 48
  49. view : Model -> Html msg view { showAvailableLanguages }

    = div [ class Styles.dropdownContainer ] [ currentSelection showAvailableLanguages , dropdownList showAvailableLanguages ] currentSelection : Bool -> Html Msg currentSelection showAvailableLanguages = p [ class (Styles.currentSelection showAvailableLanguages) , onClick ShowAvailableLanguages ] [ span [] [ text "English" ] , span [ class Styles.caret ] [ text "▾" ] ] @paulfioravanti 49
  50. view : Model -> Html msg view { showAvailableLanguages }

    = div [ class Styles.dropdownContainer ] [ currentSelection showAvailableLanguages , dropdownList showAvailableLanguages ] currentSelection : Bool -> Html Msg currentSelection showAvailableLanguages = p [ class (Styles.currentSelection showAvailableLanguages) , onClick ShowAvailableLanguages ] [ span [] [ text "English" ] , span [ class Styles.caret ] [ text "▾" ] ] @paulfioravanti 50
  51. view : Model -> Html msg view { showAvailableLanguages }

    = div [ class Styles.dropdownContainer ] [ currentSelection showAvailableLanguages , dropdownList showAvailableLanguages ] currentSelection : Bool -> Html Msg currentSelection showAvailableLanguages = p [ class (Styles.currentSelection showAvailableLanguages) , onClick ShowAvailableLanguages ] [ span [] [ text "English" ] , span [ class Styles.caret ] [ text "▾" ] ] @paulfioravanti 51
  52. view : Model -> Html msg view { showAvailableLanguages }

    = div [ class Styles.dropdownContainer ] [ currentSelection showAvailableLanguages , dropdownList showAvailableLanguages ] dropdownList : Bool -> Html msg dropdownList showAvailableLanguages = let selectableLanguages = [ "Italiano", "೔ຊޠ" ] in ul [ class (Styles.dropdownList showAvailableLanguages) ] (List.map dropdownListItem selectableLanguages) @paulfioravanti 52
  53. dropdownList : Bool -> String dropdownList showAvailableLanguages = let displayClasses

    = if showAvailableLanguages then [ "flex", "flex-column" ] else [ "dn" ] in [ "absolute" , "b--white" , -- ... ] ++ displayClasses |> String.join " " @paulfioravanti 53
  54. dropdownList : Bool -> String dropdownList showAvailableLanguages = let displayClasses

    = if showAvailableLanguages then [ "flex", "flex-column" ] else [ "dn" ] in [ "absolute" , "b--white" , -- ... ] ++ displayClasses |> String.join " " @paulfioravanti 54
  55. dropdownList : Bool -> String dropdownList showAvailableLanguages = let displayClasses

    = if showAvailableLanguages then [ "flex", "flex-column" ] else [ "dn" ] in [ "absolute" , "b--white" , -- ... ] ++ displayClasses |> String.join " " @paulfioravanti 55
  56. dropdownList : Bool -> String dropdownList showAvailableLanguages = let displayClasses

    = if showAvailableLanguages then [ "flex", "flex-column" ] else [ "dn" ] in [ "absolute" , "b--white" , -- ... ] ++ displayClasses |> String.join " " @paulfioravanti 56
  57. Subscribe to MOUSE CLICKS @paulfioravanti 57

  58. ! elm-package install -y elm-lang/mouse @paulfioravanti 58

  59. import Mouse subscriptions : Model -> Sub Msg subscriptions model

    = if model.showAvailableLanguages then Mouse.clicks (\_ -> CloseAvailableLanguages) else Sub.none @paulfioravanti 59
  60. None
  61. Language SWITCHING @paulfioravanti 61

  62. elm-i18next elm-package install -y ChristophP/elm-i18next elm-package install -y elm-lang/http @paulfioravanti

    62
  63. Translations ▾ public/ ▾ locale/ translations.en.json translations.it.json translations.ja.json @paulfioravanti 63

  64. { "verticallyCenteringInCssIsEasy": "Vertically centering things in css is easy!" }

    { "verticallyCenteringInCssIsEasy": "Centrare verticalmente con css è facile!" } { "verticallyCenteringInCssIsEasy": "CSSͰਨ௚ηϯλϦϯά͸؆୯ͩΑʂ" } @paulfioravanti 64
  65. module Translations exposing (Lang(..), getLnFromCode) type Lang = En |

    It | Ja getLnFromCode : String -> Lang getLnFromCode code = case code of "en" -> En "it" -> It "ja" -> Ja _ -> En @paulfioravanti 65
  66. type Lang = En | It | Ja @paulfioravanti 66

  67. getLnFromCode : String -> Lang getLnFromCode code = case code

    of "en" -> En "it" -> It "ja" -> Ja _ -> En @paulfioravanti 67
  68. import Http exposing (Error) import I18Next exposing (Translations) import Translations

    exposing (Lang) type Msg = ChangeLanguage Lang | CloseAvailableLanguages | FetchTranslations (Result Error Translations) | ShowAvailableLanguages @paulfioravanti 68
  69. import Http exposing (Error) import I18Next exposing (Translations) import Translations

    exposing (Lang) type Msg = ChangeLanguage Lang | CloseAvailableLanguages | FetchTranslations (Result Error Translations) | ShowAvailableLanguages @paulfioravanti 69
  70. import Http exposing (Error) import I18Next exposing (Translations) import Translations

    exposing (Lang) type Msg = ChangeLanguage Lang | CloseAvailableLanguages | FetchTranslations (Result Error Translations) | ShowAvailableLanguages @paulfioravanti 70
  71. fetchTranslations : Lang -> Cmd Msg fetchTranslations language = language

    |> toTranslationsUrl |> I18Next.fetchTranslations FetchTranslations toTranslationsUrl : Lang -> String toTranslationsUrl language = let translationLanguage = language |> toString |> String.toLower in "/locale/translations." ++ translationLanguage ++ ".json" @paulfioravanti 71
  72. fetchTranslations : Lang -> Cmd Msg fetchTranslations language = language

    |> toTranslationsUrl |> I18Next.fetchTranslations FetchTranslations toTranslationsUrl : Lang -> String toTranslationsUrl language = let translationLanguage = language |> toString |> String.toLower in "/locale/translations." ++ translationLanguage ++ ".json" @paulfioravanti 72
  73. fetchTranslations : Lang -> Cmd Msg fetchTranslations language = language

    |> toTranslationsUrl |> I18Next.fetchTranslations FetchTranslations toTranslationsUrl : Lang -> String toTranslationsUrl language = let translationLanguage = language |> toString |> String.toLower in "/locale/translations." ++ translationLanguage ++ ".json" @paulfioravanti 73
  74. fetchTranslations : Lang -> Cmd Msg fetchTranslations language = language

    |> toTranslationsUrl |> I18Next.fetchTranslations FetchTranslations toTranslationsUrl : Lang -> String toTranslationsUrl language = let translationLanguage = language |> toString |> String.toLower in "/locale/translations." ++ translationLanguage ++ ".json" @paulfioravanti 74
  75. import I18Next exposing (Translations) type alias Model = { currentLanguage

    : Lang , showAvailableLanguages : Bool , translations : Translations } init : ( Model, Cmd Msg ) init = ( { currentLanguage = En , showAvailableLanguages = False , translations = I18Next.initialTranslations } , fetchTranslations En ) @paulfioravanti 75
  76. import I18Next exposing (Translations) type alias Model = { currentLanguage

    : Lang , showAvailableLanguages : Bool , translations : Translations } init : ( Model, Cmd Msg ) init = ( { currentLanguage = En , showAvailableLanguages = False , translations = I18Next.initialTranslations } , fetchTranslations En ) @paulfioravanti 76
  77. import I18Next exposing (Translations) type alias Model = { currentLanguage

    : Lang , showAvailableLanguages : Bool , translations : Translations } init : ( Model, Cmd Msg ) init = ( { currentLanguage = En , showAvailableLanguages = False , translations = I18Next.initialTranslations } , fetchTranslations En ) @paulfioravanti 77
  78. import I18Next exposing (Translations) type alias Model = { currentLanguage

    : Lang , showAvailableLanguages : Bool , translations : Translations } init : ( Model, Cmd Msg ) init = ( { currentLanguage = En , showAvailableLanguages = False , translations = I18Next.initialTranslations } , fetchTranslations En ) @paulfioravanti 78
  79. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of -- ... ChangeLanguage language -> ( { model | currentLanguage = language } , fetchTranslations language ) FetchTranslations (Ok translations) -> ( { model | translations = translations }, Cmd.none ) FetchTranslations (Err msg) -> ( model, Cmd.none ) @paulfioravanti 79
  80. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of -- ... ChangeLanguage language -> ( { model | currentLanguage = language } , fetchTranslations language ) FetchTranslations (Ok translations) -> ( { model | translations = translations }, Cmd.none ) FetchTranslations (Err msg) -> ( model, Cmd.none ) @paulfioravanti 80
  81. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of -- ... ChangeLanguage language -> ( { model | currentLanguage = language } , fetchTranslations language ) FetchTranslations (Ok translations) -> ( { model | translations = translations }, Cmd.none ) FetchTranslations (Err msg) -> ( model, Cmd.none ) @paulfioravanti 81
  82. DYNAMIC Values @paulfioravanti 82

  83. module Language exposing (availableLanguages, langToString) import Translations exposing (Lang(En, It,

    Ja)) availableLanguages : List Lang availableLanguages = [ En, It, Ja ] langToString : Lang -> String langToString language = case language of En -> "English" It -> "Italiano" Ja -> "೔ຊޠ" @paulfioravanti 83
  84. module Language exposing (availableLanguages, langToString) import Translations exposing (Lang(En, It,

    Ja)) availableLanguages : List Lang availableLanguages = [ En, It, Ja ] langToString : Lang -> String langToString language = case language of En -> "English" It -> "Italiano" Ja -> "೔ຊޠ" @paulfioravanti 84
  85. module Language exposing (availableLanguages, langToString) import Translations exposing (Lang(En, It,

    Ja)) availableLanguages : List Lang availableLanguages = [ En, It, Ja ] langToString : Lang -> String langToString language = case language of En -> "English" It -> "Italiano" Ja -> "೔ຊޠ" @paulfioravanti 85
  86. module Language exposing (availableLanguages, langToString) import Translations exposing (Lang(En, It,

    Ja)) availableLanguages : List Lang availableLanguages = [ En, It, Ja ] langToString : Lang -> String langToString language = case language of En -> "English" It -> "Italiano" Ja -> "೔ຊޠ" @paulfioravanti 86
  87. module LanguageDropdown exposing (view) view : Model -> Html Msg

    view { currentLanguage, showAvailableLanguages } = let selectableLanguages = Language.availableLanguages |> List.filter (\language -> language /= currentLanguage) in div [ class Styles.dropdownContainer ] [ currentSelection currentLanguage showAvailableLanguages , dropdownList showAvailableLanguages selectableLanguages ] @paulfioravanti 87
  88. module LanguageDropdown exposing (view) view : Model -> Html Msg

    view { currentLanguage, showAvailableLanguages } = let selectableLanguages = Language.availableLanguages |> List.filter (\language -> language /= currentLanguage) in div [ class Styles.dropdownContainer ] [ currentSelection currentLanguage showAvailableLanguages , dropdownList showAvailableLanguages selectableLanguages ] @paulfioravanti 88
  89. module LanguageDropdown exposing (view) view : Model -> Html Msg

    view { currentLanguage, showAvailableLanguages } = let selectableLanguages = Language.availableLanguages |> List.filter (\language -> language /= currentLanguage) in div [ class Styles.dropdownContainer ] [ currentSelection currentLanguage showAvailableLanguages , dropdownList showAvailableLanguages selectableLanguages ] @paulfioravanti 89
  90. currentSelection : Lang -> Bool -> Html Msg currentSelection currentLanguage

    showAvailableLanguages = p [ class (Styles.currentSelection showAvailableLanguages) , onClick ShowAvailableLanguages ] [ span [] [ text (Language.langToString currentLanguage) ] , span [ class Styles.caret ] [ text "▾" ] ] @paulfioravanti 90
  91. currentSelection : Lang -> Bool -> Html Msg currentSelection currentLanguage

    showAvailableLanguages = p [ class (Styles.currentSelection showAvailableLanguages) , onClick ShowAvailableLanguages ] [ span [] [ text (Language.langToString currentLanguage) ] , span [ class Styles.caret ] [ text "▾" ] ] @paulfioravanti 91
  92. dropdownList : Bool -> List Lang -> Html Msg dropdownList

    showAvailableLanguages selectableLanguages = ul [ class (Styles.dropdownList showAvailableLanguages) ] (List.map dropdownListItem selectableLanguages) dropdownListItem : Lang -> Html Msg dropdownListItem language = li [ class Styles.dropdownListItem , onClick (ChangeLanguage language) ] [ span [] [ text (Language.langToString language) ] ] @paulfioravanti 92
  93. dropdownList : Bool -> List Lang -> Html Msg dropdownList

    showAvailableLanguages selectableLanguages = ul [ class (Styles.dropdownList showAvailableLanguages) ] (List.map dropdownListItem selectableLanguages) dropdownListItem : Lang -> Html Msg dropdownListItem language = li [ class Styles.dropdownListItem , onClick (ChangeLanguage language) ] [ span [] [ text (Language.langToString language) ] ] @paulfioravanti 93
  94. dropdownList : Bool -> List Lang -> Html Msg dropdownList

    showAvailableLanguages selectableLanguages = ul [ class (Styles.dropdownList showAvailableLanguages) ] (List.map dropdownListItem selectableLanguages) dropdownListItem : Lang -> Html Msg dropdownListItem language = li [ class Styles.dropdownListItem , onClick (ChangeLanguage language) ] [ span [] [ text (Language.langToString language) ] ] @paulfioravanti 94
  95. None
  96. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ LanguageDropdown.view , content model.translations ] content : Translations -> Html Msg content translations = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading translations ] ] heading : Translations -> Html Msg heading translations = h1 [ class Styles.heading ] [ text (I18Next.t translations "verticallyCenteringInCssIsEasy")] @paulfioravanti 96
  97. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ LanguageDropdown.view , content model.translations ] content : Translations -> Html Msg content translations = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading translations ] ] heading : Translations -> Html Msg heading translations = h1 [ class Styles.heading ] [ text (I18Next.t translations "verticallyCenteringInCssIsEasy")] @paulfioravanti 97
  98. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ LanguageDropdown.view , content model.translations ] content : Translations -> Html Msg content translations = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading translations ] ] heading : Translations -> Html Msg heading translations = h1 [ class Styles.heading ] [ text (I18Next.t translations "verticallyCenteringInCssIsEasy")] @paulfioravanti 98
  99. view : Model -> Html Msg view model = main_

    [ class Styles.main_ ] [ LanguageDropdown.view , content model.translations ] content : Translations -> Html Msg content translations = article [ class Styles.article ] [ div [ class Styles.articleContainer ] [ heading translations ] ] heading : Translations -> Html Msg heading translations = h1 [ class Styles.heading ] [ text (I18Next.t translations "verticallyCenteringInCssIsEasy")] @paulfioravanti 99
  100. None
  101. DETECT User Language @paulfioravanti 101

  102. Detect User Language 4 navigator.language 4 navigator.userLanguage (IE) @paulfioravanti 102

  103. import "tachyons" import { Main } from "./Main.elm" const appContainer

    = document.getElementById("root") if (appContainer) { Main.embed(appContainer, { language: getLanguage() }) } function getLanguage() { return navigator.language || navigator.userLanguage } @paulfioravanti 103
  104. import "tachyons" import { Main } from "./Main.elm" const appContainer

    = document.getElementById("root") if (appContainer) { Main.embed(appContainer, { language: getLanguage() }) } function getLanguage() { return navigator.language || navigator.userLanguage } @paulfioravanti 104
  105. import "tachyons" import { Main } from "./Main.elm" const appContainer

    = document.getElementById("root") if (appContainer) { Main.embed(appContainer, { language: getLanguage() }) } function getLanguage() { return navigator.language || navigator.userLanguage } @paulfioravanti 105
  106. import "tachyons" import { Main } from "./Main.elm" const appContainer

    = document.getElementById("root") if (appContainer) { Main.embed(appContainer, { language: getLanguage() }) } function getLanguage() { return navigator.language || navigator.userLanguage } @paulfioravanti 106
  107. import Json.Decode as Decode exposing (Value) type alias Flags =

    { language : Value } @paulfioravanti 107
  108. init : Flags -> ( Model, Cmd Msg ) init

    flags = let language = flags.language |> Decode.decodeValue Decode.string |> Language.langFromFlag in ( { currentLanguage = language , showAvailableLanguages = False , translations = I18Next.initialTranslations } , Cmd.fetchTranslations language ) @paulfioravanti 108
  109. init : Flags -> ( Model, Cmd Msg ) init

    flags = let language = flags.language |> Decode.decodeValue Decode.string |> Language.langFromFlag in ( { currentLanguage = language , showAvailableLanguages = False , translations = I18Next.initialTranslations } , Cmd.fetchTranslations language ) @paulfioravanti 109
  110. module Language exposing (..) langFromFlag : Result String String ->

    Lang langFromFlag language = case language of Ok language -> Translations.getLnFromCode language Err _ -> En @paulfioravanti 110
  111. STORE Language Preference @paulfioravanti 111

  112. port module Main exposing (..) port storeLanguageInLocalStorage : String ->

    Cmd msg storeLanguage : Lang -> Cmd msg storeLanguage language = language |> toString |> String.toLower |> storeLanguageInLocalStorage @paulfioravanti 112
  113. port module Main exposing (..) port storeLanguageInLocalStorage : String ->

    Cmd msg storeLanguage : Lang -> Cmd msg storeLanguage language = language |> toString |> String.toLower |> storeLanguageInLocalStorage @paulfioravanti 113
  114. port module Main exposing (..) port storeLanguageInLocalStorage : String ->

    Cmd msg storeLanguage : Lang -> Cmd msg storeLanguage language = language |> toString |> String.toLower |> storeLanguageInLocalStorage @paulfioravanti 114
  115. if (appContainer) { const app = Main.embed(appContainer, { language: getLanguage()

    }) app.ports.storeLanguageInLocalStorage.subscribe((language) => { localStorage.setItem("elm-i18n-example-language", language) }) } function getLanguage() { return localStorage.getItem("elm-i18n-example-language") || navigator.language || navigator.userLanguage } @paulfioravanti 115
  116. if (appContainer) { const app = Main.embed(appContainer, { language: getLanguage()

    }) app.ports.storeLanguageInLocalStorage.subscribe((language) => { localStorage.setItem("elm-i18n-example-language", language) }) } function getLanguage() { return localStorage.getItem("elm-i18n-example-language") || navigator.language || navigator.userLanguage } @paulfioravanti 116
  117. if (appContainer) { const app = Main.embed(appContainer, { language: getLanguage()

    }) app.ports.storeLanguageInLocalStorage.subscribe((language) => { localStorage.setItem("elm-i18n-example-language", language) }) } function getLanguage() { return localStorage.getItem("elm-i18n-example-language") || navigator.language || navigator.userLanguage } @paulfioravanti 117
  118. if (appContainer) { const app = Main.embed(appContainer, { language: getLanguage()

    }) app.ports.storeLanguageInLocalStorage.subscribe((language) => { localStorage.setItem("elm-i18n-example-language", language) }) } function getLanguage() { return localStorage.getItem("elm-i18n-example-language") || navigator.language || navigator.userLanguage } @paulfioravanti 118
  119. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of -- ... ChangeLanguage language -> ( { model | currentLanguage = language } , Cmd.batch [ fetchTranslations language , storeLanguage language ] ) @paulfioravanti 119
  120. update : Msg -> Model -> ( Model, Cmd Msg

    ) update msg model = case msg of -- ... ChangeLanguage language -> ( { model | currentLanguage = language } , Cmd.batch [ fetchTranslations language , storeLanguage language ] ) @paulfioravanti 120
  121. None
  122. ! @paulfioravanti 122

  123. LINGERING Issues @paulfioravanti 123

  124. Lingering Issues 4 Translation key visible on refresh @paulfioravanti 124

  125. None
  126. Lingering Issues 4 Translation key visible on refresh 4 I18Next.t

    translations "thisKeyDoesNotExist" @paulfioravanti 126
  127. Lingering Issues 4 Translation key visible on refresh 4 I18Next.t

    translations "thisKeyDoesNotExist" 4 Missed translations and typos ignored @paulfioravanti 127
  128. TYPE-SAFE Translations @paulfioravanti 128

  129. npm install -g elm-i18n-gen @paulfioravanti 129

  130. elm-i18n-gen public/locale src/Translations.elm @paulfioravanti 130

  131. module Translations exposing (..) -- ... verticallyCenteringInCssIsEasy : Lang ->

    String verticallyCenteringInCssIsEasy lang = case lang of En -> "Vertically centering things in css is easy!" It -> "Centrare verticalmente con css è facile!" Ja -> "CSSͰਨ௚ηϯλϦϯά͸؆୯ͩΑʂ" @paulfioravanti 131
  132. module Translations exposing (..) type Lang = En | It

    | Ja getLnFromCode : String -> Lang getLnFromCode code = -- ... verticallyCenteringInCssIsEasy : Lang -> String verticallyCenteringInCssIsEasy lang = case lang of En -> "Vertically centering things in css is easy!" It -> "Centrare verticalmente con css è facile!" Ja -> "CSSͰਨ௚ηϯλϦϯά͸؆୯ͩΑʂ" @paulfioravanti 132
  133. module Translations exposing (..) -- ... verticallyCenteringInCssIsEasy : Lang ->

    String verticallyCenteringInCssIsEasy lang = case lang of En -> "Vertically centering things in css is easy!" It -> "Centrare verticalmente con css è facile!" Ja -> "CSSͰਨ௚ηϯλϦϯά͸؆୯ͩΑʂ" @paulfioravanti 133
  134. view : Model -> Html Msg view model = --

    ... content : Translations -> Html Msg content translations = -- ... heading : Translations -> Html Msg heading translations = h1 [ class Styles.heading ] [ text (I18Next.t translations "verticallyCenteringInCssIsEasy")] @paulfioravanti 134
  135. view : Model -> Html Msg view model = --

    ... content : Lang -> Html Msg content language = -- ... heading : Lang -> Html Msg heading language = h1 [ class Styles.heading ] [ text (Translations.verticallyCenteringInCssIsEasy language) ] @paulfioravanti 135
  136. Type-Safe Translations 4 No need to fetch translations @paulfioravanti 136

  137. Type-Safe Translations 4 No need to fetch translations 4 No

    translation key visible @paulfioravanti 137
  138. Type-Safe Translations 4 No need to fetch translations 4 No

    translation key visible 4 Compiler errors if translation not provided @paulfioravanti 138
  139. CONCLUSION @paulfioravanti 139

  140. @paulfioravanti 140

  141. None
  142. Links 4 https://github.com/paulfioravanti/elm-i18n-example 4 https://paulfioravanti.com/blog/2018/05/11/runtime- language-switching-in-elm/ @paulfioravanti 142

  143. Thanks! @paulfioravanti