fix: translation of login page
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon 2026-03-13 14:11:54 +01:00
parent 086ecdcb1b
commit 99a8d64344
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
18 changed files with 487 additions and 200 deletions

View file

@ -76,6 +76,21 @@ For LiveViews that render an edit or new form (e.g. member, group, role, user, c
If the `<.header>` is outside the `<.form>`, the submit button must reference the form via the `form` attribute (e.g. `form="user-form"`). If the `<.header>` is outside the `<.form>`, the submit button must reference the form via the `form` attribute (e.g. `form="user-form"`).
### 2.3 Public / unauthenticated pages (Join, Sign-in, Join Confirm)
Pages that do not require authentication (e.g. `/join`, `/sign-in`, `/confirm_join/:token`) use a unified layout via the **`Layouts.public_page`** component:
- **Component:** `Layouts.public_page` renders:
- **Header:** Logo + "Mitgliederverwaltung" (left) | Club name centered via absolute positioning | Language selector (right)
- Main content slot, Flash group. No sidebar, no authenticated-layout logic.
- **Content:** DaisyUI **hero** section (`hero`, `hero-content`) for the main message or form, so all public pages share the same visual structure. The hero is constrained in width (`max-w-4xl mx-auto`) and content is left-aligned (`hero-content flex-col items-start text-left`).
- **Locale handling:** The language selector uses `Gettext.get_locale(MvWeb.Gettext)` (backend-specific) to correctly reflect the active locale. `SignInLive` sets both `Gettext.put_locale(MvWeb.Gettext, locale)` and `Gettext.put_locale(locale)` to keep global and backend locales in sync.
- **Translations for AshAuthentication components:** AshAuthentications `_gettext` mechanism translates button labels (e.g. “Sign in” → “Anmelden”, “Register” → “Registrieren”) at runtime via `gettext_fn: {MvWeb.Gettext, "auth"}`. Components that do NOT use `_gettext` (e.g. `HorizontalRule`) receive static German overrides via **`MvWeb.AuthOverridesDE`**, which is prepended to the overrides list in `SignInLive` when the locale is `"de"`.
- **Implementation:**
- **Sign-in** (`SignInLive`): Uses `use Phoenix.LiveView` (not `use MvWeb, :live_view`) so AshAuthentications sign_in_route live_session on_mount chain is not mixed with LiveHelpers hooks. Renders `<Layouts.public_page flash={@flash}>` with the SignIn component inside a hero. Displays a locale-aware `<h1>` title (“Anmelden” / “Registrieren”) above the AshAuthentication component (the librarys Banner is hidden via `show_banner: false`).
- **Join** (`JoinLive`): Uses `use MvWeb, :live_view` and wraps content in `<Layouts.public_page flash={@flash}>` with a hero for the form.
- **Join Confirm** (controller): Uses `JoinConfirmHTML` with a template that repeats the same header markup and a hero block for the result (no component call from controller templates).
## 3) Typography (system) ## 3) Typography (system)
Use these standard roles: Use these standard roles:

View file

@ -10,6 +10,7 @@ install-dependencies:
mix deps.get mix deps.get
migrate-database: migrate-database:
mix compile
mix ash.setup mix ash.setup
reset-database: reset-database:

View file

@ -36,10 +36,10 @@
**Closed Issues:** **Closed Issues:**
- ✅ [#171](https://git.local-it.org/local-it/mitgliederverwaltung/issues/171) - OIDC handling and linking (closed 2025-11-13) - ✅ [#171](https://git.local-it.org/local-it/mitgliederverwaltung/issues/171) - OIDC handling and linking (closed 2025-11-13)
- ✅ [#146](https://git.local-it.org/local-it/mitgliederverwaltung/issues/146) - Translate "or" in the login screen — fixed via `MvWeb.AuthOverridesDE` locale-specific module (2026-03-13)
- ✅ [#144](https://git.local-it.org/local-it/mitgliederverwaltung/issues/144) - Add language switch dropdown to login screen — fixed locale selector bug with `Gettext.get_locale(MvWeb.Gettext)` (2026-03-13)
**Open Issues:** **Open Issues:** (none remaining for Authentication UI)
- [#146](https://git.local-it.org/local-it/mitgliederverwaltung/issues/146) - Translate "or" in the login screen (Low)
- [#144](https://git.local-it.org/local-it/mitgliederverwaltung/issues/144) - Add language switch dropdown to login screen (Low)
**Current State:** **Current State:**
- ✅ **Role-based access control (RBAC)** - Implemented (2026-01-08, PR #346, closes #345) - ✅ **Role-based access control (RBAC)** - Implemented (2026-01-08, PR #346, closes #345)

View file

@ -3,52 +3,57 @@ defmodule MvWeb.AuthOverrides do
UI customizations for AshAuthentication Phoenix components. UI customizations for AshAuthentication Phoenix components.
## Overrides ## Overrides
- `SignIn` - Restricts form width to prevent full-width display - `SignIn` - Restricts form width and hides the library banner (title is rendered in SignInLive)
- `Banner` - Replaces default logo with "Mitgliederverwaltung" text - `Banner` - Replaces default logo with text for reset/confirm pages
- `HorizontalRule` - Translates "or" text to German - `Flash` - Hides library flash (we use flash_group in root layout)
## Documentation ## Documentation
For complete reference on available overrides, see: For complete reference on available overrides, see:
https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html
""" """
use AshAuthentication.Phoenix.Overrides use AshAuthentication.Phoenix.Overrides
use Gettext, backend: MvWeb.Gettext
# configure your UI overrides here # Avoid full-width for the Sign In Form.
# Banner is hidden because SignInLive renders its own locale-aware title.
# First argument to `override` is the component name you are overriding.
# The body contains any number of configurations you wish to override
# Below are some examples
# For a complete reference, see https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html
# override AshAuthentication.Phoenix.Components.Banner do
# set :image_url, "https://media.giphy.com/media/g7GKcSzwQfugw/giphy.gif"
# set :text_class, "bg-red-500"
# end
# Avoid full-width for the Sign In Form
override AshAuthentication.Phoenix.Components.SignIn do override AshAuthentication.Phoenix.Components.SignIn do
set :root_class, "md:min-w-md" set :root_class, "md:min-w-md"
set :show_banner, false
end end
# Replace banner logo with text (no image in light or dark so link has discernible text) # Replace banner logo with text for reset/confirm pages (no image so link has discernible text).
override AshAuthentication.Phoenix.Components.Banner do override AshAuthentication.Phoenix.Components.Banner do
set :text, "Mitgliederverwaltung" set :text, "Mitgliederverwaltung"
set :image_url, nil set :image_url, nil
set :dark_image_url, nil set :dark_image_url, nil
end end
# Translate the "or" in the horizontal rule (between password form and SSO). # Hide AshAuthentication's Flash component since we use flash_group in root layout.
# Uses auth domain so it respects the current locale (e.g. "oder" in German). # This prevents duplicate flash messages.
override AshAuthentication.Phoenix.Components.HorizontalRule do
set :text, dgettext("auth", "or")
end
# Hide AshAuthentication's Flash component since we use flash_group in root layout
# This prevents duplicate flash messages
override AshAuthentication.Phoenix.Components.Flash do override AshAuthentication.Phoenix.Components.Flash do
set :message_class_info, "hidden" set :message_class_info, "hidden"
set :message_class_error, "hidden" set :message_class_error, "hidden"
end end
end end
defmodule MvWeb.AuthOverridesDE do
@moduledoc """
German locale-specific overrides for AshAuthentication Phoenix components.
Prepended to the overrides list in SignInLive when the locale is "de".
Provides runtime-static German text for components that do not use
the `_gettext` mechanism (e.g. HorizontalRule renders its text directly),
and for submit buttons whose disable_text bypasses the POT extraction pipeline.
"""
use AshAuthentication.Phoenix.Overrides
# HorizontalRule renders text without `_gettext`, so we need a static German string.
override AshAuthentication.Phoenix.Components.HorizontalRule do
set :text, "oder"
end
# Registering ... disable-text is passed through _gettext but "Registering ..."
# has no dgettext source reference, so we supply the German string directly.
override AshAuthentication.Phoenix.Components.Password.RegisterForm do
set :disable_button_text, "Registrieren..."
end
end

View file

@ -13,6 +13,54 @@ defmodule MvWeb.Layouts do
embed_templates "layouts/*" embed_templates "layouts/*"
@doc """
Renders the public (unauthenticated) page layout: header with logo + "Mitgliederverwaltung" left,
club name centered, language selector right; plus main content and flash group. Use for sign-in, join, and join-confirm pages so they
share the same chrome without the sidebar or authenticated layout logic.
"""
attr :flash, :map, required: true, doc: "the map of flash messages"
slot :inner_block, required: true
def public_page(assigns) do
club_name =
case Mv.Membership.get_settings() do
{:ok, s} -> s.club_name || "Mitgliederverwaltung"
_ -> "Mitgliederverwaltung"
end
assigns = assign(assigns, :club_name, club_name)
~H"""
<header class="relative flex items-center justify-between p-4 border-b border-base-300 bg-base-100">
<div class="flex items-center gap-3 shrink-0 min-w-0 max-w-[45%]">
<img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" />
<span class="text-lg font-bold truncate">Mitgliederverwaltung</span>
</div>
<span class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-center max-w-[50%] truncate">
{@club_name}
</span>
<form method="post" action={~p"/set_locale"} class="shrink-0">
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
<select
name="locale"
onchange="this.form.submit()"
class="select select-sm focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label={gettext("Select language")}
>
<option value="de" selected={Gettext.get_locale(MvWeb.Gettext) == "de"}>Deutsch</option>
<option value="en" selected={Gettext.get_locale(MvWeb.Gettext) == "en"}>English</option>
</select>
</form>
</header>
<main class="px-4 py-8 sm:px-6">
<div class="mx-auto max-full space-y-4">
{render_slot(@inner_block)}
</div>
</main>
<.flash_group flash={@flash} />
"""
end
@doc """ @doc """
Renders the app layout. Can be used with or without a current_user. Renders the app layout. Can be used with or without a current_user.
When current_user is present, it will show the navigation bar. When current_user is present, it will show the navigation bar.
@ -99,10 +147,13 @@ defmodule MvWeb.Layouts do
</div> </div>
</div> </div>
<% else %> <% else %>
<!-- Unauthenticated: simple header (logo, club name, language selector; same classes as sidebar header) --> <!-- Unauthenticated: Option 3 header (logo + app name left, club name center, language selector right) -->
<header class="flex items-center gap-3 p-4 border-b border-base-300 bg-base-100"> <header class="relative flex items-center justify-between p-4 border-b border-base-300 bg-base-100">
<div class="flex items-center gap-3 shrink-0 min-w-0 max-w-[45%]">
<img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" /> <img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" />
<span class="menu-label text-lg font-bold truncate flex-1"> <span class="menu-label text-lg font-bold truncate">Mitgliederverwaltung</span>
</div>
<span class="absolute left-1/2 -translate-x-1/2 text-lg font-bold text-center max-w-[50%] truncate">
{@club_name} {@club_name}
</span> </span>
<form method="post" action={~p"/set_locale"} class="shrink-0"> <form method="post" action={~p"/set_locale"} class="shrink-0">
@ -113,8 +164,8 @@ defmodule MvWeb.Layouts do
class="select select-sm focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" class="select select-sm focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label={gettext("Select language")} aria-label={gettext("Select language")}
> >
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option> <option value="de" selected={Gettext.get_locale(MvWeb.Gettext) == "de"}>Deutsch</option>
<option value="en" selected={Gettext.get_locale() == "en"}>English</option> <option value="en" selected={Gettext.get_locale(MvWeb.Gettext) == "en"}>English</option>
</select> </select>
</form> </form>
</header> </header>

View file

@ -260,8 +260,8 @@ defmodule MvWeb.Layouts.Sidebar do
class="select select-sm w-full focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" class="select select-sm w-full focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label={gettext("Select language")} aria-label={gettext("Select language")}
> >
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option> <option value="de" selected={Gettext.get_locale(MvWeb.Gettext) == "de"}>Deutsch</option>
<option value="en" selected={Gettext.get_locale() == "en"}>English</option> <option value="en" selected={Gettext.get_locale(MvWeb.Gettext) == "en"}>English</option>
</select> </select>
</form> </form>
<!-- Theme Toggle (immer sichtbar) --> <!-- Theme Toggle (immer sichtbar) -->

View file

@ -2,8 +2,9 @@ defmodule MvWeb.JoinConfirmController do
@moduledoc """ @moduledoc """
Handles GET /confirm_join/:token for the public join flow (double opt-in). Handles GET /confirm_join/:token for the public join flow (double opt-in).
Calls a configurable callback (default Mv.Membership) so tests can stub the Renders a full HTML page with public header and hero layout (success, expired,
dependency. Public route; no authentication required. or invalid). Calls a configurable callback (default Mv.Membership) so tests can
stub the dependency. Public route; no authentication required.
""" """
use MvWeb, :controller use MvWeb, :controller
@ -26,20 +27,36 @@ defmodule MvWeb.JoinConfirmController do
defp success_response(conn) do defp success_response(conn) do
conn conn
|> put_resp_content_type("text/html") |> assign_confirm_assigns(:success)
|> send_resp(200, gettext("Thank you, we have received your request.")) |> put_view(MvWeb.JoinConfirmHTML)
|> render("confirm.html")
end end
defp expired_response(conn) do defp expired_response(conn) do
conn conn
|> put_resp_content_type("text/html") |> assign_confirm_assigns(:expired)
|> send_resp(200, gettext("This link has expired. Please submit the form again.")) |> put_view(MvWeb.JoinConfirmHTML)
|> render("confirm.html")
end end
defp invalid_response(conn) do defp invalid_response(conn) do
conn conn
|> put_resp_content_type("text/html")
|> put_status(404) |> put_status(404)
|> send_resp(404, gettext("Invalid or expired link.")) |> assign_confirm_assigns(:invalid)
|> put_view(MvWeb.JoinConfirmHTML)
|> render("confirm.html")
end
defp assign_confirm_assigns(conn, result) do
club_name =
case Mv.Membership.get_settings() do
{:ok, settings} -> settings.club_name || "Mitgliederverwaltung"
_ -> "Mitgliederverwaltung"
end
conn
|> assign(:result, result)
|> assign(:club_name, club_name)
|> assign(:csrf_token, Plug.CSRFProtection.get_csrf_token())
end end
end end

View file

@ -0,0 +1,9 @@
defmodule MvWeb.JoinConfirmHTML do
@moduledoc """
Renders join confirmation result pages (success, expired, invalid) with
public header and hero layout. Used by JoinConfirmController.
"""
use MvWeb, :html
embed_templates "join_confirm_html/*"
end

View file

@ -0,0 +1,65 @@
<%!-- Public header (same structure as Layouts.app unauthenticated branch) --%>
<header class="flex items-center gap-3 p-4 border-b border-base-300 bg-base-100">
<img src={~p"/images/mila.svg"} alt="Mila Logo" class="size-8 shrink-0" />
<span class="menu-label text-lg font-bold truncate flex-1">
{@club_name}
</span>
<form method="post" action={~p"/set_locale"} class="shrink-0">
<input type="hidden" name="_csrf_token" value={@csrf_token} />
<select
name="locale"
onchange="this.form.submit()"
class="select select-sm focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label={gettext("Select language")}
>
<option value="de" selected={Gettext.get_locale() == "de"}>Deutsch</option>
<option value="en" selected={Gettext.get_locale() == "en"}>English</option>
</select>
</form>
</header>
<main class="px-4 py-8 sm:px-6">
<div class="max-w-4xl mx-auto">
<div class="hero min-h-[60vh] bg-base-200 rounded-lg">
<div class="hero-content flex-col items-start text-left">
<div class="max-w-md">
<%= case @result do %>
<% :success -> %>
<h1 class="text-3xl font-bold">
{gettext("Thank you")}
</h1>
<p class="py-4 text-base-content/80">
{gettext("Thank you, we have received your request.")}
</p>
<p class="text-sm text-base-content/70">
{gettext("You will receive an email once your application has been reviewed.")}
</p>
<a href={~p"/join"} class="btn btn-primary mt-4">
{gettext("Back to join form")}
</a>
<% :expired -> %>
<h1 class="text-3xl font-bold">
{gettext("Link expired")}
</h1>
<p class="py-4 text-base-content/80">
{gettext("This link has expired. Please submit the form again.")}
</p>
<a href={~p"/join"} class="btn btn-primary mt-4">
{gettext("Submit new request")}
</a>
<% :invalid -> %>
<h1 class="text-3xl font-bold text-error">
{gettext("Invalid or expired link")}
</h1>
<p class="py-4 text-base-content/80">
{gettext("Invalid or expired link.")}
</p>
<a href={~p"/join"} class="btn btn-primary mt-4">
{gettext("Go to join form")}
</a>
<% end %>
</div>
</div>
</div>
</div>
</main>

View file

@ -1,28 +1,42 @@
defmodule MvWeb.SignInLive do defmodule MvWeb.SignInLive do
@moduledoc """ @moduledoc """
Custom sign-in page with language selector and conditional Single Sign-On button. Custom sign-in page with public header and hero layout (same as Join/Join Confirm).
- Renders a language selector (same pattern as LinkOidcAccountLive). Uses Layouts.public_page (no sidebar, no app-layout hooks). Wraps the AshAuthentication
- Wraps the default AshAuthentication SignIn component in a container with SignIn component in a hero section. Container has data-oidc-configured so CSS can hide
`data-oidc-configured` so that CSS can hide the SSO button when OIDC is not configured. the SSO button when OIDC is not configured.
Keeps `use Phoenix.LiveView` (not MvWeb :live_view) so AshAuthentication's sign_in_route
live_session on_mount chain is not mixed with LiveHelpers hooks.
## Locale overrides
`MvWeb.AuthOverridesDE` is prepended to the overrides list when the locale is "de",
providing static German strings for components that do not use `_gettext` internally
(e.g. HorizontalRule renders its `:text` override directly).
""" """
use Phoenix.LiveView use Phoenix.LiveView
use Gettext, backend: MvWeb.Gettext use Gettext, backend: MvWeb.Gettext
alias AshAuthentication.Phoenix.Components alias AshAuthentication.Phoenix.Components
alias Mv.Config alias Mv.Config
alias MvWeb.{AuthOverridesDE, Layouts}
@impl true @impl true
def mount(_params, session, socket) do def mount(_params, session, socket) do
overrides =
session
|> Map.get("overrides", [AshAuthentication.Phoenix.Overrides.Default])
# Locale: same fallback as LiveUserAuth so config :default_locale (e.g. "en" in test) is respected # Locale: same fallback as LiveUserAuth so config :default_locale (e.g. "en" in test) is respected
locale = locale = session["locale"] || Application.get_env(:mv, :default_locale, "de")
session["locale"] || Application.get_env(:mv, :default_locale, "de")
# Set both backend-specific and global locale so Gettext.get_locale/0 and
# Gettext.get_locale/1 both return the correct value (important for the
# language-selector `selected` attribute in Layouts.public_page).
Gettext.put_locale(MvWeb.Gettext, locale) Gettext.put_locale(MvWeb.Gettext, locale)
Gettext.put_locale(locale)
# Prepend DE-specific overrides when locale is German so that components
# without _gettext support (e.g. HorizontalRule) still render in German.
base_overrides = Map.get(session, "overrides", [AshAuthentication.Phoenix.Overrides.Default])
locale_overrides = if locale == "de", do: [AuthOverridesDE], else: []
overrides = locale_overrides ++ base_overrides
socket = socket =
socket socket
@ -36,10 +50,9 @@ defmodule MvWeb.SignInLive do
|> assign(:context, session["context"] || %{}) |> assign(:context, session["context"] || %{})
|> assign(:auth_routes_prefix, session["auth_routes_prefix"]) |> assign(:auth_routes_prefix, session["auth_routes_prefix"])
|> assign(:gettext_fn, session["gettext_fn"]) |> assign(:gettext_fn, session["gettext_fn"])
|> assign(:live_action, :sign_in) |> assign_new(:live_action, fn -> :sign_in end)
|> assign(:oidc_configured, Config.oidc_configured?()) |> assign(:oidc_configured, Config.oidc_configured?())
|> assign(:oidc_only, Config.oidc_only?()) |> assign(:oidc_only, Config.oidc_only?())
|> assign(:root_class, "grid h-screen place-items-center bg-base-100")
|> assign(:sign_in_id, "sign-in") |> assign(:sign_in_id, "sign-in")
|> assign(:locale, locale) |> assign(:locale, locale)
@ -54,34 +67,23 @@ defmodule MvWeb.SignInLive do
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<main <Layouts.public_page flash={@flash}>
<div class="max-w-4xl mx-auto">
<div
class="hero min-h-[60vh] bg-base-200 rounded-lg"
id="sign-in-page" id="sign-in-page"
role="main" role="main"
class={@root_class}
data-oidc-configured={to_string(@oidc_configured)} data-oidc-configured={to_string(@oidc_configured)}
data-oidc-only={to_string(@oidc_only)} data-oidc-only={to_string(@oidc_only)}
data-locale={@locale} data-locale={@locale}
> >
<h1 class="sr-only">{dgettext("auth", "Sign in")}</h1> <div class="hero-content flex-col items-start text-left">
<%!-- Language selector --%> <div class="w-full max-w-md">
<nav <h1 class="text-xl font-semibold leading-8">
aria-label={dgettext("auth", "Language selection")} {if @live_action == :register,
class="absolute top-4 right-4 flex justify-end z-10" do: dgettext("auth", "Register"),
> else: dgettext("auth", "Sign in")}
<form method="post" action="/set_locale" class="text-sm"> </h1>
<input type="hidden" name="_csrf_token" value={Plug.CSRFProtection.get_csrf_token()} />
<select
name="locale"
onchange="this.form.submit()"
class="select select-sm select-bordered bg-base-100"
aria-label={dgettext("auth", "Select language")}
>
<option value="de" selected={@locale == "de"}>Deutsch</option>
<option value="en" selected={@locale == "en"}>English</option>
</select>
</form>
</nav>
<.live_component <.live_component
module={Components.SignIn} module={Components.SignIn}
otp_app={@otp_app} otp_app={@otp_app}
@ -97,7 +99,11 @@ defmodule MvWeb.SignInLive do
context={@context} context={@context}
gettext_fn={@gettext_fn} gettext_fn={@gettext_fn}
/> />
</main> </div>
</div>
</div>
</div>
</Layouts.public_page>
""" """
end end
end end

View file

@ -33,8 +33,11 @@ defmodule MvWeb.JoinLive do
@impl true @impl true
def render(assigns) do def render(assigns) do
~H""" ~H"""
<Layouts.app flash={@flash} current_user={@current_user}> <Layouts.public_page flash={@flash}>
<div class="max-w-xl mx-auto mt-8 space-y-6"> <div class="max-w-4xl mx-auto">
<div class="hero min-h-[60vh] bg-base-200 rounded-lg">
<div class="hero-content flex-col items-start text-left">
<div class="max-w-xl w-full space-y-6">
<.header> <.header>
{gettext("Become a member")} {gettext("Become a member")}
</.header> </.header>
@ -117,7 +120,10 @@ defmodule MvWeb.JoinLive do
</.form> </.form>
<% end %> <% end %>
</div> </div>
</Layouts.app> </div>
</div>
</div>
</Layouts.public_page>
""" """
end end

View file

@ -139,18 +139,16 @@ msgid "This OIDC account is already linked to another user. Please contact suppo
msgstr "" msgstr ""
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Language selection" msgid "Language selection"
msgstr "" msgstr ""
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select language" msgid "Select language"
msgstr "" msgstr ""
#: lib/mv_web/auth_overrides.ex #: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "or" msgid "Register"
msgstr "" msgstr ""

View file

@ -135,18 +135,16 @@ msgid "This OIDC account is already linked to another user. Please contact suppo
msgstr "Dieses OIDC-Konto ist bereits mit einer*m anderen Benutzer*in verknüpft. Bitte kontaktiere den Support." msgstr "Dieses OIDC-Konto ist bereits mit einer*m anderen Benutzer*in verknüpft. Bitte kontaktiere den Support."
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Language selection" msgid "Language selection"
msgstr "Sprachauswahl" msgstr "Sprachauswahl"
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select language" msgid "Select language"
msgstr "Sprache auswählen" msgstr "Sprache auswählen"
#: lib/mv_web/auth_overrides.ex #: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "or" msgid "Register"
msgstr "oder" msgstr "Registrieren"

View file

@ -1688,7 +1688,7 @@ msgstr "Ungültiges Datumsformat"
msgid "Invalid email address. Please enter a valid recipient address." msgid "Invalid email address. Please enter a valid recipient address."
msgstr "Ungültige E-Mail-Adresse. Bitte gib eine gültige Empfängeradresse ein." msgstr "Ungültige E-Mail-Adresse. Bitte gib eine gültige Empfängeradresse ein."
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid or expired link." msgid "Invalid or expired link."
msgstr "Ungültiger oder abgelaufener Link." msgstr "Ungültiger oder abgelaufener Link."
@ -2897,6 +2897,7 @@ msgstr "Intervall auswählen"
#: lib/mv_web/components/layouts.ex #: lib/mv_web/components/layouts.ex
#: lib/mv_web/components/layouts/sidebar.ex #: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select language" msgid "Select language"
msgstr "Sprache auswählen" msgstr "Sprache auswählen"
@ -3197,7 +3198,7 @@ msgstr "Wird getestet..."
msgid "Text" msgid "Text"
msgstr "Textfeld" msgstr "Textfeld"
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Thank you, we have received your request." msgid "Thank you, we have received your request."
msgstr "Vielen Dank, wir haben deine Anfrage erhalten." msgstr "Vielen Dank, wir haben deine Anfrage erhalten."
@ -3270,7 +3271,7 @@ msgstr "Dies ist ein technisches Feld und kann nicht verändert werden."
msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly." msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly."
msgstr "Dies ist eine Test-E-Mail von Mila. Wenn du diese erhalten hast, funktioniert deine SMTP-Konfiguration korrekt." msgstr "Dies ist eine Test-E-Mail von Mila. Wenn du diese erhalten hast, funktioniert deine SMTP-Konfiguration korrekt."
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This link has expired. Please submit the form again." msgid "This link has expired. Please submit the form again."
msgstr "Dieser Link ist abgelaufen. Bitte sende das Formular erneut ab." msgstr "Dieser Link ist abgelaufen. Bitte sende das Formular erneut ab."
@ -3831,3 +3832,38 @@ msgstr "Wir haben deine Anfrage erhalten. Du hast bereits einen Mitgliedsantrag,
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You already had a pending request. Here is a new confirmation link." msgid "You already had a pending request. Here is a new confirmation link."
msgstr "Du hattest bereits einen offenen Antrag. Hier ist ein neuer Bestätigungslink." msgstr "Du hattest bereits einen offenen Antrag. Hier ist ein neuer Bestätigungslink."
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Back to join form"
msgstr "Zurück zu den Mitgliedsanträgen"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Go to join form"
msgstr "Zum Antragsformular"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Invalid or expired link"
msgstr "Ungültiger oder abgelaufener Link."
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Link expired"
msgstr "Link abgelaufen"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Submit new request"
msgstr "Antrag absenden"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Thank you"
msgstr "Vielen Dank"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "You will receive an email once your application has been reviewed."
msgstr "Du erhältst eine E-Mail, sobald dein Antrag geprüft wurde."

View file

@ -1689,7 +1689,7 @@ msgstr ""
msgid "Invalid email address. Please enter a valid recipient address." msgid "Invalid email address. Please enter a valid recipient address."
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid or expired link." msgid "Invalid or expired link."
msgstr "" msgstr ""
@ -2898,6 +2898,7 @@ msgstr ""
#: lib/mv_web/components/layouts.ex #: lib/mv_web/components/layouts.ex
#: lib/mv_web/components/layouts/sidebar.ex #: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select language" msgid "Select language"
msgstr "" msgstr ""
@ -3198,7 +3199,7 @@ msgstr ""
msgid "Text" msgid "Text"
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Thank you, we have received your request." msgid "Thank you, we have received your request."
msgstr "" msgstr ""
@ -3271,7 +3272,7 @@ msgstr ""
msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly." msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly."
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This link has expired. Please submit the form again." msgid "This link has expired. Please submit the form again."
msgstr "" msgstr ""
@ -3831,3 +3832,38 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You already had a pending request. Here is a new confirmation link." msgid "You already had a pending request. Here is a new confirmation link."
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Back to join form"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Go to join form"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Invalid or expired link"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Link expired"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Submit new request"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Thank you"
msgstr ""
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "You will receive an email once your application has been reviewed."
msgstr ""

View file

@ -132,18 +132,16 @@ msgid "This OIDC account is already linked to another user. Please contact suppo
msgstr "" msgstr ""
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Language selection" msgid "Language selection"
msgstr "" msgstr ""
#: lib/mv_web/live/auth/link_oidc_account_live.ex #: lib/mv_web/live/auth/link_oidc_account_live.ex
#: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Select language" msgid "Select language"
msgstr "" msgstr ""
#: lib/mv_web/auth_overrides.ex #: lib/mv_web/live/auth/sign_in_live.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "or" msgid "Register"
msgstr "or" msgstr ""

View file

@ -1689,7 +1689,7 @@ msgstr ""
msgid "Invalid email address. Please enter a valid recipient address." msgid "Invalid email address. Please enter a valid recipient address."
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Invalid or expired link." msgid "Invalid or expired link."
msgstr "Invalid or expired link." msgstr "Invalid or expired link."
@ -2898,6 +2898,7 @@ msgstr ""
#: lib/mv_web/components/layouts.ex #: lib/mv_web/components/layouts.ex
#: lib/mv_web/components/layouts/sidebar.ex #: lib/mv_web/components/layouts/sidebar.ex
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Select language" msgid "Select language"
msgstr "" msgstr ""
@ -3198,7 +3199,7 @@ msgstr ""
msgid "Text" msgid "Text"
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Thank you, we have received your request." msgid "Thank you, we have received your request."
msgstr "Thank you, we have received your request." msgstr "Thank you, we have received your request."
@ -3271,7 +3272,7 @@ msgstr ""
msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly." msgid "This is a test email sent from Mila. If you received this, your SMTP configuration is working correctly."
msgstr "" msgstr ""
#: lib/mv_web/controllers/join_confirm_controller.ex #: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This link has expired. Please submit the form again." msgid "This link has expired. Please submit the form again."
msgstr "This link has expired. Please submit the form again." msgstr "This link has expired. Please submit the form again."
@ -3831,3 +3832,38 @@ msgstr "We have received your request. You already have a membership application
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You already had a pending request. Here is a new confirmation link." msgid "You already had a pending request. Here is a new confirmation link."
msgstr "You already had a pending request. Here is a new confirmation link." msgstr "You already had a pending request. Here is a new confirmation link."
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Back to join form"
msgstr "Back to membership applications"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Go to join form"
msgstr "Go to join form"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Invalid or expired link"
msgstr "Invalid or expired link."
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Link expired"
msgstr "Link expired"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Submit new request"
msgstr "Submit new request"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "Thank you"
msgstr "Thank you"
#: lib/mv_web/controllers/join_confirm_html/confirm.html.heex
#, elixir-autogen, elixir-format
msgid "You will receive an email once your application has been reviewed."
msgstr "You will receive an email once your application has been reviewed."

View file

@ -28,6 +28,16 @@ defmodule MvWeb.AuthControllerTest do
assert html_response(conn, 200) =~ "Sign in" assert html_response(conn, 200) =~ "Sign in"
end end
@tag role: :unauthenticated
test "GET /sign-in returns 200 and renders page (exercises AuthOverrides and layout)", %{
conn: conn
} do
{:ok, _view, html} = live(conn, ~p"/sign-in")
assert html =~ "Sign in"
# Public header (logo) from Layouts.app unauthenticated branch
assert html =~ "mila.svg" or html =~ "Mila Logo"
end
test "GET /sign-out redirects to home", %{conn: authenticated_conn} do test "GET /sign-out redirects to home", %{conn: authenticated_conn} do
conn = conn_with_oidc_user(authenticated_conn) conn = conn_with_oidc_user(authenticated_conn)
conn = get(conn, ~p"/sign-out") conn = get(conn, ~p"/sign-out")