elixir-cldr / cldr_routes

Localised routes and path helpers for Phoenix applications
Other
14 stars 6 forks source link

[Request] Support for Cldr.Plug.SetLocale #7

Closed rubas closed 2 years ago

rubas commented 2 years ago

cldr_routes provides localized routes and sets cldr_locale in conn.assigns.

Wouldn't it make sense to work together with Cldr.Plug.SetLocale out of the box?


I haven't found a from configuration for Cldr.Plug.SetLocale that works with cldr_routes (except custom function).

Worse - I ended up with conn.assigns.cldr_locale set by cldr_routes and conn.private.cldr_locale set by Cldr.Plug.SetLocale.

kipcole9 commented 2 years ago

The reason I haven't done this is that it is very possible for the locale to be set by Cldr.Plug.SetLocale to be different but compatible with Cldr.Route.

For example with the following route:

localize :de do
  get "/pages/:page", PageController
end 

But the user arrives at the site with a locale defined (in path, session, params, whatever) as de-CH-u-cu-usd-ca-buddhist-co-phonebk-ka-shifted-nu-latn.

Hence the session should be set with the locale as requested by the user (which is what `Cldr.Plug.SetLocale does). The route locale identifies how the user arrived at the site (well, somewhat at least, which is what Cldr.Route does).

I agree its a bit messy and thoughts on how to reconcile the "what the user asked for" with "how they got here" and "where do I look for what the user wants" would be most helpful if you have some ideas.

kipcole9 commented 2 years ago

With ex_cldr_plugs version 1.1.0 now supporting :assigns as an option to Cldr.Plug.SetLocale I think this issue is largely addressed. It can now recognise the locale from :assigns and set the locale appropriately.

I'll close this issue, but please reopen if that change doesn't address what you expected.

rubas commented 2 years ago

Thank 👍 . This(:assign) is exactly what is was doing with my own function. It just felt natural that those library would be compatible out the box.

I agree its a bit messy and thoughts on how to reconcile the "what the user asked for" with "how they got here" and "where do I look for what the user wants" would be most helpful if you have some ideas.

TL;DR: from is perfect. Wouldn't change.

It is a complicated topic and the order has to be defined on a per-project basis. For example, depending on which site we may look at :assign, User Preference, Accept Language Header and Default Language.

from in Cldr.Plug.SetLocale allows us to do this. And it's up to us to deal with project-specific edge cases.

rubas commented 2 years ago

:assigns works perfectly for normaler Controller but the locale is not changed for the LiveView process.

Problem

[info] GET /de/test
%{
  cldr_locale: #Cldr.LanguageTag<de [validated]>,
  current_user: nil,
  locale: nil
}
#Cldr.LanguageTag<de [validated]>
[info] GET /de
#Cldr.LanguageTag<en [validated]>

Solution

This is the big problem, you referred in https://github.com/elixir-cldr/cldr_routes/issues/6.

Variant: on_mount callback

https://hexdocs.pm/phoenix_live_view/using-gettext.html

Setting the locale with on_mount is neat and would be my go-to-solution.

But this callback has to be defined with live_session. live_session may or may not be inside the localize block. This makes it difficult to deal with it on a library level.

Variant: mount function

One could write a macro (or a simple Helper) that handles it directly in the mount function of the Live Controller. But people will forget to add the macro/call the function.

Again, very unsatisfying.


Configuration

router.exs

defmodule MarkoWeb.Router do
  @moduledoc """
  # Locale
  The locale is set by

  1. by user settings, and redirected if it's not matching
  2. by cldr_routes
  3. by accept_language
  4. default
  """
  use MarkoWeb, :router

  import MarkoWeb.Plug.UserAuth

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)

    plug(Cldr.Plug.SetLocale,
      apps: [:cldr, :gettext],
      from: [:assigns],
      gettext: MarkoGettext,
      cldr: MarkoCldr
    )
   [...]
  end

  live_session :default do
    scope "/", MarkoWeb do
      pipe_through([:browser])

      get("/", HomeController, :index)

      localize do
        live("/#{locale}", HomeLive)
        get("/#{locale}/test", HomeController, :test)
      end
    end
  end
end

controllers/home_controller.ex

defmodule MarkoWeb.HomeController do
  use MarkoWeb, :controller

  def index(conn, _opts) do
    redirect(conn, to: Routes.home_live_path(conn, MarkoWeb.HomeLive))
  end

  def test(conn, _opts) do
    IO.inspect(conn.assigns)
    IO.inspect(MarkoCldr.get_locale())
    conn
  end
end

live/home_live.ex

defmodule MarkoWeb.HomeLive do
  @moduledoc """
  Home view
  """

  use MarkoWeb, :live_view

  @impl Phoenix.LiveView
  def mount(_params, session, socket) do
    IO.inspect(MarkoCldr.get_locale())

    socket =
      socket
      |> assign(:page_title, gettext("Home"))

    {:ok, socket}
  end

  @impl Phoenix.LiveView
  def render(assigns) do
    ~H"""
      <%= gettext("Home") %>
    """
  end

  @impl Phoenix.LiveView
  def handle_params(_params, _uri, socket) do
    {:noreply, socket}
  end
end
kipcole9 commented 2 years ago

Yes, this is definitely the tricky part. With you help (and great patience!) I think most of the library is now operating correctly (just added more tests too). But I do need to dive more into Liveview, especially how the session and socket are populated and how the interact with routing.

Getting late here, will jump on this tomorrow.

kipcole9 commented 2 years ago

Closing this favour of #6, lets continue the work on that issue since I think its the more relevant one.