Skip to content

Instantly share code, notes, and snippets.

@vilterp
Last active July 18, 2017 11:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vilterp/ebb1d3022050d518df70 to your computer and use it in GitHub Desktop.
Save vilterp/ebb1d3022050d518df70 to your computer and use it in GitHub Desktop.

I18n with Records

Goals:

  1. Elm's typechecker enforces that
  • each localization has all the same messages.
  • the messages you are referring to in your view code exist.
  1. Only the localized strings for the current locale are sent across the wire
  2. Messages which have interpolations are typechecked as well

It's not even a library; just a pattern...

Example

I18n or Messages type

type alias I18n =
  { header :
      { logIn : String
      , signUp : String
      }
  , checkoutFlow :
      { placeOrder : String
      , youHaveNItems : Int -> String
      }
  }
-- could break into smaller named aliases

A localization (see approach #2)

module Localizations.EnUs where

english : I18n
english =
  { header =
      { logIn : "Log In"
      , signUp : "Sign Up"
      }
  , checkoutFlow =
      { placeOrder = "Place Order"
      , youHaveNItems = \n -> "You have " ++ toString n ++ " items" -- I want string iterpolation!
      }
  }

View code

Depending on the approach, you may or may not need to pass the messages for the current language... (See below)

viewCheckout : Signal.Address Action -> I18n -> Model -> Html
viewCheckout addr i18n model =
  p [] [text <| i18n.checkoutFlow.youHaveNItems model.numItems]
  button [...] [text i18n.checkoutFlow.placeOrder]

Approaches to setting the current locale

Both achieve goals 1 & 2; only the second achieves goal #3.

#1: JSON & Ports

Generate JSON values for each localization; use a port to supply one for the current locale (a value of type I18n). So you have port messages : I18n in your main module, and have to pass that down through all of your view functions. The server is responsible for putting the localization for the current locale onto the page as a JSON blob and passing it in when the module is initialized.

Evan had the same idea in this previous thread.

  • Pros:
    • No compile-time funny business compared w/ approach #2 below
    • Easy to generate localization JSON files from other sources, e.g. Rails YML localization files — sharing translations between Rails and Elm could be nice.
  • Cons:
    • You can't serialize template functions (e.g. youHaveNItems in the example) to JSON or pass them through ports.
    • Have to add a parameter to each view function. If you're using a port to set the current language it seems hard to get away from this (as discussed in this previous thread); e.g. it seems elm-i18n would need it as well.

#2: Modules & Multiple Versions

Put each localization in its own Elm module (e.g. Localizations.EN), compile a version of the app for each locale, using some kind of compile-time template for each view module which has i18n text: import Localizations.{{ currentLocale }} as I18n, and serve the version corresponding to the user's locale.

Precedent: Google Web Toolkit compiled a version for each (browser, locale) combo. Never having used GWT, I can't say how well that worked out, but it seems reasonable...

  • Pros
    • Template functions are just normal Elm functions; no hard-to-typecheck templating library necessary
    • Defaulting to another language is easy — if you haven't translated a message, just import another localization and use its version of that message.
    • Don't have to add an I18n argument to view functions, since it's an import. Just refer to I18n.myMessage.
  • Cons
    • Harder to share w/ Rails — you would have to generate one's format from the other's...
    • Complicates your Elm build; have to munge your imports and build one for each locale
    • Complicates your serving & caching; have to serve & cache multiple versions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment