From cf28aa5295e02bc2214e9846ebcef3e9be79c822 Mon Sep 17 00:00:00 2001 From: Vladimir Rubin Date: Tue, 10 Dec 2024 00:39:52 +0200 Subject: [PATCH] feat: add i18n right not, the most of the app is translated, but there are some parts that are not translated yet, like the settings page. --- .../controllers/page_html/home.html.heex | 8 +- lib/exmr_web/helpers.ex | 7 ++ lib/exmr_web/live/exam_live/index.html.heex | 20 +++- lib/exmr_web/plug/local_plug.ex | 67 +++++++++++ lib/exmr_web/router.ex | 7 +- priv/gettext/default.pot | 113 ++++++++++++++++++ priv/gettext/en/LC_MESSAGES/default.po | 113 ++++++++++++++++++ priv/gettext/ru/LC_MESSAGES/default.mo | Bin 0 -> 1779 bytes priv/gettext/ru/LC_MESSAGES/default.po | 103 ++++++++++++++++ priv/gettext/ru/LC_MESSAGES/errors.po | 111 +++++++++++++++++ 10 files changed, 536 insertions(+), 13 deletions(-) create mode 100644 lib/exmr_web/helpers.ex create mode 100644 lib/exmr_web/plug/local_plug.ex create mode 100644 priv/gettext/default.pot create mode 100644 priv/gettext/en/LC_MESSAGES/default.po create mode 100644 priv/gettext/ru/LC_MESSAGES/default.mo create mode 100644 priv/gettext/ru/LC_MESSAGES/default.po create mode 100644 priv/gettext/ru/LC_MESSAGES/errors.po diff --git a/lib/exmr_web/controllers/page_html/home.html.heex b/lib/exmr_web/controllers/page_html/home.html.heex index 15c51e9..abe028b 100644 --- a/lib/exmr_web/controllers/page_html/home.html.heex +++ b/lib/exmr_web/controllers/page_html/home.html.heex @@ -48,16 +48,16 @@

- hell yeah + <%= gettext("A simple, modern, and fast exam management system.") %>

- y'all really though i'm gonna be serious? lol + <%= gettext("Built using Phoenix LiveView, Ecto, and TailwindCSS by @vavakado") %>

diff --git a/lib/exmr_web/helpers.ex b/lib/exmr_web/helpers.ex new file mode 100644 index 0000000..79ea4de --- /dev/null +++ b/lib/exmr_web/helpers.ex @@ -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 diff --git a/lib/exmr_web/live/exam_live/index.html.heex b/lib/exmr_web/live/exam_live/index.html.heex index 478325e..6940612 100644 --- a/lib/exmr_web/live/exam_live/index.html.heex +++ b/lib/exmr_web/live/exam_live/index.html.heex @@ -1,15 +1,15 @@ <.header> Listing Exams
- + | - +
<:actions> <.link patch={~p"/exams/new"}> <.button class="dark:bg-sky-300 text-white dark:text-black/80 dark:hover:bg-sky-400"> - New Exam + <%= gettext("New Exam") %> @@ -20,7 +20,15 @@
<%= exam.subject %>
- <%= 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 0000000000000000000000000000000000000000..2694b2e7e1be05da4e4510f14f07c4b77060530f GIT binary patch literal 1779 zcmZ{i&u<$=6vu~Be$<5mr3itF^deB{!D~vXP!&LGT3o2qLR6c!Qcsih#9r#%v3AFH z%&AF)KuS<01cC!Ugg9_Qa!uUYiR&Q2t%uHDdMg4U{sOKX_`cbYT1Xvh_T!m(^WOJ; zcYoWv^K*gm6y|=+UooGX)!S2@i5ae1vgO7l>!NL;0xd`G?xbpAlJPJ za$FVUK3d=*aCvKg4di^kfZXpLkS*oAPhg@wu@95Slb9&8JrL>!`_10)*qg!CqlGLgZ+aIu0Ph4MdwkS2{-e>uxrthmHnG$7P zksUlk4JmDLUXd@O7Y)rrGb@201ILsReqAR@AUdJCnm5_MhD;Jw5wo%8wBD(TcTU9# zoSUZH?7QAs)e3m!u?TFPhUH#4mQhldM#pX^sy1Q1S5`8dSWi>j%$+%Prg%5u%Y{R^ z6B=?NuNe8d0*j{U=v-iO7reJr zq~c7(tX6m#^h)7K;c!maZ_~?moGzwIwwqqbQ@Uu!wuimG9bw+U5Bp>XWUrH$o7M@1Z4U`q&PI{hHfJSM2bA-U|D@vtT2$O4uJzX6*69m4UVm{>wG6 z*|qD?2RviI#z$h&55@H_qj zMSHH1uCQNi_d7zwHTPzqt&7hBa^OAguuaa1bV<06XdOE6jijrBbK=gB{`+#dIreno Kgtd*cw#B~-3C2(W literal 0 HcmV?d00001 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 ""