Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I18n segments router Phoenix

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:

  1. Every use of "something_foo_path()" helpers must be replaced by this new "localized_path()"

  2. 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"

  3. I should be able to get the current locale/conn info directly in the router (feature/bugfix?) and avoid a hack like that.

like image 227
Jeremie Ges Avatar asked May 17 '17 08:05

Jeremie Ges


1 Answers

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
like image 152
Steve Pallen Avatar answered Oct 31 '22 16:10

Steve Pallen