diff --git a/lib/localize/routes.ex b/lib/localize/routes.ex index eeb71a9..4c8ab8b 100644 --- a/lib/localize/routes.ex +++ b/lib/localize/routes.ex @@ -448,8 +448,6 @@ defmodule Localize.Routes do {:<<>>, meta, merge_literals(segments)} end - defp merge_literal_segments(other), do: other - defp merge_literals([first, second | rest]) when is_binary(first) and is_binary(second) do merge_literals([first <> second | rest]) end @@ -458,42 +456,40 @@ defmodule Localize.Routes do defp merge_literals([]), do: [] @doc false - def translate_path_now(path, locale, gettext_locale, gettext_backend) do - Macro.prewalk(path, fn segment -> - translate_segment_now(locale, gettext_locale, gettext_backend, segment) - end) - end - - defp translate_segment_now(_locale, _gettext_locale, _backend, "" = segment), do: segment - - defp translate_segment_now(_locale, _gettext_locale, _backend, @interpolate <> _rest = segment), - do: segment + # Only literal path segments are translated. Once a query or fragment + # begins, the rest of the route, including later literal segments, is + # left as is. Code inside `#{...}` is never walked. + def translate_path_now({:<<>>, meta, segments}, locale, gettext_locale, gettext_backend) do + {segments, _in_query?} = + Enum.map_reduce(segments, false, fn + segment, false when is_binary(segment) -> + translate_segment_now(locale, gettext_locale, gettext_backend, segment) - defp translate_segment_now(_locale, _gettext_locale, _backend, segment) - when not is_binary(segment), - do: segment + segment, in_query? -> + {segment, in_query?} + end) - defp translate_segment_now(locale, gettext_locale, gettext_backend, segment) - when is_binary(segment) do - segment - |> String.split("/") - |> translate_segment_parts(locale, gettext_locale, gettext_backend) - |> Enum.join("/") + {:<<>>, meta, segments} end - defp translate_segment_parts([last_part], locale, gettext_locale, gettext_backend) do - [last_part | rest] = Regex.split(~r/[#\?]/, last_part, include_captures: true) + defp translate_segment_now(_locale, _gettext_locale, _backend, @interpolate <> _rest = segment), + do: {segment, false} + + defp translate_segment_now(locale, gettext_locale, gettext_backend, segment) do + case :binary.match(segment, ["?", "#"]) do + :nomatch -> + {translate_segment_parts(segment, locale, gettext_locale, gettext_backend), false} - [translate_segment_part(last_part, locale, gettext_locale, gettext_backend) | rest] - |> :erlang.iolist_to_binary() - |> List.wrap() + {start, _length} -> + {path, query} = :erlang.split_binary(segment, start) + {translate_segment_parts(path, locale, gettext_locale, gettext_backend) <> query, true} + end end - defp translate_segment_parts([part | rest], locale, gettext_locale, gettext_backend) do - [ - translate_segment_part(part, locale, gettext_locale, gettext_backend) - | translate_segment_parts(rest, locale, gettext_locale, gettext_backend) - ] + defp translate_segment_parts(path, locale, gettext_locale, gettext_backend) do + path + |> String.split("/") + |> Enum.map_join("/", &translate_segment_part(&1, locale, gettext_locale, gettext_backend)) end defp translate_segment_part("", _locale, _gettext_locale, _backend), do: "" diff --git a/test/sigil_q_test.exs b/test/sigil_q_test.exs index 07c05ca..c488447 100644 --- a/test/sigil_q_test.exs +++ b/test/sigil_q_test.exs @@ -70,4 +70,15 @@ defmodule SigilQTest do assert url(MyApp.Endpoint, ~p[/users/us]) == "http://localhost/users/us" assert url(MyApp.Endpoint, MyApp.Router, ~p[/users/us]) == "http://localhost/users/us" end + + test "sigil_q translates the path but not a path in the query" do + Localize.put_locale(:fr) + assert ~q"/users?next=/users" == "/users_fr?next=/users" + assert ~q"/users?page=#{2}&next=/users" == "/users_fr?page=2&next=/users" + end + + test "sigil_q does not translate string literals in interpolated code" do + Localize.put_locale(:fr) + assert ~q"/users/#{String.upcase("users")}" == "/users_fr/USERS" + end end