From c5192ee424071fa65dfd8b6658bb2d7055eb10cd Mon Sep 17 00:00:00 2001 From: Jose Farias <31393016+josefarias@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:17:49 -0600 Subject: [PATCH] Centralize auth messages (#269) * Add i18n-tasks * Add auth-related i18n * Centralize auth messages * Remove safe navigation * Revert "Remove safe navigation" This reverts commit 56b5e01e5e0ab9f54a9a5d9f5559e29897d239a4. * Remove newline in Gemfile --- Gemfile | 1 + Gemfile.lock | 26 ++++++++++ app/controllers/password_resets_controller.rb | 8 +-- app/controllers/passwords_controller.rb | 2 +- app/controllers/registrations_controller.rb | 6 +-- app/controllers/sessions_controller.rb | 4 +- app/helpers/auth_messages_helper.rb | 6 +++ app/views/layouts/auth.html.erb | 6 --- app/views/password_resets/edit.html.erb | 1 + app/views/password_resets/new.html.erb | 2 + app/views/passwords/edit.html.erb | 6 +-- app/views/registrations/new.html.erb | 6 +-- app/views/sessions/new.html.erb | 2 + app/views/shared/_auth_messages.html.erb | 7 +++ config/i18n-tasks.yml | 25 +++++++++ config/locales/en.yml | 51 ++++++++----------- test/i18n_test.rb | 34 +++++++++++++ 17 files changed, 138 insertions(+), 55 deletions(-) create mode 100644 app/helpers/auth_messages_helper.rb create mode 100644 app/views/shared/_auth_messages.html.erb create mode 100644 config/i18n-tasks.yml create mode 100644 test/i18n_test.rb diff --git a/Gemfile b/Gemfile index 8990e88a..ad6d4e78 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ group :development, :test do gem "rubocop-rails-omakase", require: false gem "dotenv" gem "letter_opener" + gem "i18n-tasks" end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index c0eb3a73..5f12910a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -112,6 +112,13 @@ GEM ast (2.4.2) base64 (0.2.0) bcrypt (3.1.20) + better_html (2.0.2) + actionview (>= 6.0) + activesupport (>= 6.0) + ast (~> 2.0) + erubi (~> 1.4) + parser (>= 2.4) + smart_properties bigdecimal (3.1.6) bindex (0.8.1) bootsnap (1.18.3) @@ -142,12 +149,24 @@ GEM ffi (1.16.3) globalid (1.2.1) activesupport (>= 6.1) + highline (3.0.1) hotwire-livereload (1.3.1) actioncable (>= 6.0.0) listen (>= 3.0.0) railties (>= 6.0.0) i18n (1.14.1) concurrent-ruby (~> 1.0) + i18n-tasks (1.0.13) + activesupport (>= 4.0.2) + ast (>= 2.1.0) + better_html (>= 1.0, < 3.0) + erubi + highline (>= 2.0.0) + i18n + parser (>= 3.2.2.1) + rails-i18n + rainbow (>= 2.2.2, < 4.0) + terminal-table (>= 1.5.1) importmap-rails (2.0.1) actionpack (>= 6.0.0) activesupport (>= 6.0.0) @@ -238,6 +257,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) + rails-i18n (7.0.8) + i18n (>= 0.7, < 2) + railties (>= 6.0.0, < 8) rainbow (3.1.1) rake (13.1.0) rb-fsevent (0.11.2) @@ -300,6 +322,7 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + smart_properties (1.17.0) sorbet-runtime (0.5.11226) stimulus-rails (1.3.3) railties (>= 6.0.0) @@ -316,6 +339,8 @@ GEM railties (>= 6.0.0) tailwindcss-rails (2.3.0-x86_64-linux) railties (>= 6.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) thor (1.3.0) timeout (0.4.1) tzinfo (2.0.6) @@ -352,6 +377,7 @@ DEPENDENCIES debug dotenv hotwire-livereload + i18n-tasks importmap-rails inline_svg jbuilder diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb index be83ec3a..b7584890 100644 --- a/app/controllers/password_resets_controller.rb +++ b/app/controllers/password_resets_controller.rb @@ -1,6 +1,8 @@ class PasswordResetsController < ApplicationController layout "auth" + before_action :set_user_by_token, only: :update + def new end @@ -12,7 +14,7 @@ class PasswordResetsController < ApplicationController ).password_reset.deliver_later end - redirect_to root_path, notice: "If an account with that email exists, we have sent a link to reset your password." + redirect_to root_path, notice: t(".requested") end def edit @@ -20,7 +22,7 @@ class PasswordResetsController < ApplicationController def update if @user.update(password_params) - redirect_to new_session_path, notice: "Your password has been reset." + redirect_to new_session_path, notice: t(".success") else render :edit, status: :unprocessable_entity end @@ -30,7 +32,7 @@ class PasswordResetsController < ApplicationController def set_user_by_token @user = User.find_by_token_for(password_reset: params[:token]) - redirect_to new_password_reset_path, alert: "Invalid token." unless @user.present? + redirect_to new_password_reset_path, alert: t("password_resets.update.invalid_token") unless @user.present? end def password_params diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index cf28c08e..b1e859c6 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -6,7 +6,7 @@ class PasswordsController < ApplicationController def update if current_user.update(password_params) - redirect_to root_path, notice: "Your password has been updated successfully." + redirect_to root_path, notice: t(".success") else render :edit, status: :unprocessable_entity end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3973ddbf..886962e3 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -14,10 +14,10 @@ class RegistrationsController < ApplicationController if @user.save login @user - flash[:notice] = "You have signed up successfully." + flash[:notice] = t(".success") redirect_to root_path else - flash[:alert] = "Invalid input, please try again." + flash[:alert] = t(".failure") render :new end end @@ -34,7 +34,7 @@ class RegistrationsController < ApplicationController def claim_invite_code unless InviteCode.claim! params[:user][:invite_code] - redirect_to new_registration_path, alert: "Invalid invite code, please try again." + redirect_to new_registration_path, alert: t("registrations.create.invalid_invite_code") end end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 468ab024..4bf52314 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -9,13 +9,13 @@ class SessionsController < ApplicationController login user redirect_to root_path else - flash.now[:alert] = "Invalid email or password." + flash.now[:alert] = t(".invalid_credentials") render :new, status: :unprocessable_entity end end def destroy logout - redirect_to root_path, notice: "You have signed out successfully." + redirect_to root_path, notice: t(".logout_successful") end end diff --git a/app/helpers/auth_messages_helper.rb b/app/helpers/auth_messages_helper.rb new file mode 100644 index 00000000..116ea654 --- /dev/null +++ b/app/helpers/auth_messages_helper.rb @@ -0,0 +1,6 @@ +module AuthMessagesHelper + def auth_messages(form = nil) + render "shared/auth_messages", flash: flash, + errors: form&.object&.errors&.full_messages || [] + end +end diff --git a/app/views/layouts/auth.html.erb b/app/views/layouts/auth.html.erb index 1b702ea2..14379632 100644 --- a/app/views/layouts/auth.html.erb +++ b/app/views/layouts/auth.html.erb @@ -18,12 +18,6 @@ - <% flash.each do |type, msg| %> -
- <%= msg %> -
- <% end %> -
diff --git a/app/views/password_resets/edit.html.erb b/app/views/password_resets/edit.html.erb index 1ae6fbf1..90a94474 100644 --- a/app/views/password_resets/edit.html.erb +++ b/app/views/password_resets/edit.html.erb @@ -3,6 +3,7 @@ %> <%= form_with url: password_reset_path(token: params[:token]), html: {class: 'space-y-6'} do |form| %> + <%= auth_messages form %>
<%= form.label :password, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> diff --git a/app/views/password_resets/new.html.erb b/app/views/password_resets/new.html.erb index fe06a479..119f7464 100644 --- a/app/views/password_resets/new.html.erb +++ b/app/views/password_resets/new.html.erb @@ -3,6 +3,8 @@ %> <%= form_with url: password_reset_path, html: {class: 'space-y-6'} do |form| %> + <%= auth_messages form %> +
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> diff --git a/app/views/passwords/edit.html.erb b/app/views/passwords/edit.html.erb index 36691bbc..a40731fd 100644 --- a/app/views/passwords/edit.html.erb +++ b/app/views/passwords/edit.html.erb @@ -1,11 +1,7 @@

Update Password

<%= form_with model: current_user, url: password_path do |form| %> - <% if form.object.errors.any? %> - <% form.object.errors.full_messages.each do |message| %> -
<%= message %>
- <% end %> - <% end %> + <%= auth_messages form %>
<%= form.label :password_challenge, "Current Password" %> diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb index 5b21f599..64dde02b 100644 --- a/app/views/registrations/new.html.erb +++ b/app/views/registrations/new.html.erb @@ -3,11 +3,7 @@ %> <%= form_with model: @user, url: registration_path, html: {class: 'space-y-6'} do |form| %> - <% if form.object.errors.any? %> - <% form.object.errors.full_messages.each do |message| %> -
<%= message %>
- <% end %> - <% end %> + <%= auth_messages form %>
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 93acd91f..ff762ddb 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -3,6 +3,8 @@ %> <%= form_with url: session_path, html: {class: 'space-y-6'} do |form| %> + <%= auth_messages form %> +
<%= form.label :email, "Email address", class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %> <%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %> diff --git a/app/views/shared/_auth_messages.html.erb b/app/views/shared/_auth_messages.html.erb new file mode 100644 index 00000000..62883dca --- /dev/null +++ b/app/views/shared/_auth_messages.html.erb @@ -0,0 +1,7 @@ +<% flash.each do |type, msg| %> +
<%= msg %>
+<% end %> + +<% errors.each do |message| %> +
<%= message %>
+<% end %> diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml new file mode 100644 index 00000000..c29ef601 --- /dev/null +++ b/config/i18n-tasks.yml @@ -0,0 +1,25 @@ +base_locale: en +data: + read: + - config/locales/%{locale}.yml + write: + - config/locales/%{locale}.yml + router: conservative_router +search: + paths: + - app/ + relative_roots: + - app/controllers + - app/helpers + - app/mailers + - app/presenters + - app/views + strict: true + ## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting: + ## *.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less + ## *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus *.webp *.map *.xlsx + exclude: + - app/assets/images + - app/assets/fonts + - app/assets/videos + - app/assets/builds diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c349ae5..527b4de8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,31 +1,22 @@ -# Files in the config/locales directory are used for internationalization and -# are automatically loaded by Rails. If you want to use locales other than -# English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more about the API, please read the Rails Internationalization guide -# at https://guides.rubyonrails.org/i18n.html. -# -# Be aware that YAML interprets the following case-insensitive strings as -# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings -# must be quoted to be interpreted as strings. For example: -# -# en: -# "yes": yup -# enabled: "ON" - +--- en: - hello: "Hello world" + password_resets: + create: + requested: If an account with that email exists, we have sent a link to reset + your password. + update: + invalid_token: Invalid token. + success: Your password has been reset. + passwords: + update: + success: Your password has been updated successfully. + registrations: + create: + failure: Invalid input, please try again. + invalid_invite_code: Invalid invite code, please try again. + success: You have signed up successfully. + sessions: + create: + invalid_credentials: Invalid email or password. + destroy: + logout_successful: You have signed out successfully. diff --git a/test/i18n_test.rb b/test/i18n_test.rb new file mode 100644 index 00000000..56f05df7 --- /dev/null +++ b/test/i18n_test.rb @@ -0,0 +1,34 @@ +require "i18n/tasks" + +class I18nTest < ActiveSupport::TestCase + def setup + @i18n = I18n::Tasks::BaseTask.new + end + + def test_no_missing_keys + missing_keys = @i18n.missing_keys + assert_empty missing_keys, + "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" + end + + def test_no_unused_keys + unused_keys = @i18n.unused_keys + assert_empty unused_keys, + "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" + end + + def test_files_are_normalized + non_normalized = @i18n.non_normalized_paths + error_message = "The following files need to be normalized:\n" \ + "#{non_normalized.map { |path| " #{path}" }.join("\n")}\n" \ + "Please run `i18n-tasks normalize' to fix" + assert_empty non_normalized, error_message + end + + def test_no_inconsistent_interpolations + inconsistent_interpolations = @i18n.inconsistent_interpolations + error_message = "#{inconsistent_interpolations.leaves.count} i18n keys have inconsistent interpolations.\n" \ + "Please run `i18n-tasks check-consistent-interpolations' to show them" + assert_empty inconsistent_interpolations, error_message + end +end