Skip to content

Commit

Permalink
improvement: optional gettext, docs
Browse files Browse the repository at this point in the history
Provides `_gettext` functions to views, which can be passed to
components or route generators.

* `_gettext` macro
* Pass `gettext_fn` parallel to `overrides`
* Wrap all output texts in `_gettext`
* Gettext .pot/.po file templates in `/i18n` (included in package)
* Docs
* Doc clarification regarding password reset routes
* Tests for translation and basic test for `/password-reset`
  • Loading branch information
serpent213 committed Jan 21, 2025
1 parent 202fe16 commit 5028671
Show file tree
Hide file tree
Showing 25 changed files with 471 additions and 77 deletions.
25 changes: 24 additions & 1 deletion documentation/tutorials/ui-overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Ash Authentication Phoenix provides a default UI implementation to get you start

Each of our LiveView components has a number of hooks where you can override either the CSS styles, text or images.

In addition you have the option to provide a `gettext/2` compatible function through which all output text will be run.

## Defining Overrides

You override these components by defining an "overrides module", which you will then provide in your router when setting up your routes.
Expand All @@ -24,11 +26,31 @@ end

You only need to define the overrides you want to change. Unspecified overrides will use their default value.

## Internationalisation

Plug in your own i18n library by providing a gettext-like function, for example in your Gettext backend module:

```elixir
defmodule MyAppWeb.Gettext do
use Gettext.Backend, otp_app: :my_app

def translate_auth(msgid, bindings \\ []), do: Gettext.dgettext(__MODULE__, "auth", msgid, bindings)
end
```

The repository includes Gettext templates for the untranslated messages and a growing number of translations. You might want to

```sh
cp -rv deps/ash_authentication_phoenix/i18n/gettext/* priv/gettext
```

## Telling AshAuthentication about your overrides

To do this, you modify your `sign_in_route` calls to contain the `overrides` option. Be sure to put the
`AshAuthentication.Phoenix.Overrides.Default` override last, as it contains the default values for all components!

The same way you may add a `gettext_fn` option to specify your translation function as a `{module, function}` tuple.

```elixir
defmodule MyAppWeb.Router do
use MyAppWeb, :router
Expand All @@ -37,7 +59,8 @@ defmodule MyAppWeb.Router do
# ...

scope "/", MyAppWeb do
sign_in_route(overrides: [MyAppWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default])
sign_in_route overrides: [MyAppWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default],
gettext_fn: {MyAppWeb.Gettext, :translate_auth}
end
end
```
Expand Down
64 changes: 64 additions & 0 deletions i18n/gettext/auth.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## 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.
##
## Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
"Language: en\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 "Already have an account?"
msgstr ""

msgid "Email or password was incorrect"
msgstr ""

msgid "Email"
msgstr ""

msgid "Forgot your password?"
msgstr ""

msgid "If this user exists in our database you will contacted with a sign-in link shortly."
msgstr ""

msgid "If this user exists in our system, you will be contacted with reset instructions shortly."
msgstr ""

msgid "Need an account?"
msgstr ""

msgid "Password"
msgstr ""

msgid "Password Confirmation"
msgstr ""

msgid "Request magic link"
msgstr ""

msgid "Request password reset token"
msgstr ""

msgid "Requesting ..."
msgstr ""

msgid "Reset password with token"
msgstr ""

msgid "Sign in"
msgstr ""

msgid "Signing in ..."
msgstr ""

msgid "Your password has successfully been reset"
msgstr ""
63 changes: 63 additions & 0 deletions i18n/gettext/de/LC_MESSAGES/auth.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## "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: de\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 "Already have an account?"
msgstr "Bereit zum Anmelden?"

msgid "Email or password was incorrect"
msgstr "Email oder Passwort nicht korrekt"

msgid "Email"
msgstr "Email"

msgid "Forgot your password?"
msgstr "Passwort vergessen?"

msgid "If this user exists in our database you will contacted with a sign-in link shortly."
msgstr "Falls dieser Benutzer bekannt ist, wird jetzt eine Email mit Anmelde-Link versendet."

msgid "If this user exists in our system, you will be contacted with reset instructions shortly."
msgstr "Falls dieser Benutzer bekannt ist, wird jetzt eine Email mit einer Anleitung zum Zurücksetzen versendet."

msgid "Need an account?"
msgstr "Konto anlegen?"

msgid "Password"
msgstr "Passwort"

msgid "Password Confirmation"
msgstr "Passwort Wiederholung"

msgid "Request magic link"
msgstr "Magischen Link anfordern"

msgid "Request password reset token"
msgstr "Passwort zurücksetzen"

msgid "Requesting ..."
msgstr "Anfrage låuft..."

msgid "Reset password with token"
msgstr "Neues Passwort setzen"

msgid "Sign in"
msgstr "Anmelden"

msgid "Signing in ..."
msgstr "Anmelden..."

