diff --git a/config/runtime.exs b/config/runtime.exs index 3fbb035b..b1532fb3 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -12,10 +12,8 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do config :lokal, LokalWeb.Endpoint, server: true end -config :lokal, LokalWeb.ViewHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true" - -# Set locale -Gettext.put_locale(System.get_env("LOCALE") || "en_US") +# Set default locale +config :gettext, :default_locale, System.get_env("LOCALE") || "en_US" maybe_ipv6 = if System.get_env("ECTO_IPV6") == "true", do: [:inet6], else: [] diff --git a/contributing.md b/contributing.md index f8cb007d..7ffed2ff 100644 --- a/contributing.md +++ b/contributing.md @@ -96,7 +96,7 @@ In `dev` mode, Lokal will listen for these environment variables at runtime. Defaults to `false`. - `POOL_SIZE`: Controls the pool size to use with PostgreSQL. Defaults to `10`. - `REGISTRATION`: Controls if user sign-up should be invite only or set to public. Set to `public` to enable public registration. Defaults to `invite`. -- `LOCALE`: Sets a custom locale. Defaults to `en_US`. +- `LOCALE`: Sets a custom default locale. Defaults to `en_US`. ## `MIX_ENV=test` diff --git a/lib/lokal/accounts.ex b/lib/lokal/accounts.ex index 60c23135..fdcbe127 100644 --- a/lib/lokal/accounts.ex +++ b/lib/lokal/accounts.ex @@ -269,6 +269,35 @@ defmodule Lokal.Accounts do end end + @doc """ + Returns an `%Changeset{}` for changing the user locale. + + ## Examples + + iex> change_user_locale(user) + %Changeset{data: %User{}} + + """ + @spec change_user_locale(User.t()) :: Changeset.t(User.t()) + def change_user_locale(%{locale: locale} = user), do: User.locale_changeset(user, locale) + + @doc """ + Updates the user locale. + + ## Examples + + iex> update_user_locale(user, "valid locale") + {:ok, %User{}} + + iex> update_user_password(user, "invalid locale") + {:error, %Changeset{}} + + """ + @spec update_user_locale(User.t(), locale :: String.t()) :: + {:ok, User.t()} | {:error, Changeset.t(User.t())} + def update_user_locale(user, locale), + do: user |> User.locale_changeset(locale) |> Repo.update() + @doc """ Deletes a user. must be performed by an admin or the same user! diff --git a/lib/lokal/accounts/user.ex b/lib/lokal/accounts/user.ex index 67646b0e..074a4562 100644 --- a/lib/lokal/accounts/user.ex +++ b/lib/lokal/accounts/user.ex @@ -18,6 +18,7 @@ defmodule Lokal.Accounts.User do field :hashed_password, :string field :confirmed_at, :naive_datetime field :role, Ecto.Enum, values: [:admin, :user], default: :user + field :locale, :string has_many :invites, Invite, on_delete: :delete_all @@ -32,6 +33,7 @@ defmodule Lokal.Accounts.User do confirmed_at: NaiveDateTime.t(), role: atom(), invites: [Invite.t()], + locale: String.t() | nil, inserted_at: NaiveDateTime.t(), updated_at: NaiveDateTime.t() } @@ -60,7 +62,7 @@ defmodule Lokal.Accounts.User do Changeset.t(t() | new_user()) def registration_changeset(user, attrs, opts \\ []) do user - |> cast(attrs, [:email, :password, :role]) + |> cast(attrs, [:email, :password, :role, :locale]) |> validate_email() |> validate_password(opts) end @@ -185,4 +187,14 @@ defmodule Lokal.Accounts.User do do: changeset, else: changeset |> add_error(:current_password, dgettext("errors", "is not valid")) end + + @doc """ + A changeset for changing the user's locale + """ + @spec locale_changeset(t() | Changeset.t(t()), locale :: String.t() | nil) :: Changeset.t(t()) + def locale_changeset(user_or_changeset, locale) do + user_or_changeset + |> cast(%{"locale" => locale}, [:locale]) + |> validate_required(:locale) + end end diff --git a/lib/lokal_web.ex b/lib/lokal_web.ex index abf73676..6a077124 100644 --- a/lib/lokal_web.ex +++ b/lib/lokal_web.ex @@ -47,6 +47,7 @@ defmodule LokalWeb do use Phoenix.LiveView, layout: {LokalWeb.LayoutView, "live.html"} + on_mount LokalWeb.InitAssigns unquote(view_helpers()) end end diff --git a/lib/lokal_web/controllers/user_settings_controller.ex b/lib/lokal_web/controllers/user_settings_controller.ex index 9c153a25..22171f8b 100644 --- a/lib/lokal_web/controllers/user_settings_controller.ex +++ b/lib/lokal_web/controllers/user_settings_controller.ex @@ -10,10 +10,11 @@ defmodule LokalWeb.UserSettingsController do render(conn, "edit.html", page_title: gettext("Settings")) end - def update(conn, %{"action" => "update_email"} = params) do - %{"current_password" => password, "user" => user_params} = params - user = conn.assigns.current_user - + def update(%{assigns: %{current_user: user}} = conn, %{ + "action" => "update_email", + "current_password" => password, + "user" => user_params + }) do case Accounts.apply_user_email(user, password, user_params) do {:ok, applied_user} -> Accounts.deliver_update_email_instructions( @@ -33,14 +34,15 @@ defmodule LokalWeb.UserSettingsController do |> redirect(to: Routes.user_settings_path(conn, :edit)) {:error, changeset} -> - render(conn, "edit.html", email_changeset: changeset) + conn |> render("edit.html", email_changeset: changeset) end end - def update(conn, %{"action" => "update_password"} = params) do - %{"current_password" => password, "user" => user_params} = params - user = conn.assigns.current_user - + def update(%{assigns: %{current_user: user}} = conn, %{ + "action" => "update_password", + "current_password" => password, + "user" => user_params + }) do case Accounts.update_user_password(user, password, user_params) do {:ok, user} -> conn @@ -49,12 +51,27 @@ defmodule LokalWeb.UserSettingsController do |> UserAuth.log_in_user(user) {:error, changeset} -> - render(conn, "edit.html", password_changeset: changeset) + conn |> render("edit.html", password_changeset: changeset) end end - def confirm_email(conn, %{"token" => token}) do - case Accounts.update_user_email(conn.assigns.current_user, token) do + def update( + %{assigns: %{current_user: user}} = conn, + %{"action" => "update_locale", "user" => %{"locale" => locale}} + ) do + case Accounts.update_user_locale(user, locale) do + {:ok, _user} -> + conn + |> put_flash(:info, dgettext("prompts", "Language updated successfully.")) + |> redirect(to: Routes.user_settings_path(conn, :edit)) + + {:error, changeset} -> + conn |> render("edit.html", locale_changeset: changeset) + end + end + + def confirm_email(%{assigns: %{current_user: user}} = conn, %{"token" => token}) do + case Accounts.update_user_email(user, token) do :ok -> conn |> put_flash(:info, dgettext("prompts", "Email changed successfully.")) @@ -84,11 +101,10 @@ defmodule LokalWeb.UserSettingsController do end end - defp assign_email_and_password_changesets(conn, _opts) do - user = conn.assigns.current_user - + defp assign_email_and_password_changesets(%{assigns: %{current_user: user}} = conn, _opts) do conn |> assign(:email_changeset, Accounts.change_user_email(user)) |> assign(:password_changeset, Accounts.change_user_password(user)) + |> assign(:locale_changeset, Accounts.change_user_locale(user)) end end diff --git a/lib/lokal_web/live/init_assigns.ex b/lib/lokal_web/live/init_assigns.ex new file mode 100644 index 00000000..7fb4dbd9 --- /dev/null +++ b/lib/lokal_web/live/init_assigns.ex @@ -0,0 +1,19 @@ +defmodule LokalWeb.InitAssigns do + @moduledoc """ + Ensures common `assigns` are applied to all LiveViews attaching this hook. + """ + import Phoenix.LiveView + alias Lokal.Accounts + + def on_mount(:default, _params, %{"locale" => locale, "user_token" => user_token}, socket) do + Gettext.put_locale(locale) + + socket = + socket + |> assign_new(:current_user, fn -> Accounts.get_user_by_session_token(user_token) end) + + {:cont, socket} + end + + def on_mount(:default, _params, _session, socket), do: {:cont, socket} +end diff --git a/lib/lokal_web/live/invite_live/index.ex b/lib/lokal_web/live/invite_live/index.ex index 80aeb9ad..e0a921ba 100644 --- a/lib/lokal_web/live/invite_live/index.ex +++ b/lib/lokal_web/live/invite_live/index.ex @@ -10,9 +10,7 @@ defmodule LokalWeb.InviteLive.Index do alias Phoenix.LiveView.JS @impl true - def mount(_params, session, socket) do - %{assigns: %{current_user: current_user}} = socket = socket |> assign_defaults(session) - + def mount(_params, _session, %{assigns: %{current_user: current_user}} = socket) do socket = if current_user |> Map.get(:role) == :admin do socket |> display_invites() diff --git a/lib/lokal_web/live/live_helpers.ex b/lib/lokal_web/live/live_helpers.ex index a851eea8..113911fc 100644 --- a/lib/lokal_web/live/live_helpers.ex +++ b/lib/lokal_web/live/live_helpers.ex @@ -4,21 +4,8 @@ defmodule LokalWeb.LiveHelpers do """ import Phoenix.LiveView.Helpers - import Phoenix.LiveView - alias Lokal.Accounts alias Phoenix.LiveView.JS - def assign_defaults(socket, %{"user_token" => user_token} = _session) do - socket - |> assign_new(:current_user, fn -> - Accounts.get_user_by_session_token(user_token) - end) - end - - def assign_defaults(socket, _session) do - socket - end - @doc """ Renders a live component inside a modal. diff --git a/lib/lokal_web/live/page_live.ex b/lib/lokal_web/live/page_live.ex index b697d8d1..b3499af2 100644 --- a/lib/lokal_web/live/page_live.ex +++ b/lib/lokal_web/live/page_live.ex @@ -6,13 +6,8 @@ defmodule LokalWeb.PageLive do use LokalWeb, :live_view @impl true - def mount(_params, session, socket) do - socket = - socket - |> assign_defaults(session) - |> assign(page_title: gettext("Home"), query: "", results: %{}) - - {:ok, socket} + def mount(_params, _session, socket) do + {:ok, socket |> assign(page_title: gettext("Home"), query: "", results: %{})} end @impl true diff --git a/lib/lokal_web/router.ex b/lib/lokal_web/router.ex index 76e5e5c5..41321792 100644 --- a/lib/lokal_web/router.ex +++ b/lib/lokal_web/router.ex @@ -11,6 +11,17 @@ defmodule LokalWeb.Router do plug :protect_from_forgery plug :put_secure_browser_headers plug :fetch_current_user + plug :put_user_locale, default: Application.get_env(:gettext, :default_locale, "en_US") + end + + defp put_user_locale(%{assigns: %{current_user: %{locale: locale}}} = conn, default: default) do + Gettext.put_locale(locale || default) + conn |> put_session(:locale, locale || default) + end + + defp put_user_locale(conn, default: default) do + Gettext.put_locale(default) + conn |> put_session(:locale, default) end pipeline :require_admin do diff --git a/lib/lokal_web/templates/user_registration/new.html.heex b/lib/lokal_web/templates/user_registration/new.html.heex index ddc9cb81..b113baa8 100644 --- a/lib/lokal_web/templates/user_registration/new.html.heex +++ b/lib/lokal_web/templates/user_registration/new.html.heex @@ -30,6 +30,15 @@ <%= password_input(f, :password, required: true, class: "input input-primary col-span-2") %> <%= error_tag(f, :password, "col-span-3") %> + <%= label(f, :locale, gettext("Language"), class: "title text-lg text-primary-600") %> + <%= select( + f, + :locale, + [{gettext("English"), "en_US"}], + class: "input input-primary col-span-2" + ) %> + <%= error_tag(f, :locale) %> + <%= submit(dgettext("actions", "Register"), class: "mx-auto btn btn-primary col-span-3") %> <% end %> diff --git a/lib/lokal_web/templates/user_settings/edit.html.heex b/lib/lokal_web/templates/user_settings/edit.html.heex index c0f500db..f8b8d3b1 100644 --- a/lib/lokal_web/templates/user_settings/edit.html.heex +++ b/lib/lokal_web/templates/user_settings/edit.html.heex @@ -1,17 +1,18 @@ -
+ <%= dgettext("errors", "Oops, something went wrong! Please check the errors below.") %> +
+