I have an Elixir / Phoenix app which reacts differently depending on the domain (aka tenant).
A tenant has a specific locale such as "fr_FR", "en_US" and so on.
I want to translate the URIs of the router depending the current locale:
# EN
get "/classifieds/new", ClassifiedController, :new
# FR
get "/annonces/ajout", ClassifiedController, :new
So far I thought it would be possible to do something like that (pseudo code):
if locale() == :fr do
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/annonces/ajout", ClassifiedController, :new
end
else
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/classifieds/new", ClassifiedController, :new
end
end
It doesn't work since the router is compiled during the boot of the server, so you have no context of the current connexion (locale, domain, host and so on).
So far my solution (which works) was to create two scopes with two aliases:
scope "/", Awesome.App, as: :fr_app do
pipe_through :browser # Use the default browser stack
get "/annonces/ajout", ClassifiedController, :new
end
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/classifieds/new", ClassifiedController, :new
end
and a helper associated:
localized_path(conn, path, action)
which takes a path (:app_classified_new_path) and prefix with fr (:fr_app_classified_new_path) if the current locale is "fr" (for example). If the path doesn't exist for the current locale, I fallback to the default locale "en").
It's working great but I see some pain points:
Every use of "something_foo_path()" helpers must be replaced by this new "localized_path()"
The app will accept each localized segments (fr, en, it, whatever) and it's not really what I want (I want to have only the fr segments to work on the tenant "fr"
I should be able to get the current locale/conn info directly in the router (feature/bugfix?) and avoid a hack like that.
Take a look at this article Practical i18n with Phoenix and Elixir. I believe it should give you exactly what you need.
Alternatively, you could play around with some macros to create blocks of translated routes. The code below does not handle all your requirements since you still have to translate the paths internally. However, it may give you some ideas.
defmodule LocalRoutes.Web.Router do
use LocalRoutes.Web, :router
@locales ~w(en fr)
import LocalRoutes.Web.Gettext
use LocalRoutes.LocalizedRouter
def locale(conn, locale) do
Plug.Conn.assign conn, :locale, locale
end
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", LocalRoutes.Web do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
for locale <- @locales do
Gettext.put_locale(LocalRoutes.Web.Gettext, locale)
pipeline String.to_atom(locale) do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :locale, locale
end
scope "/", LocalRoutes.Web do
pipe_through String.to_atom(locale)
get "/#{~g"classifieds"}", ClassifiedController, :index
get "/#{~g"classifieds"}/#{~g"new"}", ClassifiedController, :new
get "/#{~g"classifieds"}/:id", ClassifiedController, :show
get "/#{~g"classifieds"}/:id/#{~g"edit"}", ClassifiedController, :edit
post "/#{~g"classifieds"}", ClassifiedController, :create
patch "/#{~g"classifieds"}/:id", ClassifiedController, :update
put "/#{~g"classifieds"}/:id", ClassifiedController, :update
delete "/#{~g"classifieds"}/:id", ClassifiedController, :delete
end
end
end
defmodule LocalRoutes.LocalizedRouter do
defmacro __using__(opts) do
quote do
import unquote(__MODULE__)
end
end
defmacro sigil_g(string, _) do
quote do
dgettext "routes", unquote(string)
end
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With