View Source PlugLocale.Browser (plug_locale v0.4.1)
Puts locale into assigns
storage for Web browser environment.
The most common way of specifying the desired locale is via the URL. In general, there're three methods to do that:
- via domain name -
https://<locale>.example.com
, such as:https://en.example.com/welcome
https://zh.example.com/welcome
- via path -
https://example.com/<locale>
, such as:https://example.com/en/welcome
https://example.com/zh/welcome
- via querystring -
https://example.com?locale=<locale>
, such as:https://example.com/welcome?locale=en
https://example.com/welcome?locale=zh
Personally, I think method 2 is better, compared to the other two methods:
- method 1 is tedious for deployment.
- URLs generated by method 3 look very ugly and unprofessional.
Because of that, this plug will stick on method 2.
Usage
First, we need to integrate this plug with other libraries, or this plug
is useless. All you need is to construct a plug pipeline through
Plug.Builder
. For example:
defmodule DemoWeb.PlugBrowserLocalization do
use Plug.Builder
plug PlugLocale.Browser,
default_locale: "en",
locales: ["en", "zh"],
route_identifier: :locale,
assign_key: :locale
plug :put_locale
def put_locale(conn, _opts) do
if locale = conn.assigns[:locale] do
# integrate with gettext
Gettext.put_locale(locale)
end
conn
end
end
Then, use it in router (following one is a Phoenix router, but Plug.Router
is supported, too):
defmodule DemoWeb.Router do
use DemoWeb, :router
pipeline :browser do
plug :accepts, ["html"]
# ...
plug DemoWeb.PlugBrowserLocalization
# ...
end
scope "/", DemoWeb do
pipe_through :browser
get "/", PageController, :index
# ...
end
# Why using :locale?
# Because it is specified by `:route_identifier` option.
scope "/:locale", DemoWeb do
pipe_through :browser
get "/", PageController, :index
# ...
end
end
Options
:default_locale
- the default locale.:locales
- all the supported locales. Default to[]
.:detect_locale_from
- the sources and the order of sources for detecting locale. Available sources are:query
,:cookie
,:referrer
,:accept_language
. Default to[:cookie, :referrer, :accept_language]
.:cast_locale_by
- the function for casting extracted or detected locales. Default tonil
.:route_identifier
- the part for identifying locale in route. Default to:locale
.:assign_key
- the key for putting value intoassigns
storage. Default to:locale
.:query_key
- the key for getting locale from querystring. Default to"locale"
.:cookie_key
- the key for getting locale from cookie. Default to"locale"
.
about :cast_locale_by
option
By default, the value is nil
, which means doing nothing. But, in practice,
you will need to use something meaningful.
A possible implementation:
defmodule DemoWeb.I18n do
def cast_locale(locale) do
case locale do
# explicit matching on supported locales
locale when locale in ["en", "zh"] ->
locale
# fuzzy matching on en locale
"en-" <> _ ->
"en"
# fuzzy matching on zh locale
"zh-" <> _ ->
"zh"
# fallback for unsupported locales
_ ->
"en"
end
end
end
Then, use above implementation for plug:
plug `PlugLocale.Browser`,
default_locale: "en",
locales: ["en", "zh"],
cast_locale_by: &DemoWeb.I18n.cast_locale/1,
# ...
Helper functions
PlugLocale.Browser
also provides some helper functions, which will be useful
when implementing UI components:
Check out their docs for more details.
an example - a simple locale switcher using build_locale_path/2
<ul>
<li>
<a
href={PlugLocale.Browser.build_locale_path(@conn, "en")}
aria-label="switch to locale - en"
>
English
</a>
</li>
<li>
<a
href={PlugLocale.Browser.build_locale_path(@conn, "zh")}
aria-label="switch to locale - zh"
>
中文
</a>
</li>
</ul>
How it works?
This plug will try to:
- extract locale from URL, and check if the locale is supported:
- If it succeeds, put locale into
assigns
storage. - If it fails, jump to step 2.
- If it succeeds, put locale into
- detect locale from Web browser environment, then redirect to the path corresponding to detected locale.
Extract locale from URL
For example, the locale extracted from https://example.com/en/welcome
is en
.
Detect locale from Web browser environment
By default, local is detected from multiple sources:
- query (whose key is specified by
:query_key
option) - cookie (whose key is specified by
:cookie_key
option) - HTTP request header -
referer
- HTTP request header -
accept-language
If all detections fail, fallback to default locale.
Examples
When:
:default_locale
option is set to"en"
:locales
option is set to["en", "zh"]
For users in an English-speaking environment:
https://example.com/en
will be responded directly.https://example.com/
will be redirected tohttps://example.com/en
.https://example.com/path
will be redirected tohttps://example.com/en/path
.https://example.com/unknown
will be redirected tohttps://example.com/en
.- ...
For users in an Chinese-speaking environment:
https://example.com/zh
will be responded directly.https://example.com/
will be redirected tohttps://example.com/zh
.https://example.com/path
will be redirected tohttps://example.com/zh/path
.https://example.com/unknown
will be redirected tohttps://example.com/zh
.- ...
Summary
Functions
Builds a localized path for current path.
Puts a response cookie for locale in the connection.
Functions
@spec build_locale_path(Plug.Conn.t(), String.t()) :: String.t()
Builds a localized path for current path.
Note: the locale passed to this function won't be casted by the function which is specified by
:cast_locale_by
option.
Examples
# the request path of conn is /posts/7
iex> build_locale_path(conn, "en")
"/en/posts/7"
# the request path of conn is /en/posts/7
iex> build_locale_path(conn, "zh")
"/zh/posts/7"
Puts a response cookie for locale in the connection.
This is a simple wrapper around Plug.Conn.put_resp_cookie/4
. See its docs
for more details.
Examples
iex> put_locale_resp_cookie(conn, "en")
iex> put_locale_resp_cookie(conn, "zh", max_age: 365 * 24 * 60 * 60)
Use cases
Use this function to persistent current locale into cookie, then subsequent requests can directly read the locale from the cookie.
defmodule DemoWeb.PlugBrowserLocalization do
use Plug.Builder
plug PlugLocale.Browser,
default_locale: "en",
locales: ["en", "zh"],
route_identifier: :locale,
assign_key: :locale
plug :put_locale
def put_locale(conn, _opts) do
if locale = conn.assigns[:locale] do
# integrate with gettext
Gettext.put_locale(locale)
# persistent current locale into cookie
PlugLocale.Browser.put_locale_resp_cookie(
conn,
locale,
max_age: 365 * 24 * 60 * 60
)
else
conn
end
end
end