diff --git a/config/test.exs b/config/test.exs index 6a14147..7b1dbcc 100644 --- a/config/test.exs +++ b/config/test.exs @@ -2,5 +2,4 @@ import Config config :logger, level: :warning -config :ex_cldr_routes, MyAppWeb.Endpoint, - secret_key_base: "kjoy3o1zeidquwy1398juxzldjlksahdk3" \ No newline at end of file +config :ex_cldr_routes, MyAppWeb.Endpoint, secret_key_base: "kjoy3o1zeidquwy1398juxzldjlksahdk3" diff --git a/lib/cldr/route.ex b/lib/cldr/route.ex index 141e302..a1a8805 100644 --- a/lib/cldr/route.ex +++ b/lib/cldr/route.ex @@ -254,15 +254,15 @@ defmodule Cldr.Route do @doc false def confirm_backend_has_gettext!(backend, %Cldr.Config{gettext: nil}) do raise ArgumentError, - """ - The Cldr backend #{inspect(backend)} does not have a Gettext - module configured. + """ + The Cldr backend #{inspect(backend)} does not have a Gettext + module configured. - A Gettext module must be configured in order to define localized - routes. In addition, translations must be provided for the Gettext - backend under the "routes" domain (ie in a file "routes.pot" for - each configured Gettext locale). - """ + A Gettext module must be configured in order to define localized + routes. In addition, translations must be provided for the Gettext + backend under the "routes" domain (ie in a file "routes.pot" for + each configured Gettext locale). + """ end def confirm_backend_has_gettext!(_backend, %Cldr.Config{} = _config) do @@ -278,15 +278,21 @@ defmodule Cldr.Route do defmacro __before_compile__(env) do alias Cldr.Route.LocalizedHelpers routes = env.module |> Module.get_attribute(:phoenix_routes) |> Enum.reverse() - forwards = env.module |> Module.get_attribute(:phoenix_forwards) localized_routes = Cldr.Route.routes(routes) + forwards = env.module |> Module.get_attribute(:phoenix_forwards) # Remove bookkeeping data in :private Module.delete_attribute(env.module, :phoenix_routes) Module.register_attribute(env.module, :phoenix_routes, []) Module.put_attribute(env.module, :phoenix_routes, Cldr.Route.delete_original_path(routes)) - routes_with_exprs = Enum.map(routes, &{&1, Phoenix.Router.Route.exprs(&1, forwards)}) + routes_with_exprs = + if function_exported?(Phoenix.Router.Route, :exprs, 2) do + Enum.map(routes, &{&1, apply(Phoenix.Router.Route, :exprs, [&1, forwards])}) + else + Enum.map(routes, &{&1, apply(Phoenix.Router.Route, :exprs, [&1])}) + end + helpers_moduledoc = Module.get_attribute(env.module, :helpers_moduledoc) LocalizedHelpers.define(env, routes_with_exprs, docs: helpers_moduledoc) @@ -429,7 +435,7 @@ defmodule Cldr.Route do # Rewrite nested resources; guard against infinite recursion defmacro localize(locale, {:resources, _, [path, controller, [do: {fun, _, _}] = nested]}) - when fun != :localize do + when fun != :localize do nested = localize_nested_resources(locale, nested) quote do @@ -442,7 +448,8 @@ defmodule Cldr.Route do end # Do the actual translations - defmacro localize(cldr_locale_name, {verb, meta, [path | args]}) when verb in @localizable_verbs do + defmacro localize(cldr_locale_name, {verb, meta, [path | args]}) + when verb in @localizable_verbs do cldr_backend = Module.get_attribute(__CALLER__.module, :_cldr_backend) do_localize(:private, cldr_locale_name, cldr_backend, {verb, meta, [path | args]}) end @@ -453,10 +460,10 @@ defmodule Cldr.Route do args = Enum.map_join(args, ", ", &inspect/1) raise ArgumentError, - """ - Invalid route for localization: #{verb} #{inspect(path)}, #{inspect(args)} - Allowed localizable routes are #{inspect(@localizable_verbs)} - """ + """ + Invalid route for localization: #{verb} #{inspect(path)}, #{inspect(args)} + Allowed localizable routes are #{inspect(@localizable_verbs)} + """ end defp do_localize(field, cldr_locale_name, cldr_backend, {verb, meta, [path | args]} = route) do @@ -490,7 +497,7 @@ defmodule Cldr.Route do end defp localize_nested_resources(locale, nested) do - Macro.postwalk nested, fn + Macro.postwalk(nested, fn {:resources, _, [_path, _meta, _args, [do: {:resources, _, _}]]} = resources -> quote do localize unquote(locale) do @@ -507,27 +514,27 @@ defmodule Cldr.Route do {:resources, _, _} = route -> quote do - localize unquote(locale), unquote(route) + localize(unquote(locale), unquote(route)) end other -> other - end + end) end defp locales_from_unique_gettext_locales(cldr_backend) do cldr_backend.known_locale_names() |> Enum.map(&cldr_backend.validate_locale/1) |> Enum.map(&elem(&1, 1)) - |> Enum.uniq_by(&(&1.gettext_locale_name)) + |> Enum.uniq_by(& &1.gettext_locale_name) |> Enum.reject(&is_nil/1) - |> Enum.map(&(&1.cldr_locale_name)) + |> Enum.map(& &1.cldr_locale_name) end # Interpolates the locale, language and territory # into he path by splicing the AST defp interpolate(path, locale) do - Macro.prewalk path, fn + Macro.prewalk(path, fn {{:., _, [Kernel, :to_string]}, _, [{:locale, _, _}]} -> to_string(locale.cldr_locale_name) |> String.downcase() @@ -539,7 +546,7 @@ defmodule Cldr.Route do other -> other - end + end) end # Since we are doing com[ile-time translation of the @@ -564,7 +571,7 @@ defmodule Cldr.Route do end defp combine_string_segments({:<>, _, [a, b]}) do - [combine_string_segments(a), combine_string_segments(b)] + [combine_string_segments(a), combine_string_segments(b)] end defp combine_string_segments([a | rest]) do @@ -573,12 +580,12 @@ defmodule Cldr.Route do defp combine_string_segments(ast) do raise ArgumentError, - """ - The path arugment to a localized route must be a binary that - can be resolved at compile time. Found: + """ + The path arugment to a localized route must be a binary that + can be resolved at compile time. Found: - #{Macro.to_string(ast)} - """ + #{Macro.to_string(ast)} + """ end # Localise the helper name for the a verb (except resources) @@ -785,8 +792,9 @@ defmodule Cldr.Route do # When the routes match except for locale defp group_locales_by_path_helper_verb([ - %{path: path, helper: helper, verb: verb} = first, - %{path: path, helper: helper, verb: verb} = second | rest]) do + %{path: path, helper: helper, verb: verb} = first, + %{path: path, helper: helper, verb: verb} = second | rest + ]) do locales = Enum.uniq([locale_from_args(second) | first.metadata.locales]) |> Enum.sort() metadata = Map.put(first.metadata, :locales, locales) group_locales_by_path_helper_verb([%{first | metadata: metadata} | rest]) @@ -855,7 +863,7 @@ defmodule Cldr.Route do end defp escape_interpolation(path) do - Macro.prewalk path, fn + Macro.prewalk(path, fn {{:., _, [Kernel, :to_string]}, _, [{:locale, _, _}]} -> ~S"#{locale}" @@ -867,6 +875,6 @@ defmodule Cldr.Route do other -> other - end + end) end end diff --git a/lib/cldr/routes/helpers.ex b/lib/cldr/routes/helpers.ex index 590094f..4ba3004 100644 --- a/lib/cldr/routes/helpers.ex +++ b/lib/cldr/routes/helpers.ex @@ -40,9 +40,9 @@ defmodule Cldr.Route.LocalizedHelpers do code = quote do @moduledoc unquote(docs) && - """ - Module with localized helpers generated from #{inspect(unquote(env.module))}. - """ + """ + Module with localized helpers generated from #{inspect(unquote(env.module))}. + """ alias Cldr.Route.LocalizedHelpers @@ -351,7 +351,7 @@ defmodule Cldr.Route.LocalizedHelpers do """ @spec hreflang_links(%{LocalizedHelpers.locale_name() => LocalizedHelpers.url()}) :: - Phoenix.HTML.safe() + Phoenix.HTML.safe() def hreflang_links(url_map) do Cldr.Route.LocalizedHelpers.hreflang_links(url_map) @@ -366,11 +366,12 @@ defmodule Cldr.Route.LocalizedHelpers do for {helper, routes_by_locale} <- helper_by_locale(routes), {vars, locales} <- routes_by_locale do if locales == [] do - quiet_vars = Enum.map(vars, fn var -> - quote do - _ = unquote(var) - end - end) + quiet_vars = + Enum.map(vars, fn var -> + quote do + _ = unquote(var) + end + end) quote generated: true, location: :keep do def unquote(:"#{helper}_links")(conn_or_endpoint, plug_opts, unquote_splicing(vars)) do @@ -379,15 +380,15 @@ defmodule Cldr.Route.LocalizedHelpers do end end else - quote generated: true, location: :keep do + quote generated: true, location: :keep do def unquote(:"#{helper}_links")(conn_or_endpoint, plug_opts, unquote_splicing(vars)) do for locale <- unquote(Macro.escape(locales)) do - Cldr.with_locale locale, fn -> + Cldr.with_locale(locale, fn -> { Map.fetch!(locale, :requested_locale_name), unquote(:"#{helper}_url")(conn_or_endpoint, plug_opts, unquote_splicing(vars)) } - end + end) end |> Map.new() end @@ -413,9 +414,10 @@ defmodule Cldr.Route.LocalizedHelpers do end defp routes_by_locale(routes) do - Enum.group_by(routes, + Enum.group_by( + routes, fn {_route, exprs} -> elem(:lists.unzip(exprs.binding), 1) end, - fn {route, _exprs} -> route.private[:cldr_locale] end + fn {route, _exprs} -> route.private[:cldr_locale] end ) |> Enum.map(fn {vars, [nil]} -> {vars, []} @@ -535,6 +537,7 @@ defmodule Cldr.Route.LocalizedHelpers do @doc false def strip_locale(helper, locale) + def strip_locale(helper, %Cldr.LanguageTag{} = locale) do locale_name = locale.gettext_locale_name strip_locale(helper, locale_name) diff --git a/mix.exs b/mix.exs index a4f0faa..9f720f5 100644 --- a/mix.exs +++ b/mix.exs @@ -54,6 +54,7 @@ defmodule CldrRoutes.MixProject do [ {:ex_cldr, "~> 2.32"}, {:phoenix, "~> 1.7-rc", override: true}, + # {:phoenix, "~> 1.6", override: true}, {:phoenix_live_view, "~> 0.18", optional: true}, {:jason, "~> 1.0"}, {:gettext, "~> 0.19"}, diff --git a/mix.lock b/mix.lock index 987f8bb..084e176 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, + "castore": {:hex, :castore, "0.1.20", "62a0126cbb7cb3e259257827b9190f88316eb7aa3fdac01fd6f2dfd64e7f46e9", [:mix], [], "hexpm", "a020b7650529c986c454a4035b6b13a328e288466986307bea3aadb4c95ac98a"}, "cldr_utils": {:hex, :cldr_utils, "2.19.1", "5a7bcd2f2fd432c548e494e850bba8a9e838f1b10202f682ea1d9809d74eff31", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "fbd10f79363e70f3d893ab21e195f444ca87c2c80120b5911761491da4489620"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, @@ -19,7 +19,7 @@ "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.3", "2e3d009422addf8b15c3dccc65ce53baccbe26f7cfd21d264680b5867789a9c1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c8845177a866e017dcb7083365393c8f00ab061b8b6b2bda575891079dce81b2"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, - "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, diff --git a/mix/myapp_router.ex b/mix/myapp_router.ex index d22b36e..1f640bf 100644 --- a/mix/myapp_router.ex +++ b/mix/myapp_router.ex @@ -23,12 +23,10 @@ defmodule MyApp.Router do # Specific set of locales localize [:en, :fr] do resources "/comments", PageController, except: [:delete] - get "/pages/:page", PageController, :edit, assigns: %{key: :value} end - # Test all verbs + # Test all other verbs localize do - get "/pages/:page", PageController, :show patch "/pages/:page", PageController, :update delete "/pages/:page", PageController, :delete post "/pages/:page", PageController, :create diff --git a/test/cldr_routes_test.exs b/test/cldr_routes_test.exs index 970ae2c..dead5cc 100644 --- a/test/cldr_routes_test.exs +++ b/test/cldr_routes_test.exs @@ -112,22 +112,22 @@ defmodule Cldr.Route.Test do assert MyApp.Router.LocalizedHelpers.user_path(%Plug.Conn{}, :index) == "/users" {:ok, locale} = MyApp.Cldr.validate_locale("en-AU") - Cldr.put_locale locale + Cldr.put_locale(locale) assert MyApp.Router.LocalizedHelpers.user_path(%Plug.Conn{}, :index) == "/users" end test "An warning is printed when there is no gettext locale and raises if helper is called" do assert capture_io(:stderr, fn -> - defmodule MyTestApp.Router do - use Phoenix.Router - use Backend.Cldr.Routes - - # Nested routes to an arbitrary level (testing with 3) - localize do - get "/pages/:page", PageController, :show, private: %{key: :value} - end - end - end) =~ "No known gettext locale for :es" + defmodule MyTestApp.Router do + use Phoenix.Router + use Backend.Cldr.Routes + + # Nested routes to an arbitrary level (testing with 3) + localize do + get("/pages/:page", PageController, :show, private: %{key: :value}) + end + end + end) =~ "No known gettext locale for :es" capture_io(fn -> {:ok, locale} = Backend.Cldr.validate_locale("es") @@ -143,38 +143,38 @@ defmodule Cldr.Route.Test do describe "Interpolate during route generation" do test "interpolating a locale" do assert find_route(MyApp.Router, "/de/locale/pages_de/:page") == - %{ - helper: "with_locale_de", - metadata: %{log: :debug}, - path: "/de/locale/pages_de/:page", - plug: PageController, - plug_opts: :show, - verb: :get - } + %{ + helper: "with_locale_de", + metadata: %{log: :debug}, + path: "/de/locale/pages_de/:page", + plug: PageController, + plug_opts: :show, + verb: :get + } end test "interpolating a language" do assert find_route(MyApp.Router, "/de/language/pages_de/:page") == - %{ - helper: "with_language_de", - metadata: %{log: :debug}, - path: "/de/language/pages_de/:page", - plug: PageController, - plug_opts: :show, - verb: :get - } + %{ + helper: "with_language_de", + metadata: %{log: :debug}, + path: "/de/language/pages_de/:page", + plug: PageController, + plug_opts: :show, + verb: :get + } end test "interpolating a territory" do assert find_route(MyApp.Router, "/de/territory/pages_de/:page") == - %{ - helper: "with_territory_de", - metadata: %{log: :debug}, - path: "/de/territory/pages_de/:page", - plug: PageController, - plug_opts: :show, - verb: :get - } + %{ + helper: "with_territory_de", + metadata: %{log: :debug}, + path: "/de/territory/pages_de/:page", + plug: PageController, + plug_opts: :show, + verb: :get + } end @endpoint MyApp.Router @@ -192,33 +192,105 @@ defmodule Cldr.Route.Test do @endpoint MyAppWeb.Endpoint test "hreflang link helper" do - conn = get(build_conn(), "/users/1") - - links = MyApp.Router.LocalizedHelpers.user_links(conn, :show, 1) - header_io_data = MyApp.Router.LocalizedHelpers.hreflang_links(links) - header = Phoenix.HTML.safe_to_string(header_io_data) - - assert links == %{ - "de" => "http://localhost/users_de/1", - "en" => "http://localhost/users/1", - "fr" => "http://localhost/users_fr/1" - } - - assert header_io_data == { - :safe, - [ - [60, "link", [32, "href", 61, 34, "http://localhost/users_de/1", 34, 32, "hreflang", 61, 34, "de", 34, 32, "rel", 61, 34, "alternate", 34], 62], - 10, - [60, "link", [32, "href", 61, 34, "http://localhost/users/1", 34, 32, "hreflang", 61, 34, "en", 34, 32, "rel", 61, 34, "alternate", 34], 62], - 10, - [60, "link", [32, "href", 61, 34, "http://localhost/users_fr/1", 34, 32, "hreflang", 61, 34, "fr", 34, 32, "rel", 61, 34, "alternate", 34], 62] - ] - } - - assert header == - "\n" <> - "\n" <> - "" + conn = get(build_conn(), "/users/1") + + links = MyApp.Router.LocalizedHelpers.user_links(conn, :show, 1) + header_io_data = MyApp.Router.LocalizedHelpers.hreflang_links(links) + header = Phoenix.HTML.safe_to_string(header_io_data) + + assert links == %{ + "de" => "http://localhost/users_de/1", + "en" => "http://localhost/users/1", + "fr" => "http://localhost/users_fr/1" + } + + assert header_io_data == { + :safe, + [ + [ + 60, + "link", + [ + 32, + "href", + 61, + 34, + "http://localhost/users_de/1", + 34, + 32, + "hreflang", + 61, + 34, + "de", + 34, + 32, + "rel", + 61, + 34, + "alternate", + 34 + ], + 62 + ], + 10, + [ + 60, + "link", + [ + 32, + "href", + 61, + 34, + "http://localhost/users/1", + 34, + 32, + "hreflang", + 61, + 34, + "en", + 34, + 32, + "rel", + 61, + 34, + "alternate", + 34 + ], + 62 + ], + 10, + [ + 60, + "link", + [ + 32, + "href", + 61, + 34, + "http://localhost/users_fr/1", + 34, + 32, + "hreflang", + 61, + 34, + "fr", + 34, + 32, + "rel", + 61, + 34, + "alternate", + 34 + ], + 62 + ] + ] + } + + assert header == + "\n" <> + "\n" <> + "" end test "hreflang test helper for non-localized route" do diff --git a/test/support/my_endpoint.ex b/test/support/my_endpoint.ex index 88fd837..ce8f7d8 100644 --- a/test/support/my_endpoint.ex +++ b/test/support/my_endpoint.ex @@ -1,5 +1,5 @@ defmodule MyAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :ex_cldr_routes - plug MyApp.Router -end \ No newline at end of file + plug(MyApp.Router) +end diff --git a/test/support/test_helper.ex b/test/support/test_helper.ex index 2badd03..3f63c52 100644 --- a/test/support/test_helper.ex +++ b/test/support/test_helper.ex @@ -3,7 +3,7 @@ defmodule Cldr.Route.TestHelper do Enum.find(router.__routes__(), &(&1.path == path)) end - def find_routes(router, %Regex{} = regex) do - Enum.filter(router.__routes__(), &(Regex.match?(regex, &1.path))) + def find_routes(router, %Regex{} = regex) do + Enum.filter(router.__routes__(), &Regex.match?(regex, &1.path)) end end