- <%= exam.date %> | In <%= Date.diff(exam.date, Date.utc_today()) %> days
+ <%= exam.date %> |
+
+ <%= case Date.diff(exam.date, Date.utc_today()) do
+ 0 -> gettext("Today")
+ 1 -> gettext("Tomorrow")
+ x when x > 1 -> "#{x} #{gettext("days left")}"
+ x when x < 0 -> "#{x*-1} #{gettext("days passed")}"
+ end %>
+
diff --git a/lib/exmr_web/plug/local_plug.ex b/lib/exmr_web/plug/local_plug.ex
new file mode 100644
index 0000000..57952b4
--- /dev/null
+++ b/lib/exmr_web/plug/local_plug.ex
@@ -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?(?[\w\-]+)(?:;q=(?[\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
diff --git a/lib/exmr_web/router.ex b/lib/exmr_web/router.ex
index 460690a..2db5542 100644
--- a/lib/exmr_web/router.ex
+++ b/lib/exmr_web/router.ex
@@ -12,6 +12,7 @@ defmodule ExmrWeb.Router do
plug :put_root_layout, html: {ExmrWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
+ plug ExmrWeb.Plugs.Locale
plug :fetch_current_user
end
@@ -53,7 +54,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :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
live "/users/register", UserRegistrationLive, :new
end
@@ -70,7 +71,7 @@ defmodule ExmrWeb.Router do
pipe_through [:browser, :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
live "/users/register", UserRegistrationLive, :new
end
@@ -93,7 +94,7 @@ defmodule ExmrWeb.Router do
delete "/users/log_out", UserSessionController, :delete
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", UserConfirmationInstructionsLive, :new
end
diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot
new file mode 100644
index 0000000..91c4d4e
--- /dev/null
+++ b/priv/gettext/default.pot
@@ -0,0 +1,113 @@
+## 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:39
+#, 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.html.heex:12
+#, elixir-autogen, elixir-format
+msgid "New Exam"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:46
+#, 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:7
+#, elixir-autogen, elixir-format
+msgid "Sort by Date"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:4
+#, 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:26
+#, elixir-autogen, elixir-format
+msgid "Today"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:27
+#, elixir-autogen, elixir-format
+msgid "Tomorrow"
+msgstr ""
+
+#: 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/exam_live/index.html.heex:28
+#, elixir-autogen, elixir-format
+msgid "days left"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "days passed"
+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..b6987d9
--- /dev/null
+++ b/priv/gettext/en/LC_MESSAGES/default.po
@@ -0,0 +1,113 @@
+## "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:39
+#, 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.html.heex:12
+#, elixir-autogen, elixir-format
+msgid "New Exam"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:46
+#, 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:7
+#, elixir-autogen, elixir-format
+msgid "Sort by Date"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:4
+#, 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:26
+#, elixir-autogen, elixir-format
+msgid "Today"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:27
+#, elixir-autogen, elixir-format
+msgid "Tomorrow"
+msgstr ""
+
+#: 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/exam_live/index.html.heex:28
+#, elixir-autogen, elixir-format
+msgid "days left"
+msgstr ""
+
+#: lib/exmr_web/live/exam_live/index.html.heex:29
+#, elixir-autogen, elixir-format
+msgid "days passed"
+msgstr ""
diff --git a/priv/gettext/ru/LC_MESSAGES/default.mo b/priv/gettext/ru/LC_MESSAGES/default.mo
new file mode 100644
index 0000000..2694b2e
Binary files /dev/null and b/priv/gettext/ru/LC_MESSAGES/default.mo differ
diff --git a/priv/gettext/ru/LC_MESSAGES/default.po b/priv/gettext/ru/LC_MESSAGES/default.po
new file mode 100644
index 0000000..172fa47
--- /dev/null
+++ b/priv/gettext/ru/LC_MESSAGES/default.po
@@ -0,0 +1,103 @@
+# # 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 ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.4.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:39
+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.html.heex:12
+msgid "New Exam"
+msgstr "Новый экзамен"
+
+#: lib/exmr_web/live/exam_live/index.html.heex:46
+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:7
+msgid "Sort by Date"
+msgstr "Сортировать по дате"
+
+#: lib/exmr_web/live/exam_live/index.html.heex:4
+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:26
+msgid "Today"
+msgstr "Сегодня"
+
+#: lib/exmr_web/live/exam_live/index.html.heex:27
+msgid "Tomorrow"
+msgstr "Завтра"
+
+#: 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/exam_live/index.html.heex:28
+msgid "days left"
+msgstr "дней осталось"
+
+#: lib/exmr_web/live/exam_live/index.html.heex:29
+msgid "days passed"
+msgstr "дней прошло"
diff --git a/priv/gettext/ru/LC_MESSAGES/errors.po b/priv/gettext/ru/LC_MESSAGES/errors.po
new file mode 100644
index 0000000..66e9a13
--- /dev/null
+++ b/priv/gettext/ru/LC_MESSAGES/errors.po
@@ -0,0 +1,111 @@
+## "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: 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"
+
+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] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be %{count} character(s)"
+msgid_plural "should be %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be %{count} byte(s)"
+msgid_plural "should be %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should have at least %{count} item(s)"
+msgid_plural "should have at least %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be at least %{count} character(s)"
+msgid_plural "should be at least %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be at least %{count} byte(s)"
+msgid_plural "should be at least %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should have at most %{count} item(s)"
+msgid_plural "should have at most %{count} item(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be at most %{count} character(s)"
+msgid_plural "should be at most %{count} character(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "should be at most %{count} byte(s)"
+msgid_plural "should be at most %{count} byte(s)"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+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 ""