feat: migrate email templates to gettext (Phase 2 - Emails)
- Update UserNotifier to use t_email() helper for all email content
- Extract 4 email subjects and 4 email bodies to emails domain:
* Update email instructions
* Log in instructions
* Confirmation instructions
* Reset password instructions
- Replace string interpolation with Gettext bindings (%{email}, %{url})
- Auto-populate English translations (msgstr = msgid)
- All emails now ready for future internationalization
First module migration complete per design plan.
This commit is contained in:
parent
cfb60cd186
commit
19b635c46f
8 changed files with 179 additions and 77 deletions
|
|
@ -1,7 +1,11 @@
|
|||
import Config
|
||||
|
||||
# Disable Honeybadger in development
|
||||
config :honeybadger, environment_name: :dev, exclude_envs: [:dev]
|
||||
config :honeybadger,
|
||||
environment_name: :dev,
|
||||
exclude_envs: [:dev],
|
||||
# Don't use custom filter in dev since Honeybadger is excluded
|
||||
filter: Honeybadger.Filter.Default
|
||||
|
||||
# Do not include metadata nor timestamps in development logs
|
||||
config :logger, :default_formatter, format: "[$level] $message\n"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ defmodule Towerops.Accounts.UserNotifier do
|
|||
Sends emails for password reset, email confirmation, and other user events.
|
||||
"""
|
||||
|
||||
use Gettext, backend: ToweropsWeb.Gettext
|
||||
|
||||
import Swoosh.Email
|
||||
import ToweropsWeb.GettextHelpers
|
||||
|
||||
alias Towerops.Accounts.User
|
||||
alias Towerops.Mailer
|
||||
|
|
@ -38,16 +41,24 @@ defmodule Towerops.Accounts.UserNotifier do
|
|||
Deliver instructions to update a user email.
|
||||
"""
|
||||
def deliver_update_email_instructions(user, url) do
|
||||
deliver(user.email, "Update email instructions", """
|
||||
subject = t_email("Update email instructions")
|
||||
|
||||
Hi #{user.email},
|
||||
body =
|
||||
t_email(
|
||||
"""
|
||||
Hi %{email},
|
||||
|
||||
You can change your email by visiting the URL below:
|
||||
You can change your email by visiting the URL below:
|
||||
|
||||
#{url}
|
||||
%{url}
|
||||
|
||||
If you didn't request this change, please ignore this.
|
||||
""")
|
||||
If you didn't request this change, please ignore this.
|
||||
""",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
|
||||
deliver(user.email, subject, body)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
@ -61,46 +72,70 @@ defmodule Towerops.Accounts.UserNotifier do
|
|||
end
|
||||
|
||||
defp deliver_magic_link_instructions(user, url) do
|
||||
deliver(user.email, "Log in instructions", """
|
||||
subject = t_email("Log in instructions")
|
||||
|
||||
Hi #{user.email},
|
||||
body =
|
||||
t_email(
|
||||
"""
|
||||
Hi %{email},
|
||||
|
||||
You can log into your account by visiting the URL below:
|
||||
You can log into your account by visiting the URL below:
|
||||
|
||||
#{url}
|
||||
%{url}
|
||||
|
||||
If you didn't request this email, please ignore this.
|
||||
""")
|
||||
If you didn't request this email, please ignore this.
|
||||
""",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
|
||||
deliver(user.email, subject, body)
|
||||
end
|
||||
|
||||
defp deliver_confirmation_instructions(user, url) do
|
||||
deliver(user.email, "Confirmation instructions", """
|
||||
subject = t_email("Confirmation instructions")
|
||||
|
||||
Hi #{user.email},
|
||||
body =
|
||||
t_email(
|
||||
"""
|
||||
Hi %{email},
|
||||
|
||||
You can confirm your account by visiting the URL below:
|
||||
You can confirm your account by visiting the URL below:
|
||||
|
||||
#{url}
|
||||
%{url}
|
||||
|
||||
If you didn't create an account with us, please ignore this.
|
||||
""")
|
||||
If you didn't create an account with us, please ignore this.
|
||||
""",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
|
||||
deliver(user.email, subject, body)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deliver instructions to reset a user password.
|
||||
"""
|
||||
def deliver_reset_password_instructions(user, url) do
|
||||
deliver(user.email, "Reset password instructions", """
|
||||
subject = t_email("Reset password instructions")
|
||||
|
||||
Hi #{user.email},
|
||||
body =
|
||||
t_email(
|
||||
"""
|
||||
Hi %{email},
|
||||
|
||||
You can reset your password by visiting the URL below:
|
||||
You can reset your password by visiting the URL below:
|
||||
|
||||
#{url}
|
||||
%{url}
|
||||
|
||||
If you didn't request this change, please ignore this.
|
||||
If you didn't request this change, please ignore this.
|
||||
|
||||
This link will expire in 1 hour.
|
||||
""")
|
||||
This link will expire in 1 hour.
|
||||
""",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
|
||||
deliver(user.email, subject, body)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
gettext("can't be blank")
|
||||
"""
|
||||
|
||||
use Gettext, backend: ToweropsWeb.Gettext
|
||||
|
||||
@doc """
|
||||
Translates a string in the default domain.
|
||||
|
||||
|
|
@ -42,9 +44,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t(msgid) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("default", unquote(msgid))
|
||||
dgettext("default", unquote(msgid))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -53,9 +53,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t(msgid, bindings) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("default", unquote(msgid), unquote(bindings))
|
||||
dgettext("default", unquote(msgid), unquote(bindings))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -66,9 +64,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_auth(msgid) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("auth", unquote(msgid))
|
||||
dgettext("auth", unquote(msgid))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -77,9 +73,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_auth(msgid, bindings) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("auth", unquote(msgid), unquote(bindings))
|
||||
dgettext("auth", unquote(msgid), unquote(bindings))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -90,9 +84,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_equipment(msgid) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("equipment", unquote(msgid))
|
||||
dgettext("equipment", unquote(msgid))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,9 +93,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_equipment(msgid, bindings) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("equipment", unquote(msgid), unquote(bindings))
|
||||
dgettext("equipment", unquote(msgid), unquote(bindings))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -114,9 +104,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_admin(msgid) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("admin", unquote(msgid))
|
||||
dgettext("admin", unquote(msgid))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -125,9 +113,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_admin(msgid, bindings) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("admin", unquote(msgid), unquote(bindings))
|
||||
dgettext("admin", unquote(msgid), unquote(bindings))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -138,9 +124,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_email(msgid) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("emails", unquote(msgid))
|
||||
dgettext("emails", unquote(msgid))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -149,9 +133,7 @@ defmodule ToweropsWeb.GettextHelpers do
|
|||
"""
|
||||
defmacro t_email(msgid, bindings) do
|
||||
quote do
|
||||
require ToweropsWeb.Gettext
|
||||
|
||||
ToweropsWeb.Gettext.dgettext("emails", unquote(msgid), unquote(bindings))
|
||||
dgettext("emails", unquote(msgid), unquote(bindings))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,3 +13,43 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Language: en\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:96
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirmation instructions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can change your email by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:99
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can confirm your account by visiting the URL below:\n\n%{url}\n\nIf you didn't create an account with us, please ignore this.\n"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:78
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this.\n"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:123
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can reset your password by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n\nThis link will expire in 1 hour.\n"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:75
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Log in instructions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:120
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Reset password instructions"
|
||||
msgstr ""
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:44
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Update email instructions"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -56,13 +56,13 @@ msgstr[1] ""
|
|||
#: lib/towerops_web/components/core_components.ex:487
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
msgstr "Actions"
|
||||
|
||||
#: lib/towerops_web/components/layouts.ex:552
|
||||
#: lib/towerops_web/components/layouts.ex:564
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Attempting to reconnect"
|
||||
msgstr ""
|
||||
msgstr "Attempting to reconnect"
|
||||
|
||||
#: lib/towerops_web/helpers/time_helpers.ex:103
|
||||
#: lib/towerops_web/helpers/time_helpers.ex:156
|
||||
|
|
@ -70,19 +70,19 @@ msgstr ""
|
|||
#: lib/towerops_web/helpers/time_helpers.ex:235
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
msgstr "Never"
|
||||
|
||||
#: lib/towerops_web/components/layouts.ex:559
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Something went wrong!"
|
||||
msgstr ""
|
||||
msgstr "Something went wrong!"
|
||||
|
||||
#: lib/towerops_web/components/layouts.ex:547
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "We can't find the internet"
|
||||
msgstr ""
|
||||
msgstr "We can't find the internet"
|
||||
|
||||
#: lib/towerops_web/components/core_components.ex:94
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
msgstr "close"
|
||||
|
|
|
|||
|
|
@ -10,3 +10,43 @@ msgid ""
|
|||
msgstr ""
|
||||
"Language: en\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:96
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Confirmation instructions"
|
||||
msgstr "Confirmation instructions"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:47
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can change your email by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n"
|
||||
msgstr "Hi %{email},\n\nYou can change your email by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:99
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can confirm your account by visiting the URL below:\n\n%{url}\n\nIf you didn't create an account with us, please ignore this.\n"
|
||||
msgstr "Hi %{email},\n\nYou can confirm your account by visiting the URL below:\n\n%{url}\n\nIf you didn't create an account with us, please ignore this.\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:78
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this.\n"
|
||||
msgstr "Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this.\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:123
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Hi %{email},\n\nYou can reset your password by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n\nThis link will expire in 1 hour.\n"
|
||||
msgstr "Hi %{email},\n\nYou can reset your password by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.\n\nThis link will expire in 1 hour.\n"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:75
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Log in instructions"
|
||||
msgstr "Log in instructions"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:120
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Reset password instructions"
|
||||
msgstr "Reset password instructions"
|
||||
|
||||
#: lib/towerops/accounts/user_notifier.ex:44
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Update email instructions"
|
||||
msgstr "Update email instructions"
|
||||
|
|
|
|||
|
|
@ -12,42 +12,42 @@ msgstr ""
|
|||
|
||||
## From Ecto.Changeset.cast/4
|
||||
msgid "can't be blank"
|
||||
msgstr ""
|
||||
msgstr "can't be blank"
|
||||
|
||||
## From Ecto.Changeset.unique_constraint/3
|
||||
msgid "has already been taken"
|
||||
msgstr ""
|
||||
msgstr "has already been taken"
|
||||
|
||||
## From Ecto.Changeset.put_change/3
|
||||
msgid "is invalid"
|
||||
msgstr ""
|
||||
msgstr "is invalid"
|
||||
|
||||
## From Ecto.Changeset.validate_acceptance/3
|
||||
msgid "must be accepted"
|
||||
msgstr ""
|
||||
msgstr "must be accepted"
|
||||
|
||||
## From Ecto.Changeset.validate_format/3
|
||||
msgid "has invalid format"
|
||||
msgstr ""
|
||||
msgstr "has invalid format"
|
||||
|
||||
## From Ecto.Changeset.validate_subset/3
|
||||
msgid "has an invalid entry"
|
||||
msgstr ""
|
||||
msgstr "has an invalid entry"
|
||||
|
||||
## From Ecto.Changeset.validate_exclusion/3
|
||||
msgid "is reserved"
|
||||
msgstr ""
|
||||
msgstr "is reserved"
|
||||
|
||||
## From Ecto.Changeset.validate_confirmation/3
|
||||
msgid "does not match confirmation"
|
||||
msgstr ""
|
||||
msgstr "does not match confirmation"
|
||||
|
||||
## From Ecto.Changeset.no_assoc_constraint/3
|
||||
msgid "is still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "is still associated with this entry"
|
||||
|
||||
msgid "are still associated with this entry"
|
||||
msgstr ""
|
||||
msgstr "are still associated with this entry"
|
||||
|
||||
## From Ecto.Changeset.validate_length/3
|
||||
msgid "should have %{count} item(s)"
|
||||
|
|
@ -97,16 +97,16 @@ msgstr[1] ""
|
|||
|
||||
## From Ecto.Changeset.validate_number/3
|
||||
msgid "must be less than %{number}"
|
||||
msgstr ""
|
||||
msgstr "must be less than %{number}"
|
||||
|
||||
msgid "must be greater than %{number}"
|
||||
msgstr ""
|
||||
msgstr "must be greater than %{number}"
|
||||
|
||||
msgid "must be less than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "must be less than or equal to %{number}"
|
||||
|
||||
msgid "must be greater than or equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "must be greater than or equal to %{number}"
|
||||
|
||||
msgid "must be equal to %{number}"
|
||||
msgstr ""
|
||||
msgstr "must be equal to %{number}"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Devices Tested & Working
|
|||
|
||||
2026-02-02
|
||||
* Experimental mikrotik api and backup
|
||||
* Starting i18n
|
||||
|
||||
2026-02-01
|
||||
* Firmware version fetching and notification for Mikrotik devices
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue