This guide covers detecting the user's locale from HTTP requests, persisting it in the session, and restoring it in LiveView using the plugs provided by localize_web.
Prerequisites
Add localize_web and a Gettext backend to your project:
# mix.exs
def deps do
[
{:localize_web, "~> 0.1.0"},
{:gettext, "~> 1.0"}
]
endDefine a Gettext backend if you don't already have one:
# lib/my_app/gettext.ex
defmodule MyApp.Gettext do
use Gettext.Backend, otp_app: :my_app
endAs an alternative to standard Gettext interpolation, you can configure the backend to use ICU MessageFormat 2 for richer message formatting including plural rules, select expressions, and number/date formatting:
defmodule MyApp.Gettext do
use Gettext.Backend,
otp_app: :my_app,
interpolation: Localize.Gettext.Interpolation
endStandalone Accept-Language Parsing
The simplest way to detect a user's preferred locale is from the Accept-Language HTTP header sent by the browser. If that is all you need, use Localize.Plug.AcceptLanguage:
# lib/my_app_web/router.ex
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug Localize.Plug.AcceptLanguage
endThe detected locale is stored in conn.private[:localize_locale] and can be retrieved with:
iex> locale = Localize.Plug.AcceptLanguage.get_locale(conn)By default, a warning is logged when no configured locale matches the header. This can be changed or disabled:
plug Localize.Plug.AcceptLanguage, no_match_log_level: :debug
plug Localize.Plug.AcceptLanguage, no_match_log_level: nilFull Locale Discovery with PutLocale
For most applications, use Localize.Plug.PutLocale which checks multiple sources in priority order and sets the locale from the first match:
# lib/my_app_web/router.ex
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug Localize.Plug.PutLocale,
from: [:session, :accept_language, :query, :path],
param: "locale",
gettext: MyApp.Gettext
plug Localize.Plug.PutSession
endThe Localize process locale is always set via Localize.put_locale/1. When a :gettext backend (or list of backends) is provided, the locale is also set on each Gettext backend.
How Source Priority Works
The :from option controls the order of locale source lookup. In this example:
- The session is checked first (preserving the user's previous choice).
- The accept-language header is checked next.
- Then query parameters (e.g.
?locale=fr). - Finally, path parameters.
The first source that yields a valid locale wins. Remaining sources are not consulted.
The :param Option
The :param option specifies the parameter name to look for in query, path, body, and cookie sources. The default is "locale". For example, with param: "lang", the plug will look for ?lang=fr in query params or a lang path parameter.
Configuring Gettext
The :gettext option accepts a single Gettext backend module or a list of backends:
# Single backend
plug Localize.Plug.PutLocale, gettext: MyApp.Gettext
# Multiple backends
plug Localize.Plug.PutLocale, gettext: [MyApp.Gettext, MyOtherApp.Gettext]When omitted, only the Localize process locale is set and no Gettext locale is configured.
The Gettext backend can use either the standard Gettext interpolation or the Localize.Gettext.Interpolation module which supports ICU MessageFormat 2 (MF2). MF2 provides locale-aware plural rules, select expressions, and number/date formatting directly in your translation strings. See the Localize.Message documentation for the full MF2 syntax reference.
The Default Locale
When no source provides a locale, the :default option determines the fallback:
# Use a specific locale (default is Localize.default_locale/0)
plug Localize.Plug.PutLocale, default: "en"
# Disable the fallback entirely
plug Localize.Plug.PutLocale, default: :none
# Use a custom function
plug Localize.Plug.PutLocale, default: {MyApp.Locale, :resolve_default}All Locale Sources
Beyond the common sources shown above, Localize.Plug.PutLocale supports these additional sources in the :from list:
:body— looks for the locale parameter inconn.body_params.:cookie— looks for the locale parameter in the request cookies.:host— extracts the top-level domain from the hostname and resolves it to a locale. For example,example.co.ukresolves to a UK English locale. Generic TLDs like.com,.org, and.netare ignored.:route— reads the locale assigned to a route by thelocalize/1macro. See the Phoenix Localized Routing guide for details.{Module, function}— callsModule.function(conn, options)and expects{:ok, locale}on success.{Module, function, args}— callsModule.function(conn, options, ...args)and expects{:ok, locale}on success.
Custom Locale Resolution Example
defmodule MyApp.LocaleResolver do
def from_user(conn, _options) do
case conn.assigns[:current_user] do
%{preferred_locale: locale} when is_binary(locale) ->
Localize.validate_locale(locale)
_ ->
{:error, :no_user}
end
end
end
# In the router
plug Localize.Plug.PutLocale,
from: [{MyApp.LocaleResolver, :from_user}, :session, :accept_language],
gettext: MyApp.GettextPersisting Locale in the Session
Localize.Plug.PutSession saves the discovered locale to the session so it persists across requests. It should always be placed after Localize.Plug.PutLocale in the plug pipeline:
plug Localize.Plug.PutLocale, ...
plug Localize.Plug.PutSession, as: :stringThe :as option controls the storage format:
:string(default) — converts the locale to a string before storing. This minimizes session size at the expense of CPU time to serialize and parse on subsequent requests.:language_tag— stores the full%Localize.LanguageTag{}struct. This minimizes CPU time at the expense of larger session storage.
The session key is fixed to "localize_locale" so that downstream consumers (such as LiveView on_mount callbacks) can retrieve the locale without configuration.
LiveView Integration
In LiveView, the HTTP plug pipeline runs only on the initial page load. For subsequent live navigation, the locale must be restored from the session in the on_mount callback:
defmodule MyAppWeb.LocaleLive do
def on_mount(:default, _params, session, socket) do
{:ok, _locale} = Localize.Plug.put_locale_from_session(
session,
gettext: MyApp.Gettext
)
{:cont, socket}
end
endThen attach it in your router:
live_session :default, on_mount: [MyAppWeb.LocaleLive] do
scope "/", MyAppWeb do
live "/dashboard", DashboardLive
end
endThe put_locale_from_session/2 function reads the locale from the session (stored by Localize.Plug.PutSession) and sets it for both Localize and Gettext in the LiveView process.
Accessing the Locale in Controllers and Views
After the plug pipeline runs, the locale is available in several ways:
# From the conn (set by PutLocale)
locale = Localize.Plug.PutLocale.get_locale(conn)
# From the Localize process dictionary
locale = Localize.get_locale()Putting It All Together
A typical Phoenix application plug pipeline for full localization support:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Localize.Plug.PutLocale,
from: [:route, :session, :accept_language],
gettext: MyApp.Gettext
plug Localize.Plug.PutSession
endNote that :route is listed first in :from. This means that when a user visits a localized route (e.g. /fr/utilisateurs), the locale embedded in that route takes priority. The session serves as a fallback for non-localized routes, and the accept-language header provides a sensible default for first-time visitors.