diff --git a/Justfile b/Justfile
index 4480991..11f694e 100644
--- a/Justfile
+++ b/Justfile
@@ -19,6 +19,10 @@ start-database:
ci-dev: lint audit test
+gettext:
+ mix gettext.extract
+ mix gettext.merge priv/gettext
+
lint:
mix format --check-formatted
mix compile --warnings-as-errors
diff --git a/lib/mv_web.ex b/lib/mv_web.ex
index de7184d..4254449 100644
--- a/lib/mv_web.ex
+++ b/lib/mv_web.ex
@@ -55,6 +55,7 @@ defmodule MvWeb do
use Phoenix.LiveView,
layout: {MvWeb.Layouts, :app}
+ on_mount MvWeb.LiveHelpers
unquote(html_helpers())
end
end
diff --git a/lib/mv_web/components/layouts/app.html.heex b/lib/mv_web/components/layouts/app.html.heex
index 3b3b607..54258db 100644
--- a/lib/mv_web/components/layouts/app.html.heex
+++ b/lib/mv_web/components/layouts/app.html.heex
@@ -9,6 +9,13 @@
+
@elixirphoenix
diff --git a/lib/mv_web/live_helpers.ex b/lib/mv_web/live_helpers.ex
new file mode 100644
index 0000000..1a84aae
--- /dev/null
+++ b/lib/mv_web/live_helpers.ex
@@ -0,0 +1,9 @@
+defmodule MvWeb.LiveHelpers do
+ import Phoenix.LiveView
+
+ def on_mount(:default, _params, session, socket) do
+ locale = session["locale"] || "en"
+ Gettext.put_locale(locale)
+ {:cont, socket}
+ end
+end
diff --git a/lib/mv_web/locale_controller.ex b/lib/mv_web/locale_controller.ex
new file mode 100644
index 0000000..41b6ff7
--- /dev/null
+++ b/lib/mv_web/locale_controller.ex
@@ -0,0 +1,18 @@
+defmodule MvWeb.LocaleController do
+ use MvWeb, :controller
+
+ def set_locale(conn, %{"locale" => locale}) do
+ conn
+ |> put_session(:locale, locale)
+ |> redirect(to: get_referer(conn) || "/")
+ end
+
+ defp get_referer(conn) do
+ conn.req_headers
+ |> Enum.find(fn {k, _v} -> k == "referer" end)
+ |> case do
+ {_, v} -> URI.parse(v).path
+ _ -> nil
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/mv_web/member_live/form_component.ex b/lib/mv_web/member_live/form_component.ex
index 0f06cfa..e7c58c7 100644
--- a/lib/mv_web/member_live/form_component.ex
+++ b/lib/mv_web/member_live/form_component.ex
@@ -26,7 +26,7 @@ defmodule MvWeb.MemberLive.FormComponent do
<.header>
{@title}
- <:subtitle>Use this form to manage member records and their properties.
+ <:subtitle>{gettext("Use this form to manage member records and their properties.")}
<.simple_form
@@ -36,21 +36,21 @@ defmodule MvWeb.MemberLive.FormComponent do
phx-change="validate"
phx-submit="save"
>
- <.input field={@form[:first_name]} label="First Name" required />
- <.input field={@form[:last_name]} label="Last Name" required />
- <.input field={@form[:email]} label="Email" required type="email" />
- <.input field={@form[:birth_date]} label="Birth Date" type="date" />
- <.input field={@form[:paid]} label="Paid" type="checkbox" />
- <.input field={@form[:phone_number]} label="Phone Number" />
- <.input field={@form[:join_date]} label="Join Date" type="date" />
- <.input field={@form[:exit_date]} label="Exit Date" type="date" />
- <.input field={@form[:notes]} label="Notes" />
- <.input field={@form[:city]} label="City" />
- <.input field={@form[:street]} label="Street" />
- <.input field={@form[:house_number]} label="House Number" />
- <.input field={@form[:postal_code]} label="Postal Code" />
+ <.input field={@form[:first_name]} label={gettext("First Name")} required />
+ <.input field={@form[:last_name]} label={gettext("Last Name")} required />
+ <.input field={@form[:email]} label={gettext("Email")} required type="email" />
+ <.input field={@form[:birth_date]} label={gettext("Birth Date")} type="date" />
+ <.input field={@form[:paid]} label={gettext("Paid")} type="checkbox" />
+ <.input field={@form[:phone_number]} label={gettext("Phone Number")} />
+ <.input field={@form[:join_date]} label={gettext("Join Date")} type="date" />
+ <.input field={@form[:exit_date]} label={gettext("Exit Date")} type="date" />
+ <.input field={@form[:notes]} label={gettext("Notes")} />
+ <.input field={@form[:city]} label={gettext("City")} />
+ <.input field={@form[:street]} label={gettext("Street")} />
+ <.input field={@form[:house_number]} label={gettext("House Number")} />
+ <.input field={@form[:postal_code]} label={gettext("Postal Code")} />
-
Custom Properties
+ {gettext("Custom Properties")}
<.inputs_for :let={f_property} field={@form[:properties]}>
<% type = Enum.find(@property_types, &(&1.id == f_property[:property_type_id].value)) %>
<.inputs_for :let={value_form} field={f_property[:value]}>
@@ -70,7 +70,7 @@ defmodule MvWeb.MemberLive.FormComponent do
<:actions>
- <.button phx-disable-with="Saving...">Save Member
+ <.button phx-disable-with={gettext("Saving...")}>{gettext("Save Member")}
@@ -95,9 +95,16 @@ defmodule MvWeb.MemberLive.FormComponent do
{:ok, member} ->
notify_parent({:saved, member})
+ action =
+ case socket.assigns.form.source.type do
+ :create -> gettext("create")
+ :update -> gettext("update")
+ other -> to_string(other)
+ end
+
socket =
socket
- |> put_flash(:info, "Member #{socket.assigns.form.source.type}d successfully")
+ |> put_flash(:info, gettext("Mitglied %{action} erfolgreich", action: action))
|> push_patch(to: socket.assigns.patch)
{:noreply, socket}
diff --git a/lib/mv_web/member_live/index.ex b/lib/mv_web/member_live/index.ex
index 96c7a41..452ebab 100644
--- a/lib/mv_web/member_live/index.ex
+++ b/lib/mv_web/member_live/index.ex
@@ -5,10 +5,10 @@ defmodule MvWeb.MemberLive.Index do
def render(assigns) do
~H"""
<.header>
- Listing Members
+ {gettext("Listing Members")}
<:actions>
<.link patch={~p"/members/new"}>
- <.button>New Member
+ <.button>{gettext("New Member")}
@@ -19,26 +19,26 @@ defmodule MvWeb.MemberLive.Index do
row_click={fn {_id, member} -> JS.navigate(~p"/members/#{member}") end}
>
- <:col :let={{_id, member}} label="First Name">{member.first_name}
- <:col :let={{_id, member}} label="Last Name">{member.last_name}
- <:col :let={{_id, member}} label="Email">{member.email}
- <:col :let={{_id, member}} label="City">{member.city}
- <:col :let={{_id, member}} label="Join Date">{member.join_date}
+ <:col :let={{_id, member}} label={gettext("First Name")}>{member.first_name}
+ <:col :let={{_id, member}} label={gettext("Last Name")}>{member.last_name}
+ <:col :let={{_id, member}} label={gettext("Email")}>{member.email}
+ <:col :let={{_id, member}} label={gettext("City")}>{member.city}
+ <:col :let={{_id, member}} label={gettext("Join Date")}>{member.join_date}
<:action :let={{_id, member}}>
- <.link navigate={~p"/members/#{member}"}>Show
+ <.link navigate={~p"/members/#{member}"}>{gettext("Show")}
- <.link patch={~p"/members/#{member}/edit"}>Edit
+ <.link patch={~p"/members/#{member}/edit"}>{gettext("Edit")}
<:action :let={{id, member}}>
<.link
phx-click={JS.push("delete", value: %{id: member.id}) |> hide("##{id}")}
- data-confirm="Are you sure?"
+ data-confirm={gettext("Are you sure?")}
>
- Delete
+ {gettext("Delete")}
@@ -73,19 +73,19 @@ defmodule MvWeb.MemberLive.Index do
defp apply_action(socket, :edit, %{"id" => id}) do
socket
- |> assign(:page_title, "Edit Member")
+ |> assign(:page_title, gettext("Edit Member"))
|> assign(:member, Ash.get!(Mv.Membership.Member, id))
end
defp apply_action(socket, :new, _params) do
socket
- |> assign(:page_title, "New Member")
+ |> assign(:page_title, gettext("New Member"))
|> assign(:member, nil)
end
defp apply_action(socket, :index, _params) do
socket
- |> assign(:page_title, "Listing Members")
+ |> assign(:page_title, gettext("Listing Members"))
|> assign(:member, nil)
end
diff --git a/lib/mv_web/member_live/show.ex b/lib/mv_web/member_live/show.ex
index c58b0e3..65e6e85 100644
--- a/lib/mv_web/member_live/show.ex
+++ b/lib/mv_web/member_live/show.ex
@@ -7,33 +7,33 @@ defmodule MvWeb.MemberLive.Show do
~H"""
<.header>
{@member.first_name} {@member.last_name}
- <:subtitle>This is a member record from your database.
+ <:subtitle>{gettext("This is a member record from your database.")}
<:actions>
<.link patch={~p"/members/#{@member}/show/edit"} phx-click={JS.push_focus()}>
- <.button>Edit member
+ <.button>{gettext("Edit member")}
<.list>
- <:item title="Id">{@member.id}
- <:item title="First Name">{@member.first_name}
- <:item title="Last Name">{@member.last_name}
- <:item title="Email">{@member.email}
- <:item title="Birth Date">{@member.birth_date}
- <:item title="Paid">{if @member.paid, do: "Yes", else: "No"}
- <:item title="Phone Number">{@member.phone_number}
- <:item title="Join Date">{@member.join_date}
- <:item title="Exit Date">{@member.exit_date}
- <:item title="Notes">{@member.notes}
- <:item title="City">{@member.city}
- <:item title="Street">{@member.street}
- <:item title="House Number">{@member.house_number}
- <:item title="Postal Code">{@member.postal_code}
+ <:item title={gettext("Id")}>{@member.id}
+ <:item title={gettext("First Name")}>{@member.first_name}
+ <:item title={gettext("Last Name")}>{@member.last_name}
+ <:item title={gettext("Email")}>{@member.email}
+ <:item title={gettext("Birth Date")}>{@member.birth_date}
+ <:item title={gettext("Paid")}>{if @member.paid, do: gettext("Yes"), else: gettext("No")}
+ <:item title={gettext("Phone Number")}>{@member.phone_number}
+ <:item title={gettext("Join Date")}>{@member.join_date}
+ <:item title={gettext("Exit Date")}>{@member.exit_date}
+ <:item title={gettext("Notes")}>{@member.notes}
+ <:item title={gettext("City")}>{@member.city}
+ <:item title={gettext("Street")}>{@member.street}
+ <:item title={gettext("House Number")}>{@member.house_number}
+ <:item title={gettext("Postal Code")}>{@member.postal_code}
-
Custom Properties
+
{gettext("Custom Properties")}
<.generic_list items={
Enum.map(@member.properties, fn p ->
{
@@ -47,7 +47,7 @@ defmodule MvWeb.MemberLive.Show do
}
end)
} />
- <.back navigate={~p"/members"}>Back to members
+ <.back navigate={~p"/members"}>{gettext("Back to members")}
<.modal
:if={@live_action == :edit}
@@ -87,6 +87,6 @@ defmodule MvWeb.MemberLive.Show do
|> assign(:member, member)}
end
- defp page_title(:show), do: "Show Member"
- defp page_title(:edit), do: "Edit Member"
+ defp page_title(:show), do: gettext("Show Member")
+ defp page_title(:edit), do: gettext("Edit Member")
end
diff --git a/lib/mv_web/router.ex b/lib/mv_web/router.ex
index 3e9bdca..f2cde75 100644
--- a/lib/mv_web/router.ex
+++ b/lib/mv_web/router.ex
@@ -8,6 +8,7 @@ defmodule MvWeb.Router do
plug :put_root_layout, html: {MvWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
+ plug :set_locale
end
pipeline :api do
@@ -35,6 +36,8 @@ defmodule MvWeb.Router do
live "/properties/:id/edit", PropertyLive.Index, :edit
live "/properties/:id", PropertyLive.Show, :show
live "/properties/:id/show/edit", PropertyLive.Show, :edit
+
+ post "/set_locale", LocaleController, :set_locale
end
# Other scopes may use custom stacks.
@@ -68,4 +71,10 @@ defmodule MvWeb.Router do
ash_admin "/"
end
end
+
+ defp set_locale(conn, _opts) do
+ locale = get_session(conn, :locale) || "en"
+ Gettext.put_locale(MvWeb.Gettext, locale)
+ conn
+ end
end
diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po
new file mode 100644
index 0000000..e5615f6
--- /dev/null
+++ b/priv/gettext/de/LC_MESSAGES/default.po
@@ -0,0 +1,244 @@
+## `msgid`s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove `msgid`s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use `mix gettext.extract --merge` or `mix gettext.merge`
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en\n"
+
+#: lib/mv_web/components/core_components.ex:482
+#, elixir-autogen, elixir-format
+msgid "Actions"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:39
+#, elixir-autogen, elixir-format
+msgid "Are you sure?"
+msgstr "Bist du sicher?"
+
+#: lib/mv_web/components/core_components.ex:160
+#, elixir-autogen, elixir-format
+msgid "Attempting to reconnect"
+msgstr "Verbindung wird wiederhergestellt"
+
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/index.ex:25
+#: lib/mv_web/member_live/show.ex:30
+#, elixir-autogen, elixir-format
+msgid "City"
+msgstr "Stadt"
+
+#: lib/mv_web/member_live/index.ex:41
+#, elixir-autogen, elixir-format
+msgid "Delete"
+msgstr "Löschen"
+
+#: lib/mv_web/member_live/index.ex:33
+#, elixir-autogen, elixir-format
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: lib/mv_web/member_live/index.ex:76
+#: lib/mv_web/member_live/show.ex:91
+#, elixir-autogen, elixir-format
+msgid "Edit Member"
+msgstr "Mitglied bearbeiten"
+
+#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/index.ex:24
+#: lib/mv_web/member_live/show.ex:23
+#, elixir-autogen, elixir-format
+msgid "Email"
+msgstr "E-Mail"
+
+#: lib/mv_web/components/core_components.ex:151
+#, elixir-autogen, elixir-format
+msgid "Error!"
+msgstr "Fehler!"
+
+#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/index.ex:22
+#: lib/mv_web/member_live/show.ex:21
+#, elixir-autogen, elixir-format
+msgid "First Name"
+msgstr "Vorname"
+
+#: lib/mv_web/components/core_components.ex:172
+#, elixir-autogen, elixir-format
+msgid "Hang in there while we get back on track"
+msgstr "Bitte warten, wir stellen die Verbindung wieder her."
+
+#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/index.ex:26
+#: lib/mv_web/member_live/show.ex:27
+#, elixir-autogen, elixir-format
+msgid "Join Date"
+msgstr "Beitrittsdatum"
+
+#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/index.ex:23
+#: lib/mv_web/member_live/show.ex:22
+#, elixir-autogen, elixir-format
+msgid "Last Name"
+msgstr "Nachname"
+
+#: lib/mv_web/member_live/index.ex:8
+#: lib/mv_web/member_live/index.ex:88
+#, elixir-autogen, elixir-format
+msgid "Listing Members"
+msgstr "Mitglieder"
+
+#: lib/mv_web/member_live/index.ex:11
+#: lib/mv_web/member_live/index.ex:82
+#, elixir-autogen, elixir-format
+msgid "New Member"
+msgstr "Neues Mitglied"
+
+#: lib/mv_web/member_live/index.ex:30
+#, elixir-autogen, elixir-format
+msgid "Show"
+msgstr "Anzeigen"
+
+#: lib/mv_web/components/core_components.ex:167
+#, elixir-autogen, elixir-format
+msgid "Something went wrong!"
+msgstr "Etwas ist schiefgelaufen!"
+
+#: lib/mv_web/components/core_components.ex:150
+#, elixir-autogen, elixir-format
+msgid "Success!"
+msgstr "Erfolg!"
+
+#: lib/mv_web/components/core_components.ex:155
+#, elixir-autogen, elixir-format
+msgid "We can't find the internet"
+msgstr "Keine Internetverbindung gefunden"
+
+#: lib/mv_web/components/core_components.ex:76
+#: lib/mv_web/components/core_components.ex:130
+#, elixir-autogen, elixir-format
+msgid "close"
+msgstr "schließen"
+
+#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/show.ex:24
+#, elixir-autogen, elixir-format
+msgid "Birth Date"
+msgstr "Geburtsdatum"
+
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:36
+#, elixir-autogen, elixir-format
+msgid "Custom Properties"
+msgstr "Eigene Eigenschaften"
+
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
+#, elixir-autogen, elixir-format
+msgid "Exit Date"
+msgstr "Austrittsdatum"
+
+#: lib/mv_web/member_live/form_component.ex:50
+#: lib/mv_web/member_live/show.ex:32
+#, elixir-autogen, elixir-format
+msgid "House Number"
+msgstr "Hausnummer"
+
+#: lib/mv_web/member_live/form_component.ex:47
+#: lib/mv_web/member_live/show.ex:29
+#, elixir-autogen, elixir-format
+msgid "Notes"
+msgstr "Notizen"
+
+#: lib/mv_web/member_live/form_component.ex:43
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Paid"
+msgstr "Bezahlt"
+
+#: lib/mv_web/member_live/form_component.ex:44
+#: lib/mv_web/member_live/show.ex:26
+#, elixir-autogen, elixir-format
+msgid "Phone Number"
+msgstr "Telefonnummer"
+
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
+#, elixir-autogen, elixir-format
+msgid "Postal Code"
+msgstr "Postleitzahl"
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format
+msgid "Save Member"
+msgstr "Mitglied speichern"
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format
+msgid "Saving..."
+msgstr "Speichern..."
+
+#: lib/mv_web/member_live/form_component.ex:49
+#: lib/mv_web/member_live/show.ex:31
+#, elixir-autogen, elixir-format
+msgid "Street"
+msgstr "Straße"
+
+#: lib/mv_web/member_live/form_component.ex:29
+#, elixir-autogen, elixir-format
+msgid "Use this form to manage member records and their properties."
+msgstr "Dieses Formular dient zur Verwaltung von Mitgliedern und deren Eigenschaften."
+
+#: lib/mv_web/member_live/show.ex:50
+#, elixir-autogen, elixir-format
+msgid "Back to members"
+msgstr "Zurück zur Mitgliederliste"
+
+#: lib/mv_web/member_live/show.ex:14
+#, elixir-autogen, elixir-format
+msgid "Edit member"
+msgstr "Mitglied bearbeiten"
+
+#: lib/mv_web/member_live/show.ex:20
+#, elixir-autogen, elixir-format
+msgid "Id"
+msgstr "ID"
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "No"
+msgstr "Nein"
+
+#: lib/mv_web/member_live/show.ex:90
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Show Member"
+msgstr "Mitglied anzeigen"
+
+#: lib/mv_web/member_live/show.ex:10
+#, elixir-autogen, elixir-format
+msgid "This is a member record from your database."
+msgstr "Dies ist ein Mitglied aus deiner Datenbank."
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Yes"
+msgstr "Ja"
+
+#: lib/mv_web/member_live/form_component.ex:107
+#, elixir-autogen, elixir-format
+msgid "Mitglied %{action} erfolgreich"
+msgstr "Mitglied %{action} erfolgreich"
+
+#: lib/mv_web/member_live/form_component.ex:100
+#, elixir-autogen, elixir-format
+msgid "create"
+msgstr "erstellt"
+
+#: lib/mv_web/member_live/form_component.ex:101
+#, elixir-autogen, elixir-format
+msgid "update"
+msgstr "aktualisiert"
diff --git a/priv/gettext/de/LC_MESSAGES/errors.po b/priv/gettext/de/LC_MESSAGES/errors.po
new file mode 100644
index 0000000..c0fba6d
--- /dev/null
+++ b/priv/gettext/de/LC_MESSAGES/errors.po
@@ -0,0 +1,133 @@
+## `msgid`s in this file come from POT (.pot) files.
+##
+## Do not add, change, or remove `msgid`s manually here as
+## they're tied to the ones in the corresponding POT file
+## (with the same domain).
+##
+## Use `mix gettext.extract --merge` or `mix gettext.merge`
+## to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en\n"
+
+## From Ecto.Changeset.cast/4
+msgid "can't be blank"
+msgstr ""
+
+## From Ecto.Changeset.unique_constraint/3
+msgid "has already been taken"
+msgstr ""
+
+## From Ecto.Changeset.put_change/3
+msgid "is invalid"
+msgstr ""
+
+## From Ecto.Changeset.validate_acceptance/3
+msgid "must be accepted"
+msgstr ""
+
+## From Ecto.Changeset.validate_format/3
+msgid "has invalid format"
+msgstr ""
+
+## From Ecto.Changeset.validate_subset/3
+msgid "has an invalid entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_exclusion/3
+msgid "is reserved"
+msgstr ""
+
+## From Ecto.Changeset.validate_confirmation/3
+msgid "does not match confirmation"
+msgstr ""
+
+## From Ecto.Changeset.no_assoc_constraint/3
+msgid "is still associated with this entry"
+msgstr ""
+
+msgid "are still associated with this entry"
+msgstr ""
+
+## From Ecto.Changeset.validate_length/3
+msgid "should have %{count} item(s)"
+msgid_plural "should have %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be %{count} byte(s)"
+msgid_plural "should be %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at least %{count} byte(s)"
+msgid_plural "should be at least %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "should be at most %{count} byte(s)"
+msgid_plural "should be at most %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+
+## From Ecto.Changeset.validate_number/3
+msgid "must be less than %{number}"
+msgstr ""
+
+msgid "must be greater than %{number}"
+msgstr ""
+
+msgid "must be less than or equal to %{number}"
+msgstr ""
+
+msgid "must be greater than or equal to %{number}"
+msgstr ""
+
+msgid "must be equal to %{number}"
+msgstr ""
+
+msgid "is not a valid email"
+msgstr "ist keine gültige E-Mail-Adresse"
+
+msgid "cannot be in the future"
+msgstr "darf nicht in der Zukunft liegen"
+
+msgid "must be present"
+msgstr "muss ausgefüllt sein"
+
+msgid "is not a valid phone number"
+msgstr "ist keine gültige Telefonnummer"
+
+msgid "length must be greater than or equal to 5"
+msgstr "Die Länge muss mindestens 5 Zeichen betragen"
+
+msgid "cannot be before join date"
+msgstr "darf nicht vor dem Eintrittsdatum liegen"
+
+msgid "must consist of 5 digits"
+msgstr "muss aus 5 Ziffern bestehen"
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
new file mode 100644
index 0000000..f5b79d7
--- /dev/null
+++ b/priv/gettext/default.pot
@@ -0,0 +1,245 @@
+## This file is a PO Template file.
+##
+## "msgid"s here are often extracted from source code.
+## Add new messages manually only if they're dynamic
+## messages that can't be statically extracted.
+##
+## Run "mix gettext.extract" to bring this file up to
+## date. Leave "msgstr"s empty as changing them here has no
+## effect: edit them in PO (.po) files instead.
+#
+msgid ""
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:482
+#, elixir-autogen, elixir-format
+msgid "Actions"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:39
+#, elixir-autogen, elixir-format
+msgid "Are you sure?"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:160
+#, elixir-autogen, elixir-format
+msgid "Attempting to reconnect"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/index.ex:25
+#: lib/mv_web/member_live/show.ex:30
+#, elixir-autogen, elixir-format
+msgid "City"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:41
+#, elixir-autogen, elixir-format
+msgid "Delete"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:33
+#, elixir-autogen, elixir-format
+msgid "Edit"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:76
+#: lib/mv_web/member_live/show.ex:91
+#, elixir-autogen, elixir-format
+msgid "Edit Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/index.ex:24
+#: lib/mv_web/member_live/show.ex:23
+#, elixir-autogen, elixir-format
+msgid "Email"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:151
+#, elixir-autogen, elixir-format
+msgid "Error!"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/index.ex:22
+#: lib/mv_web/member_live/show.ex:21
+#, elixir-autogen, elixir-format
+msgid "First Name"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:172
+#, elixir-autogen, elixir-format
+msgid "Hang in there while we get back on track"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/index.ex:26
+#: lib/mv_web/member_live/show.ex:27
+#, elixir-autogen, elixir-format
+msgid "Join Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/index.ex:23
+#: lib/mv_web/member_live/show.ex:22
+#, elixir-autogen, elixir-format
+msgid "Last Name"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:8
+#: lib/mv_web/member_live/index.ex:88
+#, elixir-autogen, elixir-format
+msgid "Listing Members"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:11
+#: lib/mv_web/member_live/index.ex:82
+#, elixir-autogen, elixir-format
+msgid "New Member"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:30
+#, elixir-autogen, elixir-format
+msgid "Show"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:167
+#, elixir-autogen, elixir-format
+msgid "Something went wrong!"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:150
+#, elixir-autogen, elixir-format
+msgid "Success!"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:155
+#, elixir-autogen, elixir-format
+msgid "We can't find the internet"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:76
+#: lib/mv_web/components/core_components.ex:130
+#, elixir-autogen, elixir-format
+msgid "close"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/show.ex:24
+#, elixir-autogen, elixir-format
+msgid "Birth Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:36
+#, elixir-autogen, elixir-format
+msgid "Custom Properties"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
+#, elixir-autogen, elixir-format
+msgid "Exit Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:50
+#: lib/mv_web/member_live/show.ex:32
+#, elixir-autogen, elixir-format
+msgid "House Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:47
+#: lib/mv_web/member_live/show.ex:29
+#, elixir-autogen, elixir-format
+msgid "Notes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:43
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Paid"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:44
+#: lib/mv_web/member_live/show.ex:26
+#, elixir-autogen, elixir-format
+msgid "Phone Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
+#, elixir-autogen, elixir-format
+msgid "Postal Code"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format
+msgid "Save Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format
+msgid "Saving..."
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:49
+#: lib/mv_web/member_live/show.ex:31
+#, elixir-autogen, elixir-format
+msgid "Street"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:29
+#, elixir-autogen, elixir-format
+msgid "Use this form to manage member records and their properties."
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:50
+#, elixir-autogen, elixir-format
+msgid "Back to members"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:14
+#, elixir-autogen, elixir-format
+msgid "Edit member"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:20
+#, elixir-autogen, elixir-format
+msgid "Id"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "No"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:90
+#, elixir-autogen, elixir-format
+msgid "Show Member"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:10
+#, elixir-autogen, elixir-format
+msgid "This is a member record from your database."
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Yes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:107
+#, elixir-autogen, elixir-format
+msgid "Mitglied %{action} erfolgreich"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:100
+#, elixir-autogen, elixir-format
+msgid "create"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:101
+#, elixir-autogen, elixir-format
+msgid "update"
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po
new file mode 100644
index 0000000..efce4f9
--- /dev/null
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -0,0 +1,245 @@
+## "msgid"s in this file come from POT (.pot) files.
+###
+### Do not add, change, or remove "msgid"s manually here as
+### they're tied to the ones in the corresponding POT file
+### (with the same domain).
+###
+### Use "mix gettext.extract --merge" or "mix gettext.merge"
+### to merge POT files into PO files.
+msgid ""
+msgstr ""
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: lib/mv_web/components/core_components.ex:482
+#, elixir-autogen, elixir-format
+msgid "Actions"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:39
+#, elixir-autogen, elixir-format
+msgid "Are you sure?"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:160
+#, elixir-autogen, elixir-format
+msgid "Attempting to reconnect"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:48
+#: lib/mv_web/member_live/index.ex:25
+#: lib/mv_web/member_live/show.ex:30
+#, elixir-autogen, elixir-format
+msgid "City"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:41
+#, elixir-autogen, elixir-format
+msgid "Delete"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:33
+#, elixir-autogen, elixir-format
+msgid "Edit"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:76
+#: lib/mv_web/member_live/show.ex:91
+#, elixir-autogen, elixir-format
+msgid "Edit Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:41
+#: lib/mv_web/member_live/index.ex:24
+#: lib/mv_web/member_live/show.ex:23
+#, elixir-autogen, elixir-format
+msgid "Email"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:151
+#, elixir-autogen, elixir-format
+msgid "Error!"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:39
+#: lib/mv_web/member_live/index.ex:22
+#: lib/mv_web/member_live/show.ex:21
+#, elixir-autogen, elixir-format
+msgid "First Name"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:172
+#, elixir-autogen, elixir-format
+msgid "Hang in there while we get back on track"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:45
+#: lib/mv_web/member_live/index.ex:26
+#: lib/mv_web/member_live/show.ex:27
+#, elixir-autogen, elixir-format
+msgid "Join Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:40
+#: lib/mv_web/member_live/index.ex:23
+#: lib/mv_web/member_live/show.ex:22
+#, elixir-autogen, elixir-format
+msgid "Last Name"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:8
+#: lib/mv_web/member_live/index.ex:88
+#, elixir-autogen, elixir-format
+msgid "Listing Members"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:11
+#: lib/mv_web/member_live/index.ex:82
+#, elixir-autogen, elixir-format
+msgid "New Member"
+msgstr ""
+
+#: lib/mv_web/member_live/index.ex:30
+#, elixir-autogen, elixir-format
+msgid "Show"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:167
+#, elixir-autogen, elixir-format
+msgid "Something went wrong!"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:150
+#, elixir-autogen, elixir-format
+msgid "Success!"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:155
+#, elixir-autogen, elixir-format
+msgid "We can't find the internet"
+msgstr ""
+
+#: lib/mv_web/components/core_components.ex:76
+#: lib/mv_web/components/core_components.ex:130
+#, elixir-autogen, elixir-format
+msgid "close"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:42
+#: lib/mv_web/member_live/show.ex:24
+#, elixir-autogen, elixir-format
+msgid "Birth Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:53
+#: lib/mv_web/member_live/show.ex:36
+#, elixir-autogen, elixir-format
+msgid "Custom Properties"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:46
+#: lib/mv_web/member_live/show.ex:28
+#, elixir-autogen, elixir-format
+msgid "Exit Date"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:50
+#: lib/mv_web/member_live/show.ex:32
+#, elixir-autogen, elixir-format
+msgid "House Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:47
+#: lib/mv_web/member_live/show.ex:29
+#, elixir-autogen, elixir-format
+msgid "Notes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:43
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Paid"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:44
+#: lib/mv_web/member_live/show.ex:26
+#, elixir-autogen, elixir-format
+msgid "Phone Number"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:51
+#: lib/mv_web/member_live/show.ex:33
+#, elixir-autogen, elixir-format
+msgid "Postal Code"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Save Member"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:73
+#, elixir-autogen, elixir-format
+msgid "Saving..."
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:49
+#: lib/mv_web/member_live/show.ex:31
+#, elixir-autogen, elixir-format
+msgid "Street"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:29
+#, elixir-autogen, elixir-format
+msgid "Use this form to manage member records and their properties."
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:50
+#, elixir-autogen, elixir-format
+msgid "Back to members"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:14
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Edit member"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:20
+#, elixir-autogen, elixir-format
+msgid "Id"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "No"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:90
+#, elixir-autogen, elixir-format, fuzzy
+msgid "Show Member"
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:10
+#, elixir-autogen, elixir-format
+msgid "This is a member record from your database."
+msgstr ""
+
+#: lib/mv_web/member_live/show.ex:25
+#, elixir-autogen, elixir-format
+msgid "Yes"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:107
+#, elixir-autogen, elixir-format
+msgid "Mitglied %{action} erfolgreich"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:100
+#, elixir-autogen, elixir-format
+msgid "create"
+msgstr ""
+
+#: lib/mv_web/member_live/form_component.ex:101
+#, elixir-autogen, elixir-format
+msgid "update"
+msgstr ""
diff --git a/priv/gettext/en/LC_MESSAGES/errors.po b/priv/gettext/en/LC_MESSAGES/errors.po
index 844c4f5..60c1037 100644
--- a/priv/gettext/en/LC_MESSAGES/errors.po
+++ b/priv/gettext/en/LC_MESSAGES/errors.po
@@ -110,3 +110,12 @@ msgstr ""
msgid "must be equal to %{number}"
msgstr ""
+
+msgid "length must be greater than or equal to 5"
+msgstr "length must be greater than or equal to 5"
+
+msgid "cannot be before join date"
+msgstr "cannot be before join date"
+
+msgid "must consist of 5 digits"
+msgstr "must consist of 5 digits"
diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs
new file mode 100644
index 0000000..2d3e8bd
--- /dev/null
+++ b/test/mv_web/member_live/index_test.exs
@@ -0,0 +1,16 @@
+defmodule MvWeb.MemberLive.IndexTest do
+ use MvWeb.ConnCase, async: true
+ import Phoenix.LiveViewTest
+
+ test "zeigt übersetzten Titel auf Deutsch", %{conn: conn} do
+ Gettext.put_locale(MvWeb.Gettext, "de")
+ {:ok, _view, html} = live(conn, "/members")
+ assert html =~ "Mitglieder" # Erwarteter deutscher Titel
+ end
+
+ test "shows translated title in English", %{conn: conn} do
+ Gettext.put_locale(MvWeb.Gettext, "en")
+ {:ok, _view, html} = live(conn, "/members")
+ assert html =~ "Members" # Erwarteter englischer Titel
+ end
+end
\ No newline at end of file