Skip to content

Instantly share code, notes, and snippets.

@ulfurinn
Created March 22, 2023 18:10
Show Gist options
  • Save ulfurinn/08a9a188be47f4c2fc7b5e83a6c6d159 to your computer and use it in GitHub Desktop.
Save ulfurinn/08a9a188be47f4c2fc7b5e83a6c6d159 to your computer and use it in GitHub Desktop.
Locale extraction from URL in Phoenix

This is a quick stab at a problem that the set_locale package is supposed to solve but seems to be broken and unsupported, or at least that's what its issue list indicates.

We want to extract the locale from the path prefix; i.e. "/en/users" and "/sv/users" should correctly set the current Gettext locale for the request. "/users" should keep the default locale.

We're not considering cookies or Accept-Language in this scope. We also don't do redirecting; the unlocalized path will hit the action as usual.

Using this solution requires two changes to the router:

  1. Paths that should be localized need to be wrapped in a localized/2 macro. It simply repeats its content with and without a scope /:locale so that both versions are visible to the route inspector and play nicely with verified routes.
  2. The browser pipeline needs to include a plug that extracts the param and sets the current Gettext locale. It also creates an assign for convenience.
defmodule AppWeb do
# ...
def router do
quote do
# ...
import Localized, only: [localized: 2]
end
end
# ...
end
Current locale is <%= @locale %>. <%= gettext("Hi!") %>
<p />
<%= for locale <- Gettext.known_locales(AppWeb.Gettext) do %>
<.link href={~p"/#{locale}"}><%= locale %></.link>
<br />
<% end %>
defmodule Localized do
import Plug.Conn
def init(opts) do
case Keyword.fetch(opts, :param) do
{:ok, key} when is_binary(key) ->
opts
{:ok, key} when is_atom(key) ->
opts |> Keyword.replace(:param, Atom.to_string(key))
:error ->
opts |> Keyword.put(:param, Atom.to_string(Keyword.fetch!(opts, :assign)))
end
end
def call(conn, opts) do
with locale <- conn.params[opts[:param]],
true <- locale in Gettext.known_locales(opts[:backend]) do
Gettext.put_locale(opts[:backend], locale)
else
_ -> nil
end
conn |> assign(opts[:assign], Gettext.get_locale(opts[:backend]))
end
defmacro localized(opts, do: body) do
key = Keyword.fetch!(opts, :param)
prefix = "/:#{key}"
quote do
unquote(body)
scope unquote(prefix) do
unquote(body)
end
end
end
end
defmodule AppWeb.Router do
# ...
pipeline :browser do
# ...
plug Localized, backend: AppWeb.Gettext, assign: :locale, param: "locale"
end
scope "/", AppWeb do
pipe_through :browser
localized(param: "locale") do
get "/", PageController, :home
end
end
# ...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment