Prepend a locale or not to all link_to and redirect

Hello!
I’m trying to improve my localized application. I’ve got few questions about routing in this context.

Two locales are in place (en and fr). You can go to:

localhost/path => en locale
localhost/fr/path => fr locale
localhost/en/path would be valid but not “normally” used.

I plugged my locale switcher before the router:

defmodule MyApp.Plugs.SetLocale do

  @locales ["fr", "en"]

  def init(default) do
    default
  end

  def call(conn, _opts) do
    case conn.path_info do
      [locale | rest] when locale in @locales ->
        Gettext.put_locale(MyApp.Gettext, locale)
        %{conn | path_info: rest}
        |> Plug.Conn.assign(:locale, locale)

      _  ->
        Gettext.put_locale(MyApp.Gettext, "en")
        Plug.Conn.assign(conn, :locale, "en")
    end
  end
end

If I want to maintain the locale in the url I have to prepend local everywhere so:

<%= link gettext("My Page"), to: Routes.page_path(@conn, :my_page) %>

Became:

<%= link gettext("My Page"), to: @locale <> Routes.page_path(@conn, :my_page) %>

The same changes are made in the forms.
I end up making a helper so I can do:

<%= link gettext("My Page"), to: localized_route(@locale, Routes.page_path(@conn, :my_page)) %>

and

<%= form_for @changeset, localized_route(@locale,@action), fn f -> %>

In the controllers, I have to add the locale to the redirect too, using the same helper:

defmodule MyApp.ResourceController do
  use MyApp, :controller

  alias MyApp.Resources
  alias MyApp.Resources.Resource
  import MyApp.Views.Helpers.LocaleHelpers

  ....

  def update(conn, %{"id" => id, "resource" => resource_params}) do
    resource = Resources.get_resource!(id)
    
    case Resources.update_resource(resource, resource_params) do
      {:ok, resource} ->
        conn
        |> put_flash(:info, "resource updated successfully.")
        |> redirect(to: localized_route(conn.assigns.locale, Routes.resource_path(conn, :show, conn.params["account_uuid"], resource )))
        
        # Or with the proper router.ex
        # |> redirect(to: Routes.resource_path(conn, :show, conn.assigns.locale, conn.params["account_uuid"], resource.id ))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", resource: resource, changeset: changeset)
    end
  end
end  

I can write my redirection differently if I scope in the router. But to allow the “optional” locale I have to duplicate all routes which is not ideal in my opinion.

Here is an example of the router:

scope "/:account_uuid", MyAppWeb do
  pipe_through [...]
  ...
  resources "/resource", ResourceController
  ...
end

or

Enum.each ["/:locale", ""], fn locale ->
  scope locale <> "/:account_uuid", MyAppWeb do
    pipe_through [...]
    ...
    resources "/resource", ResourceController
    ...
  end
end

Questions:

1- Am I on the right track?
2- Is there a way to do an optional scope in the router like in rails (scope /(:locale)) or adding a default option to the scope?
3- Is there a better way to add the locale to all the links and redirects? I want avoiding the error of missing it when adding new elements to the app and having to rewrite everything each time.

You can save locale in cookie when the user switches languages, and then use plug to set locale automatically. There is a simple plug to do so https://github.com/bright-u/locale_plug.

Hello @goofansu,

I’ve seen this lib. Saving the locale somewhere is not actually my problem. I’m using assign for the moment but I’m planning to store it somewhere else.

I’m able to retrieve the user locale without difficulties. My main concern is about all the generated routes and redirects that are not taking this locale by default.

But maybe I’m missing something. How would you rewrite all the links with the locale with your approach?
Having Gettext in the appropriate language is one part of the problem. The URL is important too to avoid SEO problems for instance.

Did you check Alternate?

  • you can localize url paths (/users & /fr/utilisateurs)
  • use the same path helpers and it will generate the path/url based on the set locale
  • you can have / for English and /fr for French
2 Likes

Hello @sfusato,

Thanks for the lib it gives me some ideas.
When you use it you ended up rewriting your routes, links and redirects so basically I’m at the same point.
At least that comforts me for the first point as I’m doing a similar approach.