How to handle localisation in live view?

shipped first production liveview last week - I ended up wrapping everything in a liveview that holds state with locale etc - and then render pages (liveview components) depending on the action - so it’s kinda a router as well…

to make matters interesting it’s cookieless - so no passing the locale in the session… had to do:

in my custom plug:
%{req_headers: req_headers} = conn

browser_accept_lang = 
  with {_key, value} <- Enum.find(req_headers, fn {key, _val} -> key == "accept-language" end),
      {:ok, cldr} <- Cldr.AcceptLanguage.best_match(value, MyApp.Cldr)
  do
    cldr.language  
  else
    _err -> "en"
  end

conn
|> assign(:browser_accept_lang, browser_accept_lang)
|> assign(:conn_lang, Cldr.Plug.SetLocale.get_cldr_locale(conn).language)

browser_accept_lang is the browser locale - conn_lang could be different if the user is on /:locale/some_page - the diff is needed to conditionally rewrite uris with /:locale

then in root template: (remember we have no session to pass things through easily)

    <meta name="waccept-lang" content="<%= assigns[:conn_lang] %>">
    <meta name="browser_accept_lang" content="<%= assigns[:browser_accept_lang] %>">

in the liveview: so that the initial render is in correct lang - and then socket connects with locale params, so we also have them on “the socket” - see below:

  @impl true
  def mount(_params, _session, socket) do
    # get js client connect params
    connect_params = get_connect_params(socket)
    {conn_assigns, _} = socket.private.assign_new

    js_lang =
      if connect_params do
        Map.get(connect_params, "conn_lang")
      else
        nil
      end

    lang = Map.get(conn_assigns, :conn_lang) || js_lang || "en"

    browser_accept_lang =
      if connect_params do
        Map.get(connect_params, "browser_accept_lang")
      else
        nil
      end

    browser_accept_lang = Map.get(conn_assigns, :browser_accept_lang) || browser_accept_lang || "en"

    latest_post = MyApp.Journal.list_posts(lang) |> List.first()

    {:ok,
     assign(socket, navigate_counter: 0, original_lang: lang, lang: lang, browser_accept_lang: browser_accept_lang, latest_post: latest_post)}
  end

in the js:

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let conn_lang = document.querySelector("meta[name='waccept-lang']").getAttribute("content")

let browser_accept_lang = document.querySelector("meta[name='browser_accept_lang']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken, conn_lang: conn_lang, browser_accept_lang: browser_accept_lang}})
2 Likes