Localize Phoenix

View Source

This tutorial explains how to use Routex to localize your Phoenix application including multilingual, SEO-friendly URLs. In addition to showing how to configure Routex, you’ll learn:

  • Why localized routes matter: Enhance user experience, improve SEO, and support regional content.
  • How Routex works: How the battery included framework supports locatization.
  • Step-by-step setup: Configure your backend, set up translations, and add a language switcher.

What You’ll Build

By the end of this tutorial, you will have:

  • A set of locale-specific URLs for your product pages.
  • Translated route segments based on your Gettext files.
  • A language switcher component that keeps users within their localized scope.
  • Guidance on further customization and troubleshooting.

For example, your routes may look like:

                        /products/:id/edit                    @loc.locale = "en_US"
   /products/:id/edit   /eu/nederland/producten/:id/bewerken  @loc.locale = "nl_NL"
                        /eu/france/produit/:id/editar         @loc.locale = "fr_FR"
                        /gb/products/:id/edit                 @loc.locale = "en_GB"

Prerequisites

  • A working Phoenix project with Routex installed. (See Routex Usage Guide for installation instructions.)
  • Phoenix version ≥ 1.6 and Elixir version ≥ 1.11.

Terminology

Slightly simplified for your convenience.

  • locale: Formatted as language-region. "en-GB" is shorthand for language "en" and region "GB".
  • IANA: The Internet Assigned Numbers Authority provides an official list of region- and language-identifier including display names
  • attribute: custom value assigned to a route
  • assign: value accesible using @key in templates
  • Accept-Language: The HTTP Accept-Language request header indicates the natural language and locale that the software client prefers.

Step 1: Configuring the Routex Backend

Next, create (or update) your Routex backend module. This configuration determines which extensions to use, how to generate alternative routes, and how to integrate translations via Gettext.

An explanation of the configuration is at the bottom of this guide.

defmodule ExampleWeb.RoutexBackend do
  @moduledoc """
  Configures Routex to enable localized and translated routes.
  """

  use Routex.Backend,
    extensions: [
      # == Base extensions ==
      Routex.Extension.AttrGetters,         # Base attribute handling
      Routex.Extension.LiveViewHooks,       # Inlines LiveView lifecycle callbacks of other extensions
      Routex.Extension.Plugs,               # Inlines plug callbacks of other extensions
      # == Used for Localization ==
      Routex.Extension.Localize.Phoenix.Routes,     # Localize routes at compile time
      Routex.Extension.Localize.Phoenix.Runtime,    # Detects locale from various sources at runtime
      Routex.Extension.Translations,                # Enables route segment translations
      Routex.Extension.Alternatives,                # Generates locale alternatives set by Localize.Phoenix
      Routex.Extension.AlternativeGetters,          # Creates a helper function to get the alternatives for a route
      Routex.Extension.VerifiedRoutes,              # Make Phoenix VerifiedRoutes branch (alternatives) aware
      Routex.Extension.RuntimeDispatcher,           # Dispatches during runtime (e.g `Gettext.put_locale/{1,2}`)
    ],

    # Integration with Gettext for route segment translation.
    # Defaults to the standard Gettext module of a Phoenix project.
    # translations_backend: ExampleWeb.Gettext,

    # Drop-in replacements: Override Phoenix VerifiedRoutes macros with Routex variants.
    verified_sigil_routex: "~p",
    verified_sigil_phoenix: "~o",
    verified_url_routex: :url,
    verified_path_routex: :path,

    # Locales to generate routes for: English (Global), Dutch, French, English (Great Brittain) and English (European)
    # All optional. `locales` and `default_locale` will be detected using Cldr, Gettext or Fluent when available.
    # locales: [{"en-001", %{region_display_name: "Worldwide"}}, "nl-NL", "fr-FR", "en-GB", "en-150"],
    # default_locale: "en-100",

    # Custom language detection source priority
    # language_sources: [:query, :session, :cookie, :attrs, :accept_language],

    # Runtime dispatch targets to set Gettext locale from route attribute :language.
    # Shown below is the default.
    # dispatch_targets: [{Gettext, :put_locale, [[:attrs, :language]]}]
