You want to use I81n in Phoenix LiveView? So did I. But one can't just use gettext/1
in Phoenix LiveView out of the box. It took me a couple of hours to find out how to do it. Here is the recipe. You can copy and paste all of it. Just make sure to search and replace for example
and Example
.
Setup
mix phx.new example --live
cd example
My example application features English (en) and German (de) and defaults to English. So we have to add this at the end of config/config.ex
config :example, ExampleWeb.Gettext,
default_locale: "en",
locales: ~w(en de)
Ask the browser
There are a couple of different ways of deciding which language to use. In my opinion the "accept-language"
header from the browser is a good way to go. If you want to go a different direction this plug is a good starting point. To use "accept-language"
and set the used language we create a new plug:
lib/example_web/plug/local_plug.ex
defmodule ExampleWeb.Plugs.Locale do
import Plug.Conn
def init(_opts), do: nil
def call(conn, _opts) do
accepted_languages = extract_accept_language(conn)
known_locales = Gettext.known_locales(ExampleWeb.Gettext)
accepted_languages =
known_locales --
(known_locales -- accepted_languages)
case accepted_languages do
[locale | _] ->
Gettext.put_locale(ExampleWeb.Gettext, locale)
conn
|> put_session(:locale, locale)
_ ->
conn
end
end
# Copied from
# https://raw.githubusercontent.com/smeevil/set_locale/fd35624e25d79d61e70742e42ade955e5ff857b8/lib/headers.ex
def extract_accept_language(conn) do
case Plug.Conn.get_req_header(conn, "accept-language") do
[value | _] ->
value
|> String.split(",")
|> Enum.map(&parse_language_option/1)
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
|> ensure_language_fallbacks()
_ ->
[]
end
end
defp parse_language_option(string) do
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
quality =
case Float.parse(captures["quality"] || "1.0") do
{val, _} -> val
_ -> 1.0
end
%{tag: captures["tag"], quality: quality}
end
defp ensure_language_fallbacks(tags) do
Enum.flat_map(tags, fn tag ->
case String.split(tag, "-") do
[language, _country_variant] ->
if Enum.member?(tags, language), do: [tag], else: [tag, language]
[_language] ->
[tag]
end
end)
end
end
We have to include this plug in the pipeline:
lib/example_web/router.ex
defmodule ExampleWeb.Router do
use ExampleWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {ExampleWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug ExampleWeb.Plugs.Locale # <-----
end
[...]
The LiveView Hello World with I18n
A nice Hello World! example:
lib/example_web/live/page_live.ex
defmodule ExampleWeb.PageLive do
use ExampleWeb, :live_view
import Gettext, only: [with_locale: 2]
@impl true
def mount(_params, session, socket) do
locale = case session do
%{"locale" => locale} -> locale
_ -> "en"
end
socket =
socket
|> assign(locale: locale)
{:ok, socket}
end
def render(assigns) do
~L"""
<%= with_locale(@locale, fn -> %>
<%= gettext "Hello World!" %></h2>
<% end) %>
"""
end
end
The Translations
We need to generate the config files for the gettext
translations:
mix gettext.extract --merge
mix gettext.merge priv/gettext --locale de
Here are the files which need to be used for the translations:
priv/gettext
├── de
│ └── LC_MESSAGES
│ ├── default.po
│ └── errors.po
├── default.pot
└── errors.pot
And here's the content of priv/gettext/de/LC_MESSAGES/default.po
(the German translation):
## "msgid"s in this file come from POT (.pot) files.
##
## Do not add, change, or remove "msgid"s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use "mix gettext.extract --merge" or "mix gettext.merge"
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: de\n"
"Plural-Forms: nplurals=2\n"
#, elixir-format
#: lib/example_web/live/page_live.ex:17
msgid "Hello World!"
msgstr "Hallo Welt!"
Here's some more docu about using Gettext:
- https://phrase.com/blog/posts/i18n-for-phoenix-applications-with-gettext/
- http://blog.plataformatec.com.br/2016/03/using-gettext-to-internationalize-a-phoenix-application/
After all this you can fire up your application:
mix phx.server
That's all!
My Twitter account: https://twitter.com/wintermeyer
PS: In case you want to tackle LiveView components have a look at https://www.paulfioravanti.com/blog/internationalisation-phoenix-live-components/https://www.paulfioravanti.com/blog/internationalisation-phoenix-live-components/
Top comments (0)