From 82c62fe44f6ed4834ac5370bb5b85496a853f45d Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Thu, 4 Apr 2024 15:02:09 +0200 Subject: [PATCH] DEV: Correctly pluralize error messages (#26469) --- app/models/username_validator.rb | 4 +- config/locales/server.en.yml | 92 ++++++++++++++----- lib/theme_settings_object_validator.rb | 8 +- lib/validators/string_setting_validator.rb | 4 +- lib/validators/stripped_length_validator.rb | 2 +- .../theme_settings_object_validator_spec.rb | 6 +- spec/lib/theme_settings_validator_spec.rb | 2 +- spec/models/username_validator_spec.rb | 8 +- spec/requests/users_controller_spec.rb | 4 +- 9 files changed, 88 insertions(+), 42 deletions(-) diff --git a/app/models/username_validator.rb b/app/models/username_validator.rb index 9e909401ea8..20e5262d064 100644 --- a/app/models/username_validator.rb +++ b/app/models/username_validator.rb @@ -88,7 +88,7 @@ class UsernameValidator return unless errors.empty? if username_grapheme_clusters.size < User.username_length.begin - self.errors << I18n.t(:"user.username.short", min: User.username_length.begin) + self.errors << I18n.t(:"user.username.short", count: User.username_length.begin) end end @@ -96,7 +96,7 @@ class UsernameValidator return unless errors.empty? if username_grapheme_clusters.size > User.username_length.end - self.errors << I18n.t(:"user.username.long", max: User.username_length.end) + self.errors << I18n.t(:"user.username.long", count: User.username_length.end) elsif username.length > MAX_CHARS self.errors << I18n.t(:"user.username.too_long") end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f5c1767f766..683909ff1dd 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -134,8 +134,12 @@ en: number_value_not_valid_min: "Value must be larger than or equal to %{min}." number_value_not_valid_max: "Value must be smaller than or equal to %{max}." string_value_not_valid_min_max: "Value must be between %{min} and %{max} characters long." - string_value_not_valid_min: "Value must be at least %{min} characters long." - string_value_not_valid_max: "Value must be at most %{max} characters long." + string_value_not_valid_min: + one: "Value must be at least %{count} character long." + other: "Value must be at least %{count} characters long." + string_value_not_valid_max: + one: "Value must be at most %{count} character long." + other: "Value must be at most %{count} characters long." objects: humanize_required: "The property at JSON Pointer '%{property_json_pointer}' must be present." required: "must be present" @@ -159,10 +163,18 @@ en: humanize_not_valid_categories_value: "The property at JSON Pointer '%{property_json_pointer}' must be an array of valid category ids." not_valid_categories_value: "must be an array of valid category ids" - humanize_categories_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{min} category ids." - categories_value_not_valid_min: "must have at least %{min} category ids" - humanize_categories_value_not_valid_max: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{max} category ids." - categories_value_not_valid_max: "must have at most %{max} category ids" + humanize_categories_value_not_valid_min: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} category id." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} category ids." + categories_value_not_valid_min: + one: "must have at least %{count} category id" + other: "must have at least %{count} category ids" + humanize_categories_value_not_valid_max: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} category id." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} category ids." + categories_value_not_valid_max: + one: "must have at most %{count} category id" + other: "must have at most %{count} category ids" humanize_not_valid_topic_value: "The property at JSON Pointer '%{property_json_pointer}' must be a valid topic id." not_valid_topic_value: "must be a valid topic id" @@ -172,25 +184,49 @@ en: humanize_not_valid_groups_value: "The property at JSON Pointer '%{property_json_pointer}' must be an array of valid group ids." not_valid_groups_value: "must be an array of valid group ids" - humanize_groups_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{min} group ids." - groups_value_not_valid_min: "must have at least %{min} group ids" - humanize_groups_value_not_valid_max: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{max} group ids." - groups_value_not_valid_max: "must have at most %{max} group ids" + humanize_groups_value_not_valid_min: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} group id." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} group ids." + groups_value_not_valid_min: + one: "must have at least %{count} group id" + other: "must have at least %{count} group ids" + humanize_groups_value_not_valid_max: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} group id." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} group ids." + groups_value_not_valid_max: + one: "must have at most %{count} group id" + other: "must have at most %{count} group ids" humanize_not_valid_tags_value: "The property at JSON Pointer '%{property_json_pointer}' must be an array of valid tag names." not_valid_tags_value: "must be an array of valid tag names" - humanize_tags_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{min} tag names." - tags_value_not_valid_min: "must have at least %{min} tag names" - humanize_tags_value_not_valid_max: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{max} tag names." - tags_value_not_valid_max: "must have at most %{max} tag names" + humanize_tags_value_not_valid_min: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} tag name." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{count} tag names." + tags_value_not_valid_min: + one: "must have at least %{count} tag name" + other: "must have at least %{count} tag names" + humanize_tags_value_not_valid_max: + one: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} tag name." + other: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{count} tag names." + tags_value_not_valid_max: + one: "must have at most %{count} tag name" + other: "must have at most %{count} tag names" humanize_not_valid_upload_value: "The property at JSON Pointer '%{property_json_pointer}' must be a valid upload id." not_valid_upload_value: "must be a valid upload id" - humanize_string_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must be at least %{min} characters long." - string_value_not_valid_min: "must be at least %{min} characters long" - humanize_string_value_not_valid_max: "The property at JSON Pointer '%{property_json_pointer}' must be at most %{max} characters long." - string_value_not_valid_max: "must be at most %{max} characters long" + humanize_string_value_not_valid_min: + one: "The property at JSON Pointer '%{property_json_pointer}' must be at least %{count} character long." + other: "The property at JSON Pointer '%{property_json_pointer}' must be at least %{count} characters long." + string_value_not_valid_min: + one: "must be at least %{count} character long" + other: "must be at least %{count} characters long" + humanize_string_value_not_valid_max: + one: "The property at JSON Pointer '%{property_json_pointer}' must be at most %{count} character long." + other: "The property at JSON Pointer '%{property_json_pointer}' must be at most %{count} characters long." + string_value_not_valid_max: + one: "must be at most %{count} character long" + other: "must be at most %{count} characters long" humanize_number_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must be larger than or equal to %{min}." number_value_not_valid_min: "must be larger than or equal to %{min}" @@ -240,7 +276,9 @@ en: format: ! "%{attribute} %{message}" format_with_full_message: "%{attribute}: %{message}" messages: - too_long_validation: "is limited to %{max} characters; you entered %{length}." + too_long_validation: + one: "is limited to %{count} character; you entered %{length}." + other: "is limited to %{count} characters; you entered %{length}." invalid_boolean: "Invalid boolean." taken: "has already been taken" accepted: must be accepted @@ -2604,8 +2642,12 @@ en: must_include_latest: "Top menu must include the 'latest' tab." invalid_string: "Invalid value." invalid_string_min_max: "Must be between %{min} and %{max} characters." - invalid_string_min: "Must be at least %{min} characters." - invalid_string_max: "Must be no more than %{max} characters." + invalid_string_min: + one: "Must be at least %{count} character." + other: "Must be at least %{count} characters." + invalid_string_max: + one: "Must be no more than %{count} character." + other: "Must be no more than %{count} characters." invalid_json: "Invalid JSON." invalid_reply_by_email_address: "Value must contain '%{reply_key}' and be different from the notification email." invalid_alternative_reply_by_email_addresses: "All values must contain '%{reply_key}' and be different from the notification email." @@ -2897,8 +2939,12 @@ en: new_user_typed_too_fast: "New user typed too fast" content_matches_auto_silence_regex: "Content matches auto silence regex" username: - short: "must be at least %{min} characters" - long: "must be no more than %{max} characters" + short: + one: "must be at least %{count} character" + other: "must be at least %{count} characters" + long: + one: "must be no more than %{count} character" + other: "must be no more than %{count} characters" too_long: "is too long" characters: "must only include numbers, letters, dashes, dots, and underscores" unique: "must be unique" diff --git a/lib/theme_settings_object_validator.rb b/lib/theme_settings_object_validator.rb index 96618c6fb65..b074d5504c0 100644 --- a/lib/theme_settings_object_validator.rb +++ b/lib/theme_settings_object_validator.rb @@ -169,22 +169,22 @@ class ThemeSettingsObjectValidator end if (min = validations&.dig(:min)) && value.length < min - add_error(property_name, :"#{type}_value_not_valid_min", min:) + add_error(property_name, :"#{type}_value_not_valid_min", count: min) return false end if (max = validations&.dig(:max)) && value.length > max - add_error(property_name, :"#{type}_value_not_valid_max", max:) + add_error(property_name, :"#{type}_value_not_valid_max", count: max) return false end when "string" if (min = validations&.dig(:min_length)) && value.length < min - add_error(property_name, :string_value_not_valid_min, min:) + add_error(property_name, :string_value_not_valid_min, count: min) return false end if (max = validations&.dig(:max_length)) && value.length > max - add_error(property_name, :string_value_not_valid_max, max:) + add_error(property_name, :string_value_not_valid_max, count: max) return false end diff --git a/lib/validators/string_setting_validator.rb b/lib/validators/string_setting_validator.rb index bbc271b5217..7dcc6abd229 100644 --- a/lib/validators/string_setting_validator.rb +++ b/lib/validators/string_setting_validator.rb @@ -41,9 +41,9 @@ class StringSettingValidator if @opts[:min] && @opts[:max] I18n.t("site_settings.errors.invalid_string_min_max", min: @opts[:min], max: @opts[:max]) elsif @opts[:min] - I18n.t("site_settings.errors.invalid_string_min", min: @opts[:min]) + I18n.t("site_settings.errors.invalid_string_min", count: @opts[:min]) else - I18n.t("site_settings.errors.invalid_string_max", max: @opts[:max]) + I18n.t("site_settings.errors.invalid_string_max", count: @opts[:max]) end elsif @json_fail I18n.t("site_settings.errors.invalid_json") diff --git a/lib/validators/stripped_length_validator.rb b/lib/validators/stripped_length_validator.rb index ee79d2ca70a..2cef231b18f 100644 --- a/lib/validators/stripped_length_validator.rb +++ b/lib/validators/stripped_length_validator.rb @@ -8,7 +8,7 @@ class StrippedLengthValidator < ActiveModel::EachValidator record.errors.add attribute, I18n.t( "errors.messages.too_long_validation", - max: range.end, + count: range.end, length: value.length, ) else diff --git a/spec/lib/theme_settings_object_validator_spec.rb b/spec/lib/theme_settings_object_validator_spec.rb index 7d0e6be09a3..1f8c557a9f0 100644 --- a/spec/lib/theme_settings_object_validator_spec.rb +++ b/spec/lib/theme_settings_object_validator_spec.rb @@ -777,7 +777,7 @@ RSpec.describe ThemeSettingsObjectValidator do expect(errors.keys).to eq(["/tags_property"]) expect(errors["/tags_property"].full_messages).to contain_exactly( - "must have at least 1 tag names", + "must have at least 1 tag name", ) errors = @@ -912,7 +912,7 @@ RSpec.describe ThemeSettingsObjectValidator do expect(errors.keys).to eq(["/group_property"]) expect(errors["/group_property"].full_messages).to contain_exactly( - "must have at least 1 group ids", + "must have at least 1 group id", ) errors = @@ -1154,7 +1154,7 @@ RSpec.describe ThemeSettingsObjectValidator do expect(errors.keys).to eq(["/category_property"]) expect(errors["/category_property"].full_messages).to contain_exactly( - "must have at least 1 category ids", + "must have at least 1 category id", ) end diff --git a/spec/lib/theme_settings_validator_spec.rb b/spec/lib/theme_settings_validator_spec.rb index 5145114604a..61272d09cfc 100644 --- a/spec/lib/theme_settings_validator_spec.rb +++ b/spec/lib/theme_settings_validator_spec.rb @@ -29,7 +29,7 @@ RSpec.describe ThemeSettingsValidator do ) expect(errors).to contain_exactly( - "The property at JSON Pointer '/0/name' must be at most 1 characters long.", + "The property at JSON Pointer '/0/name' must be at most 1 character long.", ) end end diff --git a/spec/models/username_validator_spec.rb b/spec/models/username_validator_spec.rb index 4e68592ac67..cd90f1b5b6a 100644 --- a/spec/models/username_validator_spec.rb +++ b/spec/models/username_validator_spec.rb @@ -47,7 +47,7 @@ RSpec.describe UsernameValidator do expect_invalid( *usernames, - error_message: I18n.t(:"user.username.short", min: min_username_length), + error_message: I18n.t(:"user.username.short", count: min_username_length), ) end @@ -62,7 +62,7 @@ RSpec.describe UsernameValidator do expect_invalid( "a" * (max_username_length + 1), - error_message: I18n.t(:"user.username.long", max: max_username_length), + error_message: I18n.t(:"user.username.long", count: max_username_length), failure_reason: "Should be invalid as username length > #{max_username_length}", ) end @@ -154,7 +154,7 @@ RSpec.describe UsernameValidator do expect_invalid( *usernames, - error_message: I18n.t(:"user.username.short", min: min_username_length), + error_message: I18n.t(:"user.username.short", count: min_username_length), ) end @@ -170,7 +170,7 @@ RSpec.describe UsernameValidator do expect_invalid( "ם" * (max_username_length + 1), "äl" * (max_username_length + 1), - error_message: I18n.t(:"user.username.long", max: max_username_length), + error_message: I18n.t(:"user.username.long", count: max_username_length), failure_reason: "Should be invalid as username length are > #{max_username_length}", ) end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 8c33b553b8e..8547ee5ac12 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -1739,7 +1739,7 @@ RSpec.describe UsersController do body = response.parsed_body expect(body["errors"].first).to include( - I18n.t("user.username.short", min: User.username_length.begin), + I18n.t("user.username.short", count: User.username_length.begin), ) expect(user.reload.username).to eq(old_username) @@ -1893,7 +1893,7 @@ RSpec.describe UsersController do it 'should return the "too long" message' do expect(response.status).to eq(200) expect(response.parsed_body["errors"]).to include( - I18n.t(:"user.username.long", max: SiteSetting.max_username_length), + I18n.t(:"user.username.long", count: SiteSetting.max_username_length), ) end end