msgid "Your password has successfully been reset"
msgstr "Das Passwort wurde erfolgreich zurückgesetzt"
7 changes: 5 additions & 2 deletions lib/ash_authentication_phoenix/components/apple.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule AshAuthentication.Phoenix.Components.Apple do
* `strategy` - The strategy configuration as per
`AshAuthentication.Info.strategy/2`. Required.
* `overrides` - A list of override modules.
* `gettext_fn` - Optional text translation function.
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
"""
Expand All @@ -29,7 +30,8 @@ defmodule AshAuthentication.Phoenix.Components.Apple do

@type props :: %{
required(:strategy) => AshAuthentication.Strategy.t(),
optional(:overrides) => [module]
optional(:overrides) => [module],
optional(:gettext_fn) => {module, atom}
}

@doc false
Expand All @@ -53,7 +55,8 @@ defmodule AshAuthentication.Phoenix.Components.Apple do
class={override_for(@overrides, :link_class)}
>
<.icon icon={:apple_white} overrides={@overrides} />
<.icon icon={:apple_black} overrides={@overrides} /> Sign in with Apple
<.icon icon={:apple_black} overrides={@overrides} />
{_gettext("Sign in with Apple")}
</a>
</div>
"""
Expand Down
8 changes: 5 additions & 3 deletions lib/ash_authentication_phoenix/components/banner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule AshAuthentication.Phoenix.Components.Banner do
## Props
* `overrides` - A list of override modules.
* `gettext_fn` - Optional text translation function.
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
"""
Expand All @@ -26,7 +27,8 @@ defmodule AshAuthentication.Phoenix.Components.Banner do
alias Phoenix.LiveView.Rendered

@type props :: %{
optional(:overrides) => [module]
optional(:overrides) => [module],
optional(:gettext_fn) => {module, atom}
}

@doc false
Expand Down Expand Up @@ -61,12 +63,12 @@ defmodule AshAuthentication.Phoenix.Components.Banner do
<% {nil, nil} -> %>
<% {nil, txt} -> %>
<div class={override_for(@overrides, :text_class)}>
{txt}
{_gettext(txt)}
</div>
<% {hrf, txt} -> %>
<div class={override_for(@overrides, :text_class)}>
<a class={override_for(@overrides, :href_class)} href={hrf}>
{txt}
{_gettext(txt)}
</a>
</div>
<% end %>
Expand Down
20 changes: 14 additions & 6 deletions lib/ash_authentication_phoenix/components/magic_link.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do
* `strategy` - the strategy configuration as per
`AshAuthentication.Info.strategy/2`. Required.
* `overrides` - A list of override modules.
* `gettext_fn` - Optional text translation function.
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
"""
Expand All @@ -39,10 +40,11 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do

@type props :: %{
required(:strategy) => AshAuthentication.Strategy.t(),
optional(:overrides) => [module],
optional(:current_tenant) => String.t(),
optional(:context) => map(),
optional(:auth_routes_prefix) => String.t()
optional(:auth_routes_prefix) => String.t(),
optional(:overrides) => [module],
optional(:gettext_fn) => {module, atom}
}

@doc false
Expand Down Expand Up @@ -76,7 +78,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do
~H"""
<div class={override_for(@overrides, :root_class)}>
<%= if @label do %>
<h2 class={override_for(@overrides, :label_class)}>{@label}</h2>
<h2 class={override_for(@overrides, :label_class)}>{_gettext(@label)}</h2>
<% end %>
<.form
Expand All @@ -90,14 +92,20 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do
method="POST"
class={override_for(@overrides, :form_class)}
>
<Input.identity_field strategy={@strategy} form={form} overrides={@overrides} />
<Input.identity_field
strategy={@strategy}
form={form}
overrides={@overrides}
gettext_fn={@gettext_fn}
/>
<Input.submit
strategy={@strategy}
form={form}
action={@strategy.request_action_name}
disable_text={override_for(@overrides, :disable_button_text)}
disable_text={_gettext(override_for(@overrides, :disable_button_text))}
overrides={@overrides}
gettext_fn={@gettext_fn}
/>
</.form>
</div>
Expand Down Expand Up @@ -146,7 +154,7 @@ defmodule AshAuthentication.Phoenix.Components.MagicLink do
socket =
if flash do
socket
|> put_flash!(:info, flash)
|> put_flash!(:info, _gettext(flash))
else
socket
end
Expand Down
7 changes: 5 additions & 2 deletions lib/ash_authentication_phoenix/components/oauth2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule AshAuthentication.Phoenix.Components.OAuth2 do
* `strategy` - The strategy configuration as per
`AshAuthentication.Info.strategy/2`. Required.
* `overrides` - A list of override modules.
* `gettext_fn` - Optional text translation function.
#{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()}
"""
Expand All @@ -30,8 +31,9 @@ defmodule AshAuthentication.Phoenix.Components.OAuth2 do

@type props :: %{
required(:strategy) => AshAuthentication.Strategy.t(),
optional(:auth_routes_prefix) => String.t(),
optional(:overrides) => [module],
optional(:auth_routes_prefix) => String.t()
optional(:gettext_fn) => {module, atom}
}

@doc false
Expand All @@ -50,7 +52,8 @@ defmodule AshAuthentication.Phoenix.Components.OAuth2 do
href={auth_path(@socket, @subject_name, @auth_routes_prefix, @strategy, :request)}
class={override_for(@overrides, :link_class)}
>
<.icon icon={@strategy.icon} overrides={@overrides} /> Sign in with {strategy_name(@strategy)}
<.icon icon={@strategy.icon} overrides={@overrides} />
{_gettext("Sign in with #{strategy_name(@strategy)}")}
</a>
</div>
"""
Expand Down
Loading

0 comments on commit 5028671

Please sign in to comment.