{"id":28430969,"url":"https://github.com/elixir-cldr/cldr_routes","last_synced_at":"2025-08-04T06:15:18.065Z","repository":{"id":37886033,"uuid":"467364892","full_name":"elixir-cldr/cldr_routes","owner":"elixir-cldr","description":"Localised routes and path helpers for Phoenix applications","archived":false,"fork":false,"pushed_at":"2025-03-18T05:25:43.000Z","size":387,"stargazers_count":17,"open_issues_count":1,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-07-04T22:38:48.881Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elixir-cldr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-03-08T04:37:14.000Z","updated_at":"2025-03-29T09:05:16.000Z","dependencies_parsed_at":"2023-01-31T07:15:55.791Z","dependency_job_id":"45bed5ee-66c6-4e02-a3ee-4fea523485d9","html_url":"https://github.com/elixir-cldr/cldr_routes","commit_stats":{"total_commits":76,"total_committers":2,"mean_commits":38.0,"dds":0.03947368421052633,"last_synced_commit":"9d07a8f2a9cafa11d576a0a7fcfa85c6d2060376"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/elixir-cldr/cldr_routes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-cldr%2Fcldr_routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-cldr%2Fcldr_routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-cldr%2Fcldr_routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-cldr%2Fcldr_routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elixir-cldr","download_url":"https://codeload.github.com/elixir-cldr/cldr_routes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-cldr%2Fcldr_routes/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265281293,"owners_count":23739873,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-05T14:30:41.243Z","updated_at":"2025-07-14T11:09:22.132Z","avatar_url":"https://github.com/elixir-cldr.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cldr Routes\n![Build Status](http://sweatbox.noexpectations.com.au:8080/buildStatus/icon?job=cldr_routes)\n[![Hex.pm](https://img.shields.io/hexpm/v/ex_cldr_routes.svg)](https://hex.pm/packages/ex_cldr_routes)\n[![Hex.pm](https://img.shields.io/hexpm/dw/ex_cldr_routes.svg?)](https://hex.pm/packages/ex_cldr_routes)\n[![Hex.pm](https://img.shields.io/hexpm/dt/ex_cldr_routes.svg?)](https://hex.pm/packages/ex_cldr_routes)\n[![Hex.pm](https://img.shields.io/hexpm/l/ex_cldr_routes.svg)](https://hex.pm/packages/ex_cldr_routes)\n\nGenerate localized routes and a localized path helper module.\n\nThis module when `use`d , provides a `localize/1` macro that is designed to wrap the standard [Phoenix route](https://hexdocs.pm/phoenix/routing.html) macros such as `get/3`, `put/3` and `resources/3`. The routes are localised for each locale configured in a `Gettext` backend module that is attached to a `Cldr` backend module.\n\nTranslations for the parts of a given route path are performed at compile-time and are then combined into a localised route that is added to the standard Phoenix routing framework.\n\nAs a result, users can enter URLs using localised terms which can enhance user engagement and content relevance.\n\nSimilarly, localised path and URL helpers are generated that wrap the standard [Phoenix helpers](https://hexdocs.pm/phoenix/routing.html#path-helpers) to support generating localised paths and URLs.\n\nLastly, Localized Verified Routes, introduced in Phoenix 1.7, are supported and their use encouraged in preference to URL Helpers. Localized Verified Routes are specified with the `~q` sigil in the same manner as Phoenix Verified Routes `~p`.\n\n## Setting up\n\n### Installation\n\nThe first step is to configure and install by adding `ex_cldr_routes` to your list of dependencies in `mix.exs` then run `mix deps.get`.\n\n```elixir\ndef deps do\n  [\n    {:ex_cldr_routes, \"~\u003e 1.0\"}\n  ]\nend\n```\n\nThe docs can be found at https://hexdocs.pm/ex_cldr_routes.\n\n### Backend module configuration\n\nA `Cldr` backend module that configures an associated `gettext` backend is required. In addition, a `Gettext` backend must be configured and added to the `Cldr` configuration.\n\nPath parts (the parts between \"/\") are translated at compile time using `Gettext`. Therefore localization can only be applied to locales that are defined in a [gettext backend module](https://hexdocs.pm/gettext/Gettext.html#module-using-gettext) that is attached to a `Cldr` backend module.\n\nThe following steps should be followed to set up the configuration for localized routes and helpers:\n\n### Configure Gettext\n\nThe first step is to ensure there is a configured `Gettext` backend module:\n\n```elixir\ndefmodule MyApp.Gettext do\n  use Gettext.Backend, otp_app: :my_app\nend\n```\n\n### Configure Cldr\n\nThe next step is to configure a `Cldr` backend module, including configuring it with the `Gettext` module defined in the first step. The `MyApp.Cldr` backend module is used to instrospect the configured locales that drive the route and helper localization.\n\n```elixir\ndefmodule MyApp.Cldr do\n  use Cldr,\n    locales: [\"en\", \"fr\"],\n    default_locale: \"en\",\n    gettext: MyApp.Gettext,\n    providers: [Cldr.Routes]\n\nend\n```\n\n*Note* the addition of `Cldr.Routes` to the `:providers` configuration key is required.\n\n## Define Localized Routes\n\nNow we can configure the router module to use the `localize/1` macro by adding `use MyApp.Cldr.Routes` to the module and invoke the `localize/1` macro to wrap the required routes. `use MyApp.Cldr.Routes` must be added *after* `use Phoenix.Router`. For example:\n\n```elixir\ndefmodule MyAppWeb.Router do\n  use Phoenix.Router\n  use MyApp.Cldr.Routes\n\n  localize do\n    get \"/pages/:page\", PageController, :show\n    resources \"/users\", UserController\n  end\nend\n```\n\nThe following routes are generated (assuming that translations are updated in the `Gettext` configuration). For this example, the `:fr` translations are the same as the `:en` text with `_fr` appended.\n```bash\n% mix phx.routes MyAppWeb.Router\npage_de_path  GET     /pages_de/:page     PageController :show\npage_en_path  GET     /pages/:page        PageController :show\npage_fr_path  GET     /pages_fr/:page     PageController :show\nuser_de_path  GET     /users_de           UserController :index\nuser_de_path  GET     /users_de/:id/edit  UserController :edit\nuser_de_path  GET     /users_de/new       UserController :new\nuser_de_path  GET     /users_de/:id       UserController :show\n...\n```\n\n## Interpolating Locale Data\n\nA route may be defined with elements of the locale interpolated into it. These interpolatins are specified using the normal `#{}` interpolation syntax. However since route translation occurs at compile time only the following interpolations are supported:\n\n* `locale` will interpolate the Cldr locale name\n* `language` will interpolate the Cldr language name\n* `territory` will interpolate the Cldr territory code\n\nSome examples are:\n```elixir\nlocalize do\n  get \"/#{locale}/locale/pages/:page\", PageController, :show\n  get \"/#{language}/language/pages/:page\", PageController, :show\n  get \"/#{territory}/territory/pages/:page\", PageController, :show\nend\n```\n\n## Localized Helpers\n\n\u003e #### Warning {: .warning}\n\u003e\n\u003e Route helpers are deprecated in favor of verified routes as of Phoenix 1.7. Like Phoenix,\n\u003e localization route helpers can still be generated. When using `mix phx.new my_app`,\n\u003e the `MyAppWeb` module will include `use Phoenix.Routes, helpers: false`. This is also the\n\u003e recommendation when applying `use MyApp.Cldr.Routes`.\n\nManually constructing the localized helper names shown in the example above would be tedious. Therefore a `LocalizedHelpers` module is generated at compile-time. Assuming the router module is called `MyAppWeb.Router` then the full name of the localized helper module is `MyAppWeb.Router.LocalizedHelpers`.\n\nThe functions on this module are the non-localized versions that should be used by applications (they delegate ultimately to the localized routes based upon the current locale).\n\nThe functions on the `LocalizedHelpers` module all respect the current locale, based upon `Cldr.get_locale/1`, and will delegate to the appropriate localized function in the `Helpers` function created automatically at compile time. For example:\n```elixir\niex\u003e MyApp.Cldr.put_locale(\"en\")\niex\u003e MyAppWeb.Router.LocalizedHelpers.page_path %Plug.Conn{}, :show, 1\n\"/pages/1\"\niex\u003e MyApp.Cldr.put_locale(\"fr\")\niex\u003e MyAppWeb.Router.LocalizedHelpers.page_path %Plug.Conn{}, :show, 1\n\"/pages_fr/1\"\n```\n\n*Note* The localized helpers translate the path based upon the `:gettext_locale_name` for the currently set `Cldr` locale. It is the developers responsibility to ensure that the locale is set appropriately before calling any localized path helpers.\n\n### Introspecting localized routes\n\nFor convenience in introspecting routes, a module called `MyApp.Router.LocalizedRoutes` is generated that can be used with the `mix phx.routes` mix task. For example:\n\n```bash\n% cldr_routes % mix phx.routes MyAppWeb.Router.LocalizedRoutes\npage_path  GET     /pages_de/:page     PageController :show\npage_path  GET     /pages/:page        PageController :show\npage_path  GET     /pages_fr/:page     PageController :show\nuser_path  GET     /users_de           UserController :index\nuser_path  GET     /users_de/:id/edit  UserController :edit\nuser_path  GET     /users_de/new       UserController :new\n...\n```\n\nIn addition, each localized path stores the `Cldr` locale in the `:private` field for the route under the `:cldr_locale` key. This allows the developer to recognise which locale was used to generate the localized route.\n\nThis information is also used by functions in the [ex_cldr_plugs](https://hex.pm/packages/ex_cldr_plugs) library to:\n\n* Identify the users locale from the route in `Cldr.Plug.PutLocale`\n* Store the identified locale in the session in `Cldr.Plug.PutSession`\n* Propogate the locale from the session into a LiveView process during the `on_mount/3` callback with `Cldr.Plug.put_locale_from_session/2`\n\n## Localized Verified Routes\n\n`Sigil_q` implements localized verified routes for Phoenix 1.7 and later.\n\nAdding\n```elixir\nuse MyApp.Cldr.VerifiedRoutes,\n  router: MyApp.Router,\n  endpoint: MyApp.Endpoint\n```\nto a module gives access to `sigil_q` which is functionally equal to Phoenix Verified Routes `sigil_p`. In fact the result of using `sigil_q` is code that looks like this:\n\n```elixir\n# ~q\"/users\" generates the following code for a\n# Cldr backend that has configured the locales\n# :en, :fr and :de\n\ncase MyApp.Cldr.get_locale().cldr_locale_name do\n  :de -\u003e ~p\"/users_de\"\n  :en -\u003e ~p\"/users\"\n  :fr -\u003e ~p\"/users_fr\"\nend\n```\n\n### Locale interpolation\n\nSome use cases call for the locale, language or territory to be part of the URL. `Sigl_q` makes this easy by providing\nthe following interpolations into a `Sigil_q` path:\n\n* `:locale` is replaced with Cldr locale name.\n* `:language` is replaced with the Cldr language code.\n* `:territory` is replaced with the Cldr territory code.\n\n```elixir\n# ~q\"/users/:locale\" generates the following code for a\n# Cldr backend that has configured the locales\n# :en, :fr and :de. Note the interpolation of the locale\n# information which is performed at compile time.\n\ncase MyApp.Cldr.get_locale().cldr_locale_name do\n  :de -\u003e ~p\"/users_de/de\"\n  :en -\u003e ~p\"/users/en\"\n  :fr -\u003e ~p\"/users_fr/fr\"\nend\n```\n\n## Phoenix MyWebApp configuration\n\nSince localized routes, route helpers and verified routes have the same function and API as the standard Phoenix equivalent modules it is possible to update the generated Phoenix configuration. Assuming the presence of `myapp_web.ex` defining the module `MyAppWeb` then the following changes should be considered in the `my_web_app.ex` file:\n\n### Router\n```elixir\ndef router do\n  quote do\n    use Phoenix.Router, helpers: false\n\n    # Add localized routes\n    use MyApp.Cldr.Routes, helpers: false\n\n    # Import common connection and controller functions to use in pipelines\n    import Plug.Conn\n    import Phoenix.Controller\n    import Phoenix.LiveView.Router\n  end\nend\n```\n\n### Verified Routes\nChange `Phoenix.VerifiedRoutes` to `MyApp.Cldr.VerifiedRoutes`:\n```elixir\n  def verified_routes do\n    quote do\n      use MyApp.Cldr.VerifiedRoutes,\n        endpoint: PhxCldrWeb.Endpoint,\n        router: PhxCldrWeb.Router,\n        statics: PhxCldrWeb.static_paths()\n    end\n  end\n```\n\n## Generating link headers\n\nWhen the same content is produced in multiple languages it is important to cross-link the different editions of the content to each other. This is good practise in general but strong advised by [goggle](https://developers.google.com/search/docs/advanced/crawling/localized-versions) to reduce SEO risks for your site.\n\nThis cross-linking can be done with the aid of HTTP headers or with `\u003clink /\u003e` tags in the `\u003chead\u003e` section of an HTML document. Helpers are generated by `ex_cldr_routes` to facilitate the creating of these links. These functions are generated in the `MyAppWeb.Router.LocalizedHelpers` module (where `MyAppWeb.Router` is the name of your Phoenix router module).\n\n* `MyAppWeb.Router.LocalizedHelpers.\u003chelper\u003e_links` generated a mapping of locales for a given route to the URLs for those locales.\n* `MyAppWeb.Router.LocalizedHelpers.hreflang_links/1` take the generated map and produces link headers ready for insertion into an HTML document.\n\nThe functions can be used as follows:\n\n1. Update the controller to generate the links and add them to `:assigns`\n\n```elixir\ndefmodule MyAppWeb.UserController do\n  use MyAppWeb, :controller\n\n  # This alias would normally be set in the\n  # MyAppWeb.controller/0 and MyAppWeb.view_helpers/0\n  # functions\n  alias MyAppWeb.Router.LocalizedHelpers, as: Routes\n\n  alias MyApp.Accounts\n  alias MyApp.Accounts.User\n\n  def show(conn, %{\"id\" =\u003e id}) do\n    user = Accounts.get_user!(id)\n    hreflang_links = Routes.user_links(conn, :show, id)\n    render(conn, \"show.html\", user: user, hreflang_links: hreflang_links)\n  end\nend\n```\n\n2. Update the root layout to add the hreflang links\n\n```html\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"/\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/\u003e\n    \u003c%= Routes.hreflang_links(assigns[:hreflang_links]) %\u003e\n    \u003c%= csrf_meta_tag() %\u003e\n    \u003c%= live_title_tag assigns[:page_title] || \"Routing\", suffix: \" · Phoenix Framework\" %\u003e\n    \u003clink phx-track-static rel=\"stylesheet\" href={Routes.static_path(@conn, \"/assets/app.css\")}/\u003e\n    \u003cscript defer phx-track-static type=\"text/javascript\" src={Routes.static_path(@conn, \"/assets/app.js\")}\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    ...\n    \u003c%= @inner_content %\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Translations\n\nIn order for routes to be localized, translations must be provided for each path segment. This translation is performed by `dgettext/3` with the domain \"routes\". Therefore for each configured locale, a \"routes.pot\" file is required containing the path segment translations for that locale.\n\nUsing the example Cldr backend that has \"en\" and \"fr\" Gettext locales then the directory structure would look like the following (if the default Gettext configuration is used):\n\n    priv/gettext\n    ├── default.pot\n    ├── en\n    │   └── LC_MESSAGES\n    │       ├── default.po\n    │       ├── errors.po\n    │       └── routes.po\n    ├── errors.pot\n    └── fr\n        └── LC_MESSAGES\n            ├── default.po\n            ├── errors.po\n            └── routes.po\n\nThe `mix` tasks `gettext.extract` and `gettext.merge` can be used to support the extraction of routing segments and to create new translation locales.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-cldr%2Fcldr_routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixir-cldr%2Fcldr_routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-cldr%2Fcldr_routes/lists"}