From 8b33d16236b4e3aebba000620cc26025ff8550ec Mon Sep 17 00:00:00 2001 From: Elephant OSS Date: Thu, 4 Jul 2024 08:03:45 -0400 Subject: [PATCH] feat: add i18n support for default error messages --- README.md | 37 ++++++++++++++++++- config/config.exs | 3 ++ demo/config/config.exs | 3 ++ demo/lib/demo/gettext.ex | 4 ++ demo/lib/demo_web/live/home_live.ex | 6 ++- demo/mix.exs | 1 + demo/mix.lock | 2 + .../priv/gettext/es/LC_MESSAGES/live_toast.po | 19 ++++++++++ demo/test/demo_web/live/home_live_test.exs | 16 ++++++++ lib/live_toast/components.ex | 8 ++-- lib/live_toast/gettext.ex | 4 ++ lib/live_toast/live_component.ex | 11 ++---- lib/live_toast/utility.ex | 6 +++ mix.exs | 1 + mix.lock | 2 + 15 files changed, 109 insertions(+), 14 deletions(-) create mode 100644 demo/lib/demo/gettext.ex create mode 100644 demo/priv/gettext/es/LC_MESSAGES/live_toast.po create mode 100644 lib/live_toast/gettext.ex diff --git a/README.md b/README.md index 484686a..cd9876f 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,41 @@ You can change which corner the toasts are anchored to by passing the `corner` s ``` +### Internationalization + +You can provide translations for the defaul error toasts by adding the following to your `config.exs`: + +```elixir +config :live_toast, + gettext_backend: MyApp.Gettext +``` + +You have to create a `live_toast.po` file, inside the `priv/gettext//LC_MESSAGES/` folder for each language you want to support. + +For example, if you want to support spanish, you would create the file `live_toast.po` in the `priv/gettext/es/LC_MESSAGES/` folder, with the following content: + +```po +msgid "" +msgstr "" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "We can't find the internet" +msgstr "Nosotros no podemos encontrar internet" + +msgid "Attempting to reconnect" +msgstr "Intentando reconectar" + +msgid "Something went wrong!" +msgstr "¡Algo salió mal!" + +msgid "Hang in there while we get back on track" +msgstr "Aguanta mientras volvemos a la normalidad" +``` + ### Function Options [`send_toast`](https://hexdocs.pm/live_toast/LiveToast.html#send_toast/3) takes a number of arguments to control it's behavior. They are currently: @@ -159,7 +194,7 @@ You can change which corner the toasts are anchored to by passing the `corner` s - `kind`: The 'level' of this toast. The `component` function can receive this and modify behavior based on severity. the `toast_class_fn` also receives it, and it can be used there to modify styles, for example, making `:info` toasts green and `:error` toasts red. -- `body`: The primary text of the message. +- `body`: The primary text of the message. - `title`: The optional title of the toast displayed at the top. - `icon`: An optional function component that renders next to the title. You can use this with the default toast to display an icon. - `action`: An optional function component that renders to the side. You can use this with the default toast to display an action, like a button. diff --git a/config/config.exs b/config/config.exs index 65e81ec..2103d99 100644 --- a/config/config.exs +++ b/config/config.exs @@ -23,3 +23,6 @@ if Mix.env() == :dev do ~w(--format=iife --target=es2016 --global-name=LiveMotion --minify --outfile=../priv/static/live_toast.min.js) ) end + +config :live_toast, + gettext_backend: LiveToast.Gettext diff --git a/demo/config/config.exs b/demo/config/config.exs index d1146fa..92ae11b 100644 --- a/demo/config/config.exs +++ b/demo/config/config.exs @@ -29,6 +29,9 @@ config :demo, DemoWeb.Endpoint, pubsub_server: Demo.PubSub, live_view: [signing_salt: "mca0adK+"] +config :live_toast, + gettext_backend: Demo.Gettext + config :logger, :console, format: "$time $metadata[$level] $message\n", metadata: [:request_id] diff --git a/demo/lib/demo/gettext.ex b/demo/lib/demo/gettext.ex new file mode 100644 index 0000000..dff3c83 --- /dev/null +++ b/demo/lib/demo/gettext.ex @@ -0,0 +1,4 @@ +defmodule Demo.Gettext do + @moduledoc false + use Gettext, otp_app: :demo +end diff --git a/demo/lib/demo_web/live/home_live.ex b/demo/lib/demo_web/live/home_live.ex index a873bee..525b98d 100644 --- a/demo/lib/demo_web/live/home_live.ex +++ b/demo/lib/demo_web/live/home_live.ex @@ -14,7 +14,11 @@ defmodule DemoWeb.HomeLive do "action" => nil } - def handle_params(_params, _uri, socket) do + def handle_params(params, _uri, socket) do + params + |> Map.get("locale", "en") + |> Gettext.put_locale() + socket = socket |> assign(:settings, @default_settings) diff --git a/demo/mix.exs b/demo/mix.exs index 34749ec..c5304a0 100644 --- a/demo/mix.exs +++ b/demo/mix.exs @@ -38,6 +38,7 @@ defmodule Demo.MixProject do {:dialyxir, ">= 0.0.0", only: [:dev], runtime: false}, {:doctor, ">= 0.0.0", only: [:dev], runtime: false}, {:ex_doc, ">= 0.0.0", only: [:dev], runtime: false}, + {:gettext, "~> 0.24"}, {:mix_audit, ">= 0.0.0", only: [:dev], runtime: false}, {:styler, "~> 0.11.9", only: [:dev, :test], runtime: false}, {:phoenix_html, "~> 4.0"}, diff --git a/demo/mix.lock b/demo/mix.lock index a37ed4d..5239662 100644 --- a/demo/mix.lock +++ b/demo/mix.lock @@ -16,8 +16,10 @@ "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, "ex_doc": {:hex, :ex_doc, "0.32.2", "f60bbeb6ccbe75d005763e2a328e6f05e0624232f2393bc693611c2d3ae9fa0e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "a4480305cdfe7fdfcbb77d1092c76161626d9a7aa4fb698aee745996e34602df"}, + "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, diff --git a/demo/priv/gettext/es/LC_MESSAGES/live_toast.po b/demo/priv/gettext/es/LC_MESSAGES/live_toast.po new file mode 100644 index 0000000..db09862 --- /dev/null +++ b/demo/priv/gettext/es/LC_MESSAGES/live_toast.po @@ -0,0 +1,19 @@ +msgid "" +msgstr "" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "We can't find the internet" +msgstr "Nosotros no podemos encontrar internet" + +msgid "Attempting to reconnect" +msgstr "Intentando reconectar" + +msgid "Something went wrong!" +msgstr "¡Algo salió mal!" + +msgid "Hang in there while we get back on track" +msgstr "Aguanta mientras volvemos a la normalidad" diff --git a/demo/test/demo_web/live/home_live_test.exs b/demo/test/demo_web/live/home_live_test.exs index de7de93..1b7b595 100644 --- a/demo/test/demo_web/live/home_live_test.exs +++ b/demo/test/demo_web/live/home_live_test.exs @@ -29,6 +29,22 @@ defmodule DemoWeb.HomeLiveTest do end end + describe "Localized demo page" do + test "renders error toasts in English", %{conn: conn} do + assert conn + |> get(~p"/") + |> html_response(200) =~ + "We can't find the internet" + end + + test "renders error toasts in Spanish", %{conn: conn} do + assert conn + |> get(~p"/?locale=es") + |> html_response(200) =~ + "Nosotros no podemos encontrar internet" + end + end + describe "LiveToast.send_toast/7" do test "renders correctly", %{conn: conn} do {:ok, view, html} = live(conn, ~p"/") diff --git a/lib/live_toast/components.ex b/lib/live_toast/components.ex index 0e2b537..90bfe68 100644 --- a/lib/live_toast/components.ex +++ b/lib/live_toast/components.ex @@ -142,13 +142,13 @@ defmodule LiveToast.Components do toast_class_fn={@toast_class_fn} id="client-error" kind={:error} - title="We can't find the internet" + title={Utility.translate("We can't find the internet")} phx-update="ignore" phx-disconnected={Utility.show(".phx-client-error #client-error")} phx-connected={Utility.hide("#client-error")} hidden > - Attempting to reconnect + <%= Utility.translate("Attempting to reconnect") %> @@ -158,13 +158,13 @@ defmodule LiveToast.Components do toast_class_fn={@toast_class_fn} id="server-error" kind={:error} - title="Something went wrong!" + title={Utility.translate("Something went wrong!")} phx-update="ignore" phx-disconnected={Utility.show(".phx-server-error #server-error")} phx-connected={Utility.hide("#server-error")} hidden > - Hang in there while we get back on track + <%= Utility.translate("Hang in there while we get back on track") %> """ diff --git a/lib/live_toast/gettext.ex b/lib/live_toast/gettext.ex new file mode 100644 index 0000000..e4dbfc6 --- /dev/null +++ b/lib/live_toast/gettext.ex @@ -0,0 +1,4 @@ +defmodule LiveToast.Gettext do + @moduledoc false + use Gettext, otp_app: :live_toast +end diff --git a/lib/live_toast/live_component.ex b/lib/live_toast/live_component.ex index 805750b..882713d 100644 --- a/lib/live_toast/live_component.ex +++ b/lib/live_toast/live_component.ex @@ -4,6 +4,7 @@ defmodule LiveToast.LiveComponent do use Phoenix.LiveComponent alias LiveToast.Components + alias LiveToast.Utility @impl Phoenix.LiveComponent def mount(socket) do @@ -61,16 +62,10 @@ defmodule LiveToast.LiveComponent do icon={icon} action={action} corner={@corner} - title={ - if title do - title - else - nil - end - } + title={if title, do: Utility.translate(title), else: nil} target={@myself} > - <%= body %> + <%= Utility.translate(body) %> diff --git a/lib/live_toast/utility.ex b/lib/live_toast/utility.ex index be704d0..5403943 100644 --- a/lib/live_toast/utility.ex +++ b/lib/live_toast/utility.ex @@ -65,4 +65,10 @@ defmodule LiveToast.Utility do "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} ) end + + def translate(message) do + :live_toast + |> Application.get_env(:gettext_backend, LiveToast.Gettext) + |> Gettext.dgettext("live_toast", message) + end end diff --git a/mix.exs b/mix.exs index 386e429..b024795 100644 --- a/mix.exs +++ b/mix.exs @@ -42,6 +42,7 @@ defmodule LiveToast.MixProject do {:dialyxir, ">= 0.0.0", only: [:dev], runtime: false}, {:doctor, ">= 0.0.0", only: [:dev], runtime: false}, {:ex_doc, "~> 0.32.2", only: [:dev], runtime: false}, + {:gettext, "~> 0.24.0"}, {:mix_audit, ">= 0.0.0", only: [:dev], runtime: false}, {:styler, "~> 0.11.9", only: [:dev, :test], runtime: false}, {:makeup, "1.1.2", only: [:dev], runtime: false}, diff --git a/mix.lock b/mix.lock index 2eef446..24253d0 100644 --- a/mix.lock +++ b/mix.lock @@ -12,7 +12,9 @@ "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, "ex_doc": {:hex, :ex_doc, "0.32.2", "f60bbeb6ccbe75d005763e2a328e6f05e0624232f2393bc693611c2d3ae9fa0e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "a4480305cdfe7fdfcbb77d1092c76161626d9a7aa4fb698aee745996e34602df"}, + "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},