From fc466316df51df4a8968a9ba35152de7a54cfa94 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Wed, 31 Dec 2025 14:30:42 -0300 Subject: [PATCH] Ensure auth keys at the start of the i18n msg are properly cased Otherwise if we humanized the whole string, it could cause us to change the output of strings with periods and maybe other side-effects, since we're changing the whole string from i18n. This is safer as it only changes the first char of the translated message, and only if it is a match with the first translated auth key, so we can more safely humanize & downcase all auth keys to interpolate in the message whenever needed. Also add changelog for the change. --- CHANGELOG.md | 1 + lib/devise/failure_app.rb | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6ba844..2f1de46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ * Fix passing `format` option to `devise_for` [#5732](https://github.com/heartcombo/devise/pull/5732) * Use `ActiveRecord::SecurityUtils.secure_compare` in `Devise.secure_compare` to match two empty strings correctly. [#4829](https://github.com/heartcombo/devise/pull/4829) * Respond with `401 Unauthorized` for non-navigational requests to destroy the session when there is no authenticated resource. [#4878](https://github.com/heartcombo/devise/pull/4878) + * Fix incorrect grammar of invalid authentication message with capitalized attributes, e.g.: "Invalid Email or password" => "Invalid email or password". (originally introduced by [#4014](https://github.com/heartcombo/devise/pull/4014), released on v4.1.0) [#4834](https://github.com/heartcombo/devise/pull/4834) Please check [4-stable](https://github.com/heartcombo/devise/blob/4-stable/CHANGELOG.md) diff --git a/lib/devise/failure_app.rb b/lib/devise/failure_app.rb index 2f3e11e5..8222780f 100644 --- a/lib/devise/failure_app.rb +++ b/lib/devise/failure_app.rb @@ -111,13 +111,16 @@ module Devise options[:scope] = "devise.failure" options[:default] = [message] auth_keys = scope_class.authentication_keys - keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key).downcase } - options[:authentication_keys] = keys.join(I18n.t(:"support.array.words_connector")) + human_keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| + scope_class.human_attribute_name(key).downcase + } + options[:authentication_keys] = human_keys.join(I18n.t(:"support.array.words_connector")) options = i18n_options(options) - translated_message = I18n.t(:"#{scope}.#{message}", **options) - # only call `#humanize` when the message is `:invalid` to ensure the original format - # of other messages - like `:does_not_exist` - is kept. - message == :invalid ? translated_message.humanize : translated_message + + I18n.t(:"#{scope}.#{message}", **options).then { |msg| + # Ensure that auth keys at the start of the translated string are properly cased. + msg.start_with?(human_keys.first) ? msg.upcase_first : msg + } else message.to_s end