Ex_cldr - Common Locale Data Repository (CLDR) functions for Elixir

A rainy Saturday is an opportunity to publish the first release of ex_cldr_routes that supports localised route generation and localised path helpers. Localised routes allow a user to enter URLs in their local language yet still route to the correct controller and action. Similarly, localised path helpers generate paths in the users locale. Here’s a simple example:

Route generators

# First, add the `Cldr.Routes` provider to a `Cldr` backend module configuration
# And ensure that a `Gettext` module is also configured (this is use for the
# translation process)
defmodule MyApp.Cldr do
  use Cldr,
    locales: ["en", "fr"],
    default_locale: "en",
    gettext: MyAppWeb.Gettext,
    providers: [Cldr.Routes]
end

# Then add `use MyApp.Cldr.Routes` to your router after
# `use Phoenix.Router`
defmodule MyApp.Router do
  use Phoenix.Router
  use MyApp.Cldr.Routes

  # The localize/1 macro wraps the normal route definitions
  # and it will generate localised versions of these routes at
  # compile time.
  localize do
    get "/pages/:page", PageController, :show, assigns: %{key: :value}
    resources "/users", UserController do
      resources "/faces", UserController
    end
  end

Now we can see what routes have been generated:

% mix phx.routes MyApp.Router
Compiling 3 files (.ex)
Generating MyApp.Cldr for 3 locales named [:en, :fr, :und] with a default locale named :en
     page_path  GET     /pages/:page                          PageController :show
     page_path  GET     /pages_fr/:page                       PageController :show
     user_path  GET     /users                                UserController :index
     user_path  GET     /users/:id/edit                       UserController :edit
     user_path  GET     /users/new                            UserController :new
     user_path  GET     /users/:id                            UserController :show
     user_path  POST    /users                                UserController :create
     user_path  PATCH   /users/:id                            UserController :update
                PUT     /users/:id                            UserController :update
     user_path  DELETE  /users/:id                            UserController :delete
user_user_path  GET     /users/:user_id/faces                 UserController :index
user_user_path  GET     /users/:user_id/faces/:id/edit        UserController :edit
user_user_path  GET     /users/:user_id/faces/new             UserController :new
user_user_path  GET     /users/:user_id/faces/:id             UserController :show
user_user_path  POST    /users/:user_id/faces                 UserController :create
user_user_path  PATCH   /users/:user_id/faces/:id             UserController :update
                PUT     /users/:user_id/faces/:id             UserController :update
user_user_path  DELETE  /users/:user_id/faces/:id             UserController :delete
     user_path  GET     /users_fr                             UserController :index
     user_path  GET     /users_fr/:id/edit                    UserController :edit
     user_path  GET     /users_fr/new                         UserController :new
     user_path  GET     /users_fr/:id                         UserController :show
     user_path  POST    /users_fr                             UserController :create
     user_path  PATCH   /users_fr/:id                         UserController :update
                PUT     /users_fr/:id                         UserController :update
     user_path  DELETE  /users_fr/:id                         UserController :delete
user_user_path  GET     /users_fr/:user_id/faces_fr           UserController :index
user_user_path  GET     /users_fr/:user_id/faces_fr/:id/edit  UserController :edit
user_user_path  GET     /users_fr/:user_id/faces_fr/new       UserController :new
user_user_path  GET     /users_fr/:user_id/faces_fr/:id       UserController :show
user_user_path  POST    /users_fr/:user_id/faces_fr           UserController :create
user_user_path  PATCH   /users_fr/:user_id/faces_fr/:id       UserController :update
                PUT     /users_fr/:user_id/faces_fr/:id       UserController :update
user_user_path  DELETE  /users_fr/:user_id/faces_fr/:id       UserController :delete

Path helpers

Localised path helpers are generated in a module MyApp.Router.LocalizedHelpers. The helpers have exactly the same API as the standard Phoenix MyApp.Router.Helpers module that is also generated. The only difference is that if a route is localised, the path helper is also localised. For example:

iex> Gettext.put_locale MyAppWeb.Gettext, "en"
iex> MyApp.Router.LocalizedHelpers.page_path(%Plug.Conn{}, :show, 1)
"/pages/1"

iex> Gettext.put_locale MyAppWeb.Gettext, "fr"
iex> MyApp.Router.LocalizedHelpers.page_path(%Plug.Conn{}, :show, 1)
"/pages_fr/1"

This is quite a simple library that builds upon existing Phoenix macros. There are a couple of capabilities to be completed before a 1.0 release:

  • Generate a list of locales and paths suitable to be used in generating a series of link rel="alternate" href="http://example.com/localized/path" hreflang="locale" />. The function will generate a list of locales and URLs, not the link tag itself.
  • Consider %-encoding. Given the specific focus on localisation and recognising that the URL specification allows US-ASCII only characters in the URL, there is a higher likelihood of non-ASCII translations of path segments leading to the generation of invalid URLs.
2 Likes