Merge pull request 'feat: add i18n' (#3) from i18n into main
All checks were successful
Checks / check (push) Successful in -6m24s
Build and Push Docker Image / Build and Push Image (push) Successful in -6m1s

Reviewed-on: #3
This commit is contained in:
Vladimir Rubin 2024-12-12 18:56:58 +02:00
commit b5d52426b6
17 changed files with 952 additions and 76 deletions

View file

@ -83,7 +83,7 @@ defmodule ExmrWeb.CoreComponents do
</button> </button>
</div> </div>
<div id={"#{@id}-content"}> <div id={"#{@id}-content"}>
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</div> </div>
</.focus_wrap> </.focus_wrap>
</div> </div>
@ -128,9 +128,9 @@ defmodule ExmrWeb.CoreComponents do
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6"> <p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" /> <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" /> <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
<%= @title %> {@title}
</p> </p>
<p class="mt-2 text-sm leading-5"><%= msg %></p> <p class="mt-2 text-sm leading-5">{msg}</p>
<button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}> <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" /> <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
</button> </button>
@ -161,7 +161,7 @@ defmodule ExmrWeb.CoreComponents do
phx-connected={hide("#client-error")} phx-connected={hide("#client-error")}
hidden hidden
> >
<%= gettext("Attempting to reconnect") %> {gettext("Attempting to reconnect")}
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash> </.flash>
@ -173,7 +173,7 @@ defmodule ExmrWeb.CoreComponents do
phx-connected={hide("#server-error")} phx-connected={hide("#server-error")}
hidden hidden
> >
<%= gettext("Hang in there while we get back on track") %> {gettext("Hang in there while we get back on track")}
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" /> <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash> </.flash>
</div> </div>
@ -207,12 +207,12 @@ defmodule ExmrWeb.CoreComponents do
~H""" ~H"""
<.form :let={f} for={@for} as={@as} {@rest}> <.form :let={f} for={@for} as={@as} {@rest}>
<div class="mt-10 space-y-8 bg-white dark:bg-zinc-950"> <div class="mt-10 space-y-8 bg-white dark:bg-zinc-950">
<%= render_slot(@inner_block, f) %> {render_slot(@inner_block, f)}
<div <div
:for={action <- @actions} :for={action <- @actions}
class="mt-2 flex items-center justify-between gap-6 dark:text-zinc-100" class="mt-2 flex items-center justify-between gap-6 dark:text-zinc-100"
> >
<%= render_slot(action, f) %> {render_slot(action, f)}
</div> </div>
</div> </div>
</.form> </.form>
@ -244,7 +244,7 @@ defmodule ExmrWeb.CoreComponents do
]} ]}
{@rest} {@rest}
> >
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</button> </button>
""" """
end end
@ -328,9 +328,9 @@ defmodule ExmrWeb.CoreComponents do
class="rounded border-zinc-300 text-zinc-900 focus:ring-0" class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
{@rest} {@rest}
/> />
<%= @label %> {@label}
</label> </label>
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}>{msg}</.error>
</div> </div>
""" """
end end
@ -338,7 +338,7 @@ defmodule ExmrWeb.CoreComponents do
def input(%{type: "select"} = assigns) do def input(%{type: "select"} = assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}><%= @label %></.label> <.label for={@id}>{@label}</.label>
<select <select
id={@id} id={@id}
name={@name} name={@name}
@ -346,10 +346,10 @@ defmodule ExmrWeb.CoreComponents do
multiple={@multiple} multiple={@multiple}
{@rest} {@rest}
> >
<option :if={@prompt} value=""><%= @prompt %></option> <option :if={@prompt} value="">{@prompt}</option>
<%= Phoenix.HTML.Form.options_for_select(@options, @value) %> {Phoenix.HTML.Form.options_for_select(@options, @value)}
</select> </select>
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}>{msg}</.error>
</div> </div>
""" """
end end
@ -357,7 +357,7 @@ defmodule ExmrWeb.CoreComponents do
def input(%{type: "textarea"} = assigns) do def input(%{type: "textarea"} = assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}><%= @label %></.label> <.label for={@id}>{@label}</.label>
<textarea <textarea
id={@id} id={@id}
name={@name} name={@name}
@ -368,7 +368,7 @@ defmodule ExmrWeb.CoreComponents do
]} ]}
{@rest} {@rest}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea> ><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}>{msg}</.error>
</div> </div>
""" """
end end
@ -377,7 +377,7 @@ defmodule ExmrWeb.CoreComponents do
def input(assigns) do def input(assigns) do
~H""" ~H"""
<div> <div>
<.label for={@id}><%= @label %></.label> <.label for={@id}>{@label}</.label>
<input <input
type={@type} type={@type}
name={@name} name={@name}
@ -390,7 +390,7 @@ defmodule ExmrWeb.CoreComponents do
]} ]}
{@rest} {@rest}
/> />
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}>{msg}</.error>
</div> </div>
""" """
end end
@ -404,7 +404,7 @@ defmodule ExmrWeb.CoreComponents do
def label(assigns) do def label(assigns) do
~H""" ~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800 dark:text-zinc-200"> <label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800 dark:text-zinc-200">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</label> </label>
""" """
end end
@ -418,7 +418,7 @@ defmodule ExmrWeb.CoreComponents do
~H""" ~H"""
<p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600"> <p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
<.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</p> </p>
""" """
end end
@ -437,13 +437,13 @@ defmodule ExmrWeb.CoreComponents do
<header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}> <header class={[@actions != [] && "flex items-center justify-between gap-6", @class]}>
<div> <div>
<h1 class="text-lg font-semibold leading-8 text-zinc-800 dark:text-zinc-200"> <h1 class="text-lg font-semibold leading-8 text-zinc-800 dark:text-zinc-200">
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</h1> </h1>
<p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600 dark:text-zinc-100"> <p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600 dark:text-zinc-100">
<%= render_slot(@subtitle) %> {render_slot(@subtitle)}
</p> </p>
</div> </div>
<div class="flex-none"><%= render_slot(@actions) %></div> <div class="flex-none">{render_slot(@actions)}</div>
</header> </header>
""" """
end end
@ -484,9 +484,9 @@ defmodule ExmrWeb.CoreComponents do
<table class="w-[40rem] mt-11 sm:w-full"> <table class="w-[40rem] mt-11 sm:w-full">
<thead class="text-sm text-left leading-6 text-zinc-500"> <thead class="text-sm text-left leading-6 text-zinc-500">
<tr> <tr>
<th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th> <th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal">{col[:label]}</th>
<th :if={@action != []} class="relative p-0 pb-4"> <th :if={@action != []} class="relative p-0 pb-4">
<span class="sr-only"><%= gettext("Actions") %></span> <span class="sr-only">{gettext("Actions")}</span>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -504,7 +504,7 @@ defmodule ExmrWeb.CoreComponents do
<div class="block py-4 pr-6"> <div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" /> <span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}> <span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
<%= render_slot(col, @row_item.(row)) %> {render_slot(col, @row_item.(row))}
</span> </span>
</div> </div>
</td> </td>
@ -515,7 +515,7 @@ defmodule ExmrWeb.CoreComponents do
:for={action <- @action} :for={action <- @action}
class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700" class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
> >
<%= render_slot(action, @row_item.(row)) %> {render_slot(action, @row_item.(row))}
</span> </span>
</div> </div>
</td> </td>
@ -545,8 +545,8 @@ defmodule ExmrWeb.CoreComponents do
<div class="mt-14"> <div class="mt-14">
<dl class="-my-4 divide-y divide-zinc-100"> <dl class="-my-4 divide-y divide-zinc-100">
<div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8"> <div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
<dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt> <dt class="w-1/4 flex-none text-zinc-500">{item.title}</dt>
<dd class="text-zinc-700"><%= render_slot(item) %></dd> <dd class="text-zinc-700">{render_slot(item)}</dd>
</div> </div>
</dl> </dl>
</div> </div>
@ -571,7 +571,7 @@ defmodule ExmrWeb.CoreComponents do
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
> >
<.icon name="hero-arrow-left-solid" class="h-3 w-3" /> <.icon name="hero-arrow-left-solid" class="h-3 w-3" />
<%= render_slot(@inner_block) %> {render_slot(@inner_block)}
</.link> </.link>
</div> </div>
""" """

View file

@ -5,7 +5,7 @@
<img src={~p"/images/logo.svg"} width="36" /> <img src={~p"/images/logo.svg"} width="36" />
</a> </a>
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6"> <p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
v<%= Application.spec(:exmr, :vsn) %> v{Application.spec(:exmr, :vsn)}
</p> </p>
</div> </div>
</div> </div>
@ -13,6 +13,6 @@
<main class="px-4 py-20 sm:px-6 lg:px-8"> <main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} /> <.flash_group flash={@flash} />
<%= @inner_content %> {@inner_content}
</div> </div>
</main> </main>

View file

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} /> <meta name="csrf-token" content={get_csrf_token()} />
<.live_title suffix=" · Phoenix Framework"> <.live_title suffix=" · Phoenix Framework">
<%= assigns[:page_title] || "Exmr" %> {assigns[:page_title] || "Exmr"}
</.live_title> </.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script> <script>
@ -23,14 +23,14 @@
<DarkMode.button /> <DarkMode.button />
<%= if @current_user do %> <%= if @current_user do %>
<li class="text-[0.8125rem] leading-6 text-zinc-900 dark:text-zinc-300"> <li class="text-[0.8125rem] leading-6 text-zinc-900 dark:text-zinc-300">
<%= @current_user.email %> {@current_user.email}
</li> </li>
<li> <li>
<.link <.link
href={~p"/users/settings"} href={~p"/users/settings"}
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100"
> >
Settings {gettext("Settings")}
</.link> </.link>
</li> </li>
<li> <li>
@ -39,7 +39,7 @@
method="delete" method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100" class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700 dark:text-zinc-300 dark:hover:text-zinc-100"
> >
Log out {gettext("Log out")}
</.link> </.link>
</li> </li>
<% else %> <% else %>
@ -61,6 +61,6 @@
</li> </li>
<% end %> <% end %>
</ul> </ul>
<%= @inner_content %> {@inner_content}
</body> </body>
</html> </html>

View file

@ -44,20 +44,20 @@
<h1 class="mt-10 flex items-center text-sm font-semibold leading-6 text-purple-400"> <h1 class="mt-10 flex items-center text-sm font-semibold leading-6 text-purple-400">
ExMR ExMR
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6 text-purple-400"> <small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6 text-purple-400">
v<%= Application.spec(:exmr, :vsn) %> v{Application.spec(:exmr, :vsn)}
</small> </small>
</h1> </h1>
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 dark:text-zinc-200/90 text-balance"> <p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 dark:text-zinc-200/90 text-balance">
hell yeah {gettext("A simple, modern, and fast exam management system.")}
</p> </p>
<p class="mt-4 text-base leading-7 text-zinc-600 dark:text-zinc-300"> <p class="mt-4 text-base leading-7 text-zinc-600 dark:text-zinc-300">
y'all really though i'm gonna be serious? lol {gettext("Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado")}
</p> </p>
<div class="flex"> <div class="flex">
<div class="w-full sm:w-auto"> <div class="w-full sm:w-auto">
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3"> <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
<a <a
href="https://git.vavakado.com/vavakado/exmr" href="https://git.vavakado.xyz/vavakado/exmr"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6" class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
> >
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 dark:bg-zinc-800 dark:group-hover:bg-zinc-900/95 sm:group-hover:scale-105"> <span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 dark:bg-zinc-800 dark:group-hover:bg-zinc-900/95 sm:group-hover:scale-105">
@ -93,7 +93,7 @@
</div> </div>
<div class="text-center py-8 font-bold text-3xl text-red-600"> <div class="text-center py-8 font-bold text-3xl text-red-600">
<.link navigate={~p"/exams"} class="underline hover:text-red-400 transition-all ease-in-out"> <.link navigate={~p"/exams"} class="underline hover:text-red-400 transition-all ease-in-out">
exams {gettext("Exams")}
</.link> </.link>
</div> </div>
</div> </div>

7
lib/exmr_web/helpers.ex Normal file
View file

@ -0,0 +1,7 @@
defmodule ExmrWeb.LiveHelpers do
def on_mount(:default, _params, session, socket) do
locale = session["locale"] || "en"
Gettext.put_locale(locale)
{:cont, socket}
end
end

View file

@ -8,8 +8,10 @@ defmodule ExmrWeb.ExamLive.FormComponent do
~H""" ~H"""
<div> <div>
<.header> <.header>
<%= @title %> {@title}
<:subtitle>Use this form to manage exam records in your database.</:subtitle> <:subtitle>
{gettext("Use this form to manage exam records in your database.")}
</:subtitle>
</.header> </.header>
<.simple_form <.simple_form
@ -19,11 +21,11 @@ defmodule ExmrWeb.ExamLive.FormComponent do
phx-change="validate" phx-change="validate"
phx-submit="save" phx-submit="save"
> >
<.input field={@form[:subject]} type="text" label="Subject" /> <.input field={@form[:subject]} type="text" label={gettext("Subject")} />
<.input field={@form[:description]} type="text" label="Description" /> <.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:date]} type="date" label="Date" /> <.input field={@form[:date]} type="date" label={gettext("Date")} />
<:actions> <:actions>
<.button phx-disable-with="Saving...">Save Exam</.button> <.button phx-disable-with={gettext("Saving...")}>{gettext("Save Exam")}</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>

View file

@ -29,7 +29,7 @@ defmodule ExmrWeb.ExamLive.Index do
defp apply_action(socket, :new, _params) do defp apply_action(socket, :new, _params) do
socket socket
|> assign(:page_title, "New Exam") |> assign(:page_title, gettext("New Exam"))
|> assign(:exam, %Exam{}) |> assign(:exam, %Exam{})
end end

View file

@ -1,15 +1,19 @@
<.header> <.header>
Listing Exams {gettext("Listing Exams")}
<div> <div>
<button phx-click="sort" phx-value-by="subject" class="font-light">Sort by Subject</button> <button phx-click="sort" phx-value-by="subject" class="font-light">
{gettext("Sort by Subject")}
</button>
<a>|</a> <a>|</a>
<button phx-click="sort" phx-value-by="date" class="font-light">Sort by Date</button> <button phx-click="sort" phx-value-by="date" class="font-light">
{gettext("Sort by Date")}
</button>
</div> </div>
<:actions> <:actions>
<.link patch={~p"/exams/new"}> <.link patch={~p"/exams/new"}>
<.button class="dark:bg-sky-300 text-white dark:text-black/80 dark:hover:bg-sky-400"> <.button class="dark:bg-sky-300 text-white dark:text-black/80 dark:hover:bg-sky-400">
New Exam {gettext("New Exam")}
</.button> </.button>
</.link> </.link>
</:actions> </:actions>
@ -18,9 +22,17 @@
<div class="divide-y"> <div class="divide-y">
<div :for={exam <- @exams} class="py-2 flex gap-2"> <div :for={exam <- @exams} class="py-2 flex gap-2">
<div class="grow"> <div class="grow">
<div class="font-bold"><%= exam.subject %></div> <div class="font-bold">{exam.subject}</div>
<div class="font-sm"> <div class="font-sm">
<%= exam.date %> | In <b><%= Date.diff(exam.date, Date.utc_today()) %></b> days {exam.date} |
<b>
{case Date.diff(exam.date, Date.utc_today()) do
0 -> gettext("Today")
1 -> gettext("Tommorow")
x when x > 1 -> ngettext("Tomorrow", "%{count} days left", x)
x when x < 0 -> ngettext("Yesterday", "%{count} days passed", x * -1)
end}
</b>
</div> </div>
</div> </div>
<button <button
@ -28,14 +40,14 @@
phx-value-id={exam.id} phx-value-id={exam.id}
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-3 rounded-md" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-3 rounded-md"
> >
Edit {gettext("Edit")}
</button> </button>
<button <button
phx-click="remove" phx-click="remove"
phx-value-id={exam.id} phx-value-id={exam.id}
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-3 rounded-md" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-3 rounded-md"
> >
Remove {gettext("Remove")}
</button> </button>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<.header> <.header>
exam №<%= @exam.id %> exam №{@exam.id}
<:subtitle>очередная контрольная работа по <strong><%= @exam.subject %></strong></:subtitle> <:subtitle>очередная контрольная работа по <strong>{@exam.subject}</strong></:subtitle>
<:actions> <:actions>
<.link patch={~p"/exams/#{@exam}/show/edit"} phx-click={JS.push_focus()}> <.link patch={~p"/exams/#{@exam}/show/edit"} phx-click={JS.push_focus()}>
<.button>изменить</.button> <.button>изменить</.button>
@ -9,8 +9,8 @@
</.header> </.header>
<.list> <.list>
<:item title="Description"><%= @exam.description %></:item> <:item title="Description">{@exam.description}</:item>
<:item title="Date"><%= @exam.date %></:item> <:item title="Date">{@exam.date}</:item>
</.list> </.list>
<.back navigate={~p"/exams"}>Back to exams</.back> <.back navigate={~p"/exams"}>Back to exams</.back>

View file

@ -6,8 +6,8 @@ defmodule ExmrWeb.UserSettingsLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.header class="text-center"> <.header class="text-center">
Account Settings {gettext("Account Settings")}
<:subtitle>Manage your account email address and password settings</:subtitle> <:subtitle>{gettext("Manage your account email address and password settings")}</:subtitle>
</.header> </.header>
<div class="space-y-12 divide-y"> <div class="space-y-12 divide-y">
@ -18,18 +18,20 @@ defmodule ExmrWeb.UserSettingsLive do
phx-submit="update_email" phx-submit="update_email"
phx-change="validate_email" phx-change="validate_email"
> >
<.input field={@email_form[:email]} type="email" label="Email" required /> <.input field={@email_form[:email]} type="email" label={gettext("Email")} required />
<.input <.input
field={@email_form[:current_password]} field={@email_form[:current_password]}
name="current_password" name="current_password"
id="current_password_for_email" id="current_password_for_email"
type="password" type="password"
label="Current password" label={gettext("Current password")}
value={@email_form_current_password} value={@email_form_current_password}
required required
/> />
<:actions> <:actions>
<.button phx-disable-with="Changing...">Change Email</.button> <.button phx-disable-with={gettext("Changing...")}>
{gettext("Change Email")}
</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>
@ -49,23 +51,30 @@ defmodule ExmrWeb.UserSettingsLive do
id="hidden_user_email" id="hidden_user_email"
value={@current_email} value={@current_email}
/> />
<.input field={@password_form[:password]} type="password" label="New password" required /> <.input
field={@password_form[:password]}
type="password"
label={gettext("New password")}
required
/>
<.input <.input
field={@password_form[:password_confirmation]} field={@password_form[:password_confirmation]}
type="password" type="password"
label="Confirm new password" label={gettext("Confirm new password")}
/> />
<.input <.input
field={@password_form[:current_password]} field={@password_form[:current_password]}
name="current_password" name="current_password"
type="password" type="password"
label="Current password" label={gettext("Current password")}
id="current_password_for_password" id="current_password_for_password"
value={@current_password} value={@current_password}
required required
/> />
<:actions> <:actions>
<.button phx-disable-with="Changing...">Change Password</.button> <.button phx-disable-with={gettext("Changing...")}>
{gettext("Change Password")}
</.button>
</:actions> </:actions>
</.simple_form> </.simple_form>
</div> </div>
@ -77,10 +86,10 @@ defmodule ExmrWeb.UserSettingsLive do
socket = socket =
case Users.update_user_email(socket.assigns.current_user, token) do case Users.update_user_email(socket.assigns.current_user, token) do
:ok -> :ok ->
put_flash(socket, :info, "Email changed successfully.") put_flash(socket, :info, gettext("Email changed successfully."))
:error -> :error ->
put_flash(socket, :error, "Email change link is invalid or it has expired.") put_flash(socket, :error, gettext("Email change link is invalid or it has expired."))
end end
{:ok, push_navigate(socket, to: ~p"/users/settings")} {:ok, push_navigate(socket, to: ~p"/users/settings")}
@ -127,7 +136,7 @@ defmodule ExmrWeb.UserSettingsLive do
&url(~p"/users/settings/confirm_email/#{&1}") &url(~p"/users/settings/confirm_email/#{&1}")
) )
info = "A link to confirm your email change has been sent to the new address." info = gettext("A link to confirm your email change has been sent to the new address.")
{:noreply, socket |> put_flash(:info, info) |> assign(email_form_current_password: nil)} {:noreply, socket |> put_flash(:info, info) |> assign(email_form_current_password: nil)}
{:error, changeset} -> {:error, changeset} ->

View file

@ -0,0 +1,67 @@
defmodule ExmrWeb.Plugs.Locale do
import Plug.Conn
def init(_opts), do: nil
def call(conn, _opts) do
accepted_languages = extract_accept_language(conn)
known_locales = Gettext.known_locales(ExmrWeb.Gettext)
accepted_languages =
known_locales --
known_locales -- accepted_languages
case accepted_languages do
[locale | _] ->
Gettext.put_locale(ExmrWeb.Gettext, locale)
conn
|> put_session(:locale, locale)
_ ->
conn
end
end
# Copied from
# https://raw.githubusercontent.com/smeevil/set_locale/fd35624e25d79d61e70742e42ade955e5ff857b8/lib/headers.ex
def extract_accept_language(conn) do
case Plug.Conn.get_req_header(conn, "accept-language") do
[value | _] ->
value
|> String.split(",")
|> Enum.map(&parse_language_option/1)
|> Enum.sort(&(&1.quality > &2.quality))
|> Enum.map(& &1.tag)
|> Enum.reject(&is_nil/1)
|> ensure_language_fallbacks()
_ ->
[]
end
end
defp parse_language_option(string) do
captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
quality =
case Float.parse(captures["quality"] || "1.0") do
{val, _} -> val
_ -> 1.0
end
%{tag: captures["tag"], quality: quality}
end
defp ensure_language_fallbacks(tags) do
Enum.flat_map(tags, fn tag ->
case String.split(tag, "-") do
[language, _country_variant] ->
if Enum.member?(tags, language), do: [tag], else: [tag, language]
[_language] ->
[tag]
end
end)
end
end

View file

@ -12,6 +12,7 @@ defmodule ExmrWeb.Router do
plug :put_root_layout, html: {ExmrWeb.Layouts, :root} plug :put_root_layout, html: {ExmrWeb.Layouts, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug ExmrWeb.Plugs.Locale
plug :fetch_current_user plug :fetch_current_user
end end
@ -53,7 +54,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :redirect_if_user_is_authenticated] pipe_through [:browser, :redirect_if_user_is_authenticated]
live_session :redirect_if_user_is_authenticated, live_session :redirect_if_user_is_authenticated,
on_mount: [{ExmrWeb.UserAuth, :redirect_if_user_is_authenticated}] do on_mount: [{ExmrWeb.UserAuth, :redirect_if_user_is_authenticated}, ExmrWeb.LiveHelpers] do
if Exmr.enable_registration() != "false" do if Exmr.enable_registration() != "false" do
live "/users/register", UserRegistrationLive, :new live "/users/register", UserRegistrationLive, :new
end end
@ -70,7 +71,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :require_authenticated_user] pipe_through [:browser, :require_authenticated_user]
live_session :require_authenticated_user, live_session :require_authenticated_user,
on_mount: [{ExmrWeb.UserAuth, :ensure_authenticated}] do on_mount: [{ExmrWeb.UserAuth, :ensure_authenticated}, ExmrWeb.LiveHelpers] do
if Exmr.enable_registration() == "false" do if Exmr.enable_registration() == "false" do
live "/users/register", UserRegistrationLive, :new live "/users/register", UserRegistrationLive, :new
end end
@ -93,7 +94,7 @@ defmodule ExmrWeb.Router do
delete "/users/log_out", UserSessionController, :delete delete "/users/log_out", UserSessionController, :delete
live_session :current_user, live_session :current_user,
on_mount: [{ExmrWeb.UserAuth, :mount_current_user}] do on_mount: [{ExmrWeb.UserAuth, :mount_current_user}, ExmrWeb.LiveHelpers] do
live "/users/confirm/:token", UserConfirmationLive, :edit live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new live "/users/confirm", UserConfirmationInstructionsLive, :new
end end

225
priv/gettext/default.pot Normal file
View file

@ -0,0 +1,225 @@
## 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/exmr_web/controllers/page_html/home.html.heex:51
#, elixir-autogen, elixir-format
msgid "A simple, modern, and fast exam management system."
msgstr ""
#: lib/exmr_web/components/core_components.ex:489
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/exmr_web/components/core_components.ex:164
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:54
#, elixir-autogen, elixir-format
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:44
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/exmr_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:96
#, elixir-autogen, elixir-format
msgid "Exams"
msgstr ""
#: lib/exmr_web/components/core_components.ex:176
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/exmr_web/live/exam_live/index.ex:32
#: lib/exmr_web/live/exam_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "New Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:51
#, elixir-autogen, elixir-format
msgid "Remove"
msgstr ""
#: lib/exmr_web/components/core_components.ex:171
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:10
#, elixir-autogen, elixir-format
msgid "Sort by Date"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:5
#, elixir-autogen, elixir-format
msgid "Sort by Subject"
msgstr ""
#: lib/exmr_web/components/core_components.ex:154
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:31
#, elixir-autogen, elixir-format
msgid "Today"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:33
#, elixir-autogen, elixir-format
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/components/core_components.ex:159
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:139
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:9
#, elixir-autogen, elixir-format
msgid "Account Settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:33
#, elixir-autogen, elixir-format
msgid "Change Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:76
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
#, elixir-autogen, elixir-format
msgid "Changing..."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:63
#, elixir-autogen, elixir-format
msgid "Confirm new password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
#, elixir-autogen, elixir-format
msgid "Current password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:21
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:92
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:89
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:42
#, elixir-autogen, elixir-format
msgid "Log out"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:10
#, elixir-autogen, elixir-format
msgid "Manage your account email address and password settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:57
#, elixir-autogen, elixir-format
msgid "New password"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:33
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:26
#, elixir-autogen, elixir-format
msgid "Date"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:25
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:24
#, elixir-autogen, elixir-format
msgid "Subject"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:13
#, elixir-autogen, elixir-format
msgid "Use this form to manage exam records in your database."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:2
#, elixir-autogen, elixir-format
msgid "Listing Exams"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Save Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:34
#, elixir-autogen, elixir-format
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/live/exam_live/index.html.heex:32
#, elixir-autogen, elixir-format
msgid "Tommorow"
msgstr ""

View file

@ -0,0 +1,225 @@
## "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/exmr_web/controllers/page_html/home.html.heex:51
#, elixir-autogen, elixir-format
msgid "A simple, modern, and fast exam management system."
msgstr ""
#: lib/exmr_web/components/core_components.ex:489
#, elixir-autogen, elixir-format
msgid "Actions"
msgstr ""
#: lib/exmr_web/components/core_components.ex:164
#, elixir-autogen, elixir-format
msgid "Attempting to reconnect"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:54
#, elixir-autogen, elixir-format
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:44
#, elixir-autogen, elixir-format
msgid "Edit"
msgstr ""
#: lib/exmr_web/components/core_components.ex:155
#, elixir-autogen, elixir-format
msgid "Error!"
msgstr ""
#: lib/exmr_web/controllers/page_html/home.html.heex:96
#, elixir-autogen, elixir-format
msgid "Exams"
msgstr ""
#: lib/exmr_web/components/core_components.ex:176
#, elixir-autogen, elixir-format
msgid "Hang in there while we get back on track"
msgstr ""
#: lib/exmr_web/live/exam_live/index.ex:32
#: lib/exmr_web/live/exam_live/index.html.heex:16
#, elixir-autogen, elixir-format
msgid "New Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:51
#, elixir-autogen, elixir-format
msgid "Remove"
msgstr ""
#: lib/exmr_web/components/core_components.ex:171
#, elixir-autogen, elixir-format
msgid "Something went wrong!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:10
#, elixir-autogen, elixir-format
msgid "Sort by Date"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:5
#, elixir-autogen, elixir-format
msgid "Sort by Subject"
msgstr ""
#: lib/exmr_web/components/core_components.ex:154
#, elixir-autogen, elixir-format
msgid "Success!"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:31
#, elixir-autogen, elixir-format
msgid "Today"
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:33
#, elixir-autogen, elixir-format
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/components/core_components.ex:159
#, elixir-autogen, elixir-format
msgid "We can't find the internet"
msgstr ""
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
#, elixir-autogen, elixir-format
msgid "close"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:139
#, elixir-autogen, elixir-format
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:9
#, elixir-autogen, elixir-format
msgid "Account Settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:33
#, elixir-autogen, elixir-format
msgid "Change Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:76
#, elixir-autogen, elixir-format
msgid "Change Password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
#, elixir-autogen, elixir-format
msgid "Changing..."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:63
#, elixir-autogen, elixir-format
msgid "Confirm new password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
#, elixir-autogen, elixir-format
msgid "Current password"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:21
#, elixir-autogen, elixir-format
msgid "Email"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:92
#, elixir-autogen, elixir-format
msgid "Email change link is invalid or it has expired."
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:89
#, elixir-autogen, elixir-format
msgid "Email changed successfully."
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:42
#, elixir-autogen, elixir-format
msgid "Log out"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:10
#, elixir-autogen, elixir-format
msgid "Manage your account email address and password settings"
msgstr ""
#: lib/exmr_web/live/user_settings_live.ex:57
#, elixir-autogen, elixir-format
msgid "New password"
msgstr ""
#: lib/exmr_web/components/layouts/root.html.heex:33
#, elixir-autogen, elixir-format
msgid "Settings"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:26
#, elixir-autogen, elixir-format
msgid "Date"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:25
#, elixir-autogen, elixir-format
msgid "Description"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:24
#, elixir-autogen, elixir-format
msgid "Subject"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:13
#, elixir-autogen, elixir-format
msgid "Use this form to manage exam records in your database."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:2
#, elixir-autogen, elixir-format
msgid "Listing Exams"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format, fuzzy
msgid "Save Exam"
msgstr ""
#: lib/exmr_web/live/exam_live/form_component.ex:28
#, elixir-autogen, elixir-format
msgid "Saving..."
msgstr ""
#: lib/exmr_web/live/exam_live/index.html.heex:34
#, elixir-autogen, elixir-format
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] ""
msgstr[1] ""
#: lib/exmr_web/live/exam_live/index.html.heex:32
#, elixir-autogen, elixir-format, fuzzy
msgid "Tommorow"
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,206 @@
# # 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.
# Vladimir Rubin <vavakado@proton.me>, 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 2024-12-12 18:19+0200\n"
"Last-Translator: Vladimir Rubin <vavakado@proton.me>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Gtranslator 47.1\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: lib/exmr_web/controllers/page_html/home.html.heex:51
msgid "A simple, modern, and fast exam management system."
msgstr "Простая, современная и быстрая система управления экзаменами."
#: lib/exmr_web/components/core_components.ex:489
msgid "Actions"
msgstr "Действия"
#: lib/exmr_web/components/core_components.ex:164
msgid "Attempting to reconnect"
msgstr "Попытка восстановить соединение"
#: lib/exmr_web/controllers/page_html/home.html.heex:54
msgid "Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado"
msgstr ""
"Создано с использованием Phoenix LiveView, Ecto и TailwindCSS в исполнении "
"@vavakado"
#: lib/exmr_web/live/exam_live/index.html.heex:44
msgid "Edit"
msgstr "Изменить"
#: lib/exmr_web/components/core_components.ex:155
msgid "Error!"
msgstr "Ошибка!"
#: lib/exmr_web/controllers/page_html/home.html.heex:96
msgid "Exams"
msgstr "Экзамены"
#: lib/exmr_web/components/core_components.ex:176
msgid "Hang in there while we get back on track"
msgstr "Держитесь, пока мы не вернемся в строй"
#: lib/exmr_web/live/exam_live/index.ex:32
#: lib/exmr_web/live/exam_live/index.html.heex:16
msgid "New Exam"
msgstr "Новый экзамен"
#: lib/exmr_web/live/exam_live/index.html.heex:51
msgid "Remove"
msgstr "Удалить"
#: lib/exmr_web/components/core_components.ex:171
msgid "Something went wrong!"
msgstr "Что-то пошло не так!"
#: lib/exmr_web/live/exam_live/index.html.heex:10
msgid "Sort by Date"
msgstr "Сортировать по дате"
#: lib/exmr_web/live/exam_live/index.html.heex:5
msgid "Sort by Subject"
msgstr "Сортировать по предмету"
#: lib/exmr_web/components/core_components.ex:154
msgid "Success!"
msgstr "Успех!"
#: lib/exmr_web/live/exam_live/index.html.heex:31
msgid "Today"
msgstr "Сегодня"
#: lib/exmr_web/live/exam_live/index.html.heex:33
msgid "Tomorrow"
msgid_plural "%{count} days left"
msgstr[0] "%{count} день остался"
msgstr[1] "%{count} дня осталось"
msgstr[2] "%{count} дней осталось"
#: lib/exmr_web/components/core_components.ex:159
msgid "We can't find the internet"
msgstr "Мы не можем найти интернет"
#: lib/exmr_web/components/core_components.ex:80
#: lib/exmr_web/components/core_components.ex:134
msgid "close"
msgstr "закрыть"
#: lib/exmr_web/live/user_settings_live.ex:139
msgid "A link to confirm your email change has been sent to the new address."
msgstr ""
"На новый адрес отправлена ссылка для подтверждения изменения электронной "
"почты."
#: lib/exmr_web/live/user_settings_live.ex:9
msgid "Account Settings"
msgstr "Настройки аккаунта"
#: lib/exmr_web/live/user_settings_live.ex:33
msgid "Change Email"
msgstr "Изменить электронную почту"
#: lib/exmr_web/live/user_settings_live.ex:76
msgid "Change Password"
msgstr "Изменить пароль"
#: lib/exmr_web/live/user_settings_live.ex:32
#: lib/exmr_web/live/user_settings_live.ex:75
msgid "Changing..."
msgstr "Меняется..."
#: lib/exmr_web/live/user_settings_live.ex:63
msgid "Confirm new password"
msgstr "Подтвердите новый пароль"
#: lib/exmr_web/live/user_settings_live.ex:27
#: lib/exmr_web/live/user_settings_live.ex:69
msgid "Current password"
msgstr "Текущий пароль"
#: lib/exmr_web/live/user_settings_live.ex:21
msgid "Email"
msgstr "Электронная почта"
#: lib/exmr_web/live/user_settings_live.ex:92
msgid "Email change link is invalid or it has expired."
msgstr ""
"Ссылка для изменения электронной почты недействительна или срок ее действия "
"истек."
#: lib/exmr_web/live/user_settings_live.ex:89
msgid "Email changed successfully."
msgstr "Электронная почта успешно изменена."
#: lib/exmr_web/components/layouts/root.html.heex:42
msgid "Log out"
msgstr "Выйти из системы"
#: lib/exmr_web/live/user_settings_live.ex:10
msgid "Manage your account email address and password settings"
msgstr "Управление адресом электронной почты и настройками пароля"
#: lib/exmr_web/live/user_settings_live.ex:57
msgid "New password"
msgstr "Новый пароль"
#: lib/exmr_web/components/layouts/root.html.heex:33
msgid "Settings"
msgstr "Настройки"
#: lib/exmr_web/live/exam_live/form_component.ex:26
msgid "Date"
msgstr "Дата"
#: lib/exmr_web/live/exam_live/form_component.ex:25
msgid "Description"
msgstr "Описание"
#: lib/exmr_web/live/exam_live/form_component.ex:24
msgid "Subject"
msgstr "Предмет"
#: lib/exmr_web/live/exam_live/form_component.ex:13
msgid "Use this form to manage exam records in your database."
msgstr ""
"Используйте эту форму для управления записями экзаменов в вашей базе данных."
#: lib/exmr_web/live/exam_live/index.html.heex:2
msgid "Listing Exams"
msgstr "Список Экзаменов"
#: lib/exmr_web/live/exam_live/form_component.ex:28
msgid "Save Exam"
msgstr "Сохранить экзамен"
#: lib/exmr_web/live/exam_live/form_component.ex:28
msgid "Saving..."
msgstr "Сохранение..."
#: lib/exmr_web/live/exam_live/index.html.heex:34
msgid "Yesterday"
msgid_plural "%{count} days passed"
msgstr[0] "%{count} день прошёл"
msgstr[1] "%{count} дня прошло"
msgstr[2] "%{count} дней прошло"
#: lib/exmr_web/live/exam_live/index.html.heex:32
msgid "Tommorow"
msgstr "Завтра"

View file

@ -0,0 +1,122 @@
# # "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.
# Vladimir Rubin <vavakado@proton.me>, 2024.
#
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"MIME-Version: 1.0\n"
"Project-Id-Version: unnamed project\n"
"Last-Translator: Vladimir Rubin <vavakado@proton.me>\n"
"Language-Team: Russian\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2024-12-12 18:52+0200\n"
"X-Generator: Gtranslator 47.1\n"
msgid "can't be blank"
msgstr "не может быть пустым"
msgid "has already been taken"
msgstr "уже забрали"
msgid "is invalid"
msgstr "недействителен"
msgid "must be accepted"
msgstr "должен быть принят"
msgid "has invalid format"
msgstr "имеет неправильный формат"
msgid "has an invalid entry"
msgstr "имеет недопустимую запись"
msgid "is reserved"
msgstr "зарезервирован"
msgid "does not match confirmation"
msgstr "не соответствует подтверждению"
msgid "is still associated with this entry"
msgstr "по-прежнему связана с этой записью"
msgid "are still associated with this entry"
msgstr "по-прежнему связаны с этой записью"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "должен иметь %{count} элементов"
msgstr[1] "должен иметь %{count} элемента"
msgstr[2] "должен иметь %{count} элементов"
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "должен быть %{count} символов"
msgstr[1] "должен быть %{count} символа"
msgstr[2] "должен быть %{count} символов"
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] "должно быть %{count} байт"
msgstr[1] "должно быть %{count} байта"
msgstr[2] "должно быть %{count} байтов"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "должно быть не менее %{count} элементов"
msgstr[1] "должно быть не менее %{count} элемента"
msgstr[2] "должно быть не менее %{count} элементов"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "должно быть не менее %{count} символов"
msgstr[1] "должно быть не менее %{count} символа"
msgstr[2] "должно быть не менее %{count} символов"
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] "должно быть хотя бы %{count} байт"
msgstr[1] "должно быть хотя бы %{count} байта"
msgstr[2] "должно быть хотя бы %{count} байтов"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "должно содержать не более %{count} элементов"
msgstr[1] "должно содержать не более %{count} элементов"
msgstr[2] "должно содержать не более %{count} элементов"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "должно быть не более %{count} символа(ов)"
msgstr[1] "должно быть не более %{count} символа(ов)"
msgstr[2] "должно быть не более %{count} символа(ов)"
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] "должно быть не более %{count} байт(ов)."
msgstr[1] "должно быть не более %{count} байт(ов)."
msgstr[2] "должно быть не более %{count} байт(ов)."
msgid "must be less than %{number}"
msgstr "должно быть меньше, чем %{number}"
msgid "must be greater than %{number}"
msgstr "должно быть больше, чем %{number}"
msgid "must be less than or equal to %{number}"
msgstr "должно быть меньше или равно %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "должно быть больше или равно %{number}"
msgid "must be equal to %{number}"
msgstr "должно быть равно %{number}"