end

Step 2: Translate Route Segments

Generate the translation files for your routes:

mix gettext.extract
mix gettext.merge priv/gettext --locale nl
mix gettext.merge priv/gettext --locale fr

This creates the following structure:

priv/
 gettext/
   nl/
     LC_MESSAGES/
       default.po  # phoenix translations
       routes.po   # routex translations
    fr/
     LC_MESSAGES/
       default.po  # phoenix translations
       routes.po   # routex translations

Translate your route segments using any .po file editor (Poedit, OmegaT, etc.).

After you have translated segments, run mix compile --force for trigger a recompilation with translated routes.


Step 3: Adding a Language Switcher Component

To improve user experience, add a component that lets users switch locales seamlessly. Below is an example using a LiveView component with explicit styling and accessibility features:

<.link
  :for={alternative <- Routes.alternatives(@url)}
  class="button"
  rel="alternate"
  hreflang={alternative.attrs.language}
  navigate={alternative.slug}>
  <.button class={if(alternative.match?, do: "bg-[#FD4F00]", else: "")}>
    <%= alternative.attrs.language_display_name %>
  </.button>
</.link>

Component Highlights:

  • Looping over Alternatives: Fetches all localized route variants for the current URL.
  • User Friendly Language Names: Uses the :language_display_name as set by Localize.
  • Dynamic Styling: Highlights the current language (using a conditional CSS class).
  • Accessible Markup: Uses proper rel and hreflang attributes.

Next Steps: Customize further using Tailwind CSS or your preferred framework and ensure it meets accessibility standards.


Troubleshooting & Testing

Common Pitfalls:

  • Missing Translation: Ensure your PO files are updated and merged after any change.
  • Route Mismatch: Run mix phx.routes to verify that all localized routes are generated.
  • Cookie/Session Issues: Double-check your browser settings if locale detection does not work as expected.

Additional Features & Customization


Conclusion

This tutorial has guided you through localizing your Phoenix routes using Routex by:

  • Explaining the benefits of localized routes.
  • Providing a detailed configuration example with clear commentary.
  • Demonstrating how to extract translations and build a language switcher.
  • Offering troubleshooting and testing recommendations.

By following these steps, you now have a powerful and flexible routing system that can adapt to any locale requirement without modifying your templates. For further enhancements, check the official Routex documentation and join the discussion on the Elixir Forum.

Happy coding and enjoy creating a multilingual Phoenix application!


The Configuration Explained

AttrGetters, LiveViewHooks, Plugs:

  • Extensions supporting other extensions.

Alternatives Structure:

  • Creates a hierarchical URL structure
  • Supports regional variations (e.g., European vs British English)
  • Associates locales with URL paths
  • Supports [language|region]_display_name overrides

Localize with custom language sources:

  • Expands route attribute :locale into route attributes :locale, :region, :language, :region_display_name, :language_display_name
  • Handles locale detection using a variery of sources including Accept-Language
  • Sets attributes :locale, :region and :language at runtime
  • Comes with an IANA-based locale registry to validate locale-, region- and language and to convert these to display names
  • Custom detection source priority to favor the routes' language over the Accept-Language browser language

Translation Setup:

  • Enables path segment translation
  • Uses the default translation lib use by Phoenix: Gettext.
  • Consistent segment localization

Verified Routess:

  • Preserves existing Phoenix path sigils (e.g. ~p"/my/path")
  • Adds locale awareness to routes
  • Maintains backward compatibility

AlternativeGetters:

  • Fetch alternative locale routes using alternatives(@url)
  • Use to generate buttons to switch language

RuntimeDispatcher:

  • Configured to call Gettext.put_locale
  • Uses the runtime detected attribute :language which is set by Localize.