diff --git a/app/models/api_secret.rb b/app/models/api_secret.rb index d7bb85703..5c2703da0 100644 --- a/app/models/api_secret.rb +++ b/app/models/api_secret.rb @@ -14,6 +14,6 @@ class ApiSecret < ApplicationRecord def user_api_secret_count return if user && user.api_secrets.count < 20 - errors.add(:user, "API secret limit of 20 per user has been reached") + errors.add(:user, I18n.t("models.api_secret.api_limit_reached")) end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 8dd6494d9..66ce77a4b 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -60,7 +60,8 @@ class ApplicationRecord < ActiveRecord::Base return superclass.decorator_class(called_on) if superclass.respond_to?(:decorator_class) - raise UninferrableDecoratorError, "Could not infer a decorator for #{called_on.class.name}." + raise UninferrableDecoratorError, + I18n.t("models.application_record.uninferrable", class: called_on.class.name) end def self.statement_timeout diff --git a/app/models/article.rb b/app/models/article.rb index 973d51b59..998e1d0a5 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -29,10 +29,12 @@ class Article < ApplicationRecord # The date that we began limiting the number of user mentions in an article. MAX_USER_MENTION_LIVE_AT = Time.utc(2021, 4, 7).freeze - UNIQUE_URL_ERROR = "has already been taken. " \ - "Email #{ForemInstance.email} for further details.".freeze PROHIBITED_UNICODE_CHARACTERS_REGEX = /[\u202a-\u202e]/ # BIDI embedding controls + def self.unique_url_error + I18n.t("models.article.unique_url", email: ForemInstance.email) + end + has_one :discussion_lock, dependent: :delete has_many :mentions, as: :mentionable, inverse_of: :mentionable, dependent: :delete_all @@ -58,17 +60,20 @@ class Article < ApplicationRecord inverse_of: :commentable, class_name: "Comment" - validates :body_markdown, bytesize: { maximum: 800.kilobytes, too_long: "is too long." } + validates :body_markdown, bytesize: { + maximum: 800.kilobytes, + too_long: proc { I18n.t("models.article.is_too_long") } + } validates :body_markdown, length: { minimum: 0, allow_nil: false } validates :body_markdown, uniqueness: { scope: %i[user_id title] } validates :cached_tag_list, length: { maximum: 126 } validates :canonical_url, - uniqueness: { allow_nil: true, scope: :published, message: UNIQUE_URL_ERROR }, + uniqueness: { allow_nil: true, scope: :published, message: unique_url_error }, if: :published? validates :canonical_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] } validates :comments_count, presence: true validates :feed_source_url, - uniqueness: { allow_nil: true, scope: :published, message: UNIQUE_URL_ERROR }, + uniqueness: { allow_nil: true, scope: :published, message: unique_url_error }, if: :published? validates :feed_source_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] } validates :main_image, url: { allow_blank: true, schemes: %w[https http] } @@ -379,7 +384,7 @@ class Article < ApplicationRecord .tr("\n", " ") .strip else - "A post by #{user.name}" + I18n.t("models.article.a_post_by", user_name: user.name) end end @@ -434,18 +439,20 @@ class Article < ApplicationRecord return unless edited? if edited_at.year == Time.current.year - edited_at.strftime("%b %e") + I18n.l(edited_at, format: :short) else - edited_at.strftime("%b %e '%y") + I18n.l(edited_at, format: :short_with_yy) end end def readable_publish_date relevant_date = displayable_published_at - if relevant_date && relevant_date.year == Time.current.year - relevant_date&.strftime("%b %-e") - else - relevant_date&.strftime("%b %-e '%y") + return unless relevant_date + + if relevant_date.year == Time.current.year + I18n.l(relevant_date, format: :short) + elsif relevant_date + I18n.l(relevant_date, format: :short_with_yy) end end @@ -606,7 +613,7 @@ class Article < ApplicationRecord end def update_notifications - Notification.update_notifications(self, "Published") + Notification.update_notifications(self, I18n.t("models.article.published")) end def update_notification_subscriptions @@ -664,7 +671,7 @@ class Article < ApplicationRecord add_tag_adjustments_to_tag_list # check there are not too many tags - return errors.add(:tag_list, "exceed the maximum of 4 tags") if tag_list.size > 4 + return errors.add(:tag_list, I18n.t("models.article.too_many_tags")) if tag_list.size > 4 # check tags names aren't too long and don't contain non alphabet characters tag_list.each do |tag| @@ -691,48 +698,48 @@ class Article < ApplicationRecord def validate_video if published && video_state == "PROGRESSING" return errors.add(:published, - "cannot be set to true if video is still processing") + I18n.t("models.article.video_processing")) end return unless video.present? && user.created_at > 2.weeks.ago - errors.add(:video, "cannot be added by member without permission") + errors.add(:video, I18n.t("models.article.video_unpermitted")) end def validate_collection_permission return unless collection && collection.user_id != user_id - errors.add(:collection_id, "must be one you have permission to post to") + errors.add(:collection_id, I18n.t("models.article.series_unpermitted")) end def validate_co_authors return if co_author_ids.exclude?(user_id) - errors.add(:co_author_ids, "must not be the same user as the author") + errors.add(:co_author_ids, I18n.t("models.article.same_author")) end def validate_co_authors_must_not_be_the_same return if co_author_ids.uniq.count == co_author_ids.count - errors.add(:base, "co-author IDs must be unique") + errors.add(:base, I18n.t("models.article.unique_coauthor")) end def validate_co_authors_exist return if User.where(id: co_author_ids).count == co_author_ids.count - errors.add(:co_author_ids, "must be valid user IDs") + errors.add(:co_author_ids, I18n.t("models.article.invalid_coauthor")) end def past_or_present_date return unless published_at && published_at > Time.current - errors.add(:date_time, "must be entered in DD/MM/YYYY format with current or past date") + errors.add(:date_time, I18n.t("models.article.invalid_date")) end def canonical_url_must_not_have_spaces return unless canonical_url.to_s.match?(/[[:space:]]/) - errors.add(:canonical_url, "must not have spaces") + errors.add(:canonical_url, I18n.t("models.article.must_not_have_spaces")) end def user_mentions_in_markdown @@ -742,7 +749,8 @@ class Article < ApplicationRecord mentions_count = Nokogiri::HTML(processed_html).css(".mentioned-user").size return if mentions_count <= Settings::RateLimit.mention_creation - errors.add(:base, "You cannot mention more than #{Settings::RateLimit.mention_creation} users in a post!") + errors.add(:base, + I18n.t("models.article.mention_too_many", count: Settings::RateLimit.mention_creation)) end def create_slug diff --git a/app/models/broadcast.rb b/app/models/broadcast.rb index 6f5485e33..b017d1b2f 100644 --- a/app/models/broadcast.rb +++ b/app/models/broadcast.rb @@ -32,7 +32,7 @@ class Broadcast < ApplicationRecord type_of == "Announcement" && [nil, id].exclude?(first_broadcast.pick(:id)) - errors.add(:base, "You can only have one active announcement broadcast") + errors.add(:base, I18n.t("models.broadcast.single_active")) end def update_active_status_updated_at diff --git a/app/models/comment.rb b/app/models/comment.rb index 6b9e46418..f72dcfbb5 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -9,9 +9,6 @@ class Comment < ApplicationRecord COMMENTABLE_TYPES = %w[Article PodcastEpisode].freeze - TITLE_DELETED = "[deleted]".freeze - TITLE_HIDDEN = "[hidden by post author]".freeze - URI_REGEXP = %r{ \A (?:https?://)? # optional scheme @@ -59,7 +56,8 @@ class Comment < ApplicationRecord validates :reactions_count, presence: true validates :commentable, on: :create, presence: { message: lambda do |object, _data| - "#{object.commentable_type.presence || 'item'} has been deleted." + I18n.t("models.comment.has_been_deleted", + type: I18n.t("models.comment.type.#{object.commentable_type.presence || 'item'}")) end } @@ -97,6 +95,14 @@ class Comment < ApplicationRecord .to_h end + def self.title_deleted + I18n.t("models.comment.deleted") + end + + def self.title_hidden + I18n.t("models.comment.hidden") + end + def search_id "comment_#{id}" end @@ -134,8 +140,8 @@ class Comment < ApplicationRecord end def title(length = 80) - return TITLE_DELETED if deleted - return TITLE_HIDDEN if hidden_by_commentable_user + return self.class.title_deleted if deleted + return self.class.title_hidden if hidden_by_commentable_user text = ActionController::Base.helpers.strip_tags(processed_html).strip truncated_text = ActionController::Base.helpers.truncate(text, length: length).gsub("'", "'").gsub("&", "&") @@ -148,9 +154,9 @@ class Comment < ApplicationRecord def readable_publish_date if created_at.year == Time.current.year - created_at.strftime("%b %-e") + I18n.l(created_at, format: :short) else - created_at.strftime("%b %-e '%y") + I18n.l(created_at, format: :short_with_yy) end end @@ -315,11 +321,13 @@ class Comment < ApplicationRecord def discussion_not_locked return unless commentable_type == "Article" && commentable.discussion_lock - errors.add(:commentable_id, "the discussion is locked on this Post") + errors.add(:commentable_id, I18n.t("models.comment.locked")) end def published_article - errors.add(:commentable_id, "is not valid.") if commentable_type == "Article" && !commentable.published + return unless commentable_type == "Article" && !commentable.published + + errors.add(:commentable_id, I18n.t("models.comment.is_not_valid")) end def user_mentions_in_markdown @@ -329,7 +337,9 @@ class Comment < ApplicationRecord mentions_count = Nokogiri::HTML(processed_html).css(".mentioned-user").size return if mentions_count <= Settings::RateLimit.mention_creation - errors.add(:base, "You cannot mention more than #{Settings::RateLimit.mention_creation} users in a comment!") + errors.add(:base, + I18n.t("models.comment.mention_too_many", + count: Settings::RateLimit.mention_creation)) end def record_field_test_event diff --git a/app/models/feedback_message.rb b/app/models/feedback_message.rb index bd2261ab3..7a0c00d44 100644 --- a/app/models/feedback_message.rb +++ b/app/models/feedback_message.rb @@ -9,10 +9,13 @@ class FeedbackMessage < ApplicationRecord has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy REPORTER_UNIQUENESS_SCOPE = %i[reported_url feedback_type].freeze - REPORTER_UNIQUENESS_MSG = "(you) previously reported this URL.".freeze CATEGORIES = ["spam", "other", "rude or vulgar", "harassment", "bug", "listings"].freeze STATUSES = %w[Open Invalid Resolved].freeze + def self.reporter_uniqueness_msg + I18n.t("models.feedback_message.reported") + end + scope :open_abuse_reports, -> { where(status: "Open", feedback_type: "abuse-reports") } scope :all_user_reports, lambda { |user| user.reporter_feedback_messages @@ -31,7 +34,7 @@ class FeedbackMessage < ApplicationRecord inclusion: { in: STATUSES } - validates :reporter_id, uniqueness: { scope: REPORTER_UNIQUENESS_SCOPE, message: REPORTER_UNIQUENESS_MSG }, + validates :reporter_id, uniqueness: { scope: REPORTER_UNIQUENESS_SCOPE, message: reporter_uniqueness_msg }, if: :abuse_report? && :reporter_id def abuse_report? diff --git a/app/models/github_issue.rb b/app/models/github_issue.rb index a5c1c5b05..a923eded6 100644 --- a/app/models/github_issue.rb +++ b/app/models/github_issue.rb @@ -72,10 +72,10 @@ class GithubIssue < ApplicationRecord def error_message(url) if PATH_COMMENT_REGEXP.match?(url) _, issue_id = comment_repo_and_issue_id(url) - "Issue comment #{issue_id} not found" + I18n.t("models.github_issue.issue_comment_not_found", issue_id: issue_id) else _, issue_id = issue_or_pull_repo_and_issue_id(url) - "Issue #{issue_id} not found" + I18n.t("models.github_issue.issue_not_found", issue_id: issue_id) end end end diff --git a/app/models/html_variant.rb b/app/models/html_variant.rb index 35c5a3efb..95b450915 100644 --- a/app/models/html_variant.rb +++ b/app/models/html_variant.rb @@ -56,7 +56,7 @@ class HtmlVariant < ApplicationRecord return if group == "campaign" published_and_approved = (approved && (html_changed? || name_changed? || group_changed?)) && persisted? - errors.add(:base, "cannot change once published and approved") if published_and_approved + errors.add(:base, I18n.t("models.html_variant.no_edits")) if published_and_approved end def prefix_all_images diff --git a/app/models/identity.rb b/app/models/identity.rb index 055aea7db..80572ad3e 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -2,9 +2,6 @@ # :delete for the relationship. That means no before/after # destroy callbacks will be called on this object. class Identity < ApplicationRecord - NO_EMAIL_MSG = "No email found. Please relink your %s " \ - "account to avoid errors.".freeze - belongs_to :user scope :enabled, -> { where(provider: Authentication::Providers.enabled) } @@ -44,6 +41,6 @@ class Identity < ApplicationRecord end def email - auth_data_dump&.info&.email || format(NO_EMAIL_MSG, provider: provider) + auth_data_dump&.info&.email || I18n.t("models.identity.no_email_msg", provider: provider) end end diff --git a/app/models/listing.rb b/app/models/listing.rb index 971188dd0..c84e7e014 100644 --- a/app/models/listing.rb +++ b/app/models/listing.rb @@ -115,15 +115,14 @@ class Listing < ApplicationRecord def restrict_markdown_input markdown_string = body_markdown.to_s if markdown_string.scan(/(?=\n)/).count > 12 - errors.add(:body_markdown, - "has too many linebreaks. No more than 12 allowed.") + errors.add(:body_markdown, I18n.t("models.listing.too_many_linebreaks")) end - errors.add(:body_markdown, "is not allowed to include images.") if markdown_string.include?("![") - errors.add(:body_markdown, "is not allowed to include liquid tags.") if markdown_string.include?("{% ") + errors.add(:body_markdown, I18n.t("models.listing.image_not_allowed")) if markdown_string.include?("![") + errors.add(:body_markdown, I18n.t("models.listing.liquid_not_allowed")) if markdown_string.include?("{% ") end def validate_tags - errors.add(:tag_list, "exceed the maximum of 8 tags") if tag_list.length > 8 + errors.add(:tag_list, I18n.t("models.listing.too_many_tags")) if tag_list.length > 8 end def create_slug diff --git a/app/models/mention.rb b/app/models/mention.rb index 08813eae2..7890ee981 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -29,6 +29,6 @@ class Mention < ApplicationRecord end def permission - errors.add(:mentionable_id, "is not valid.") unless mentionable&.valid? + errors.add(:mentionable_id, I18n.t("models.mention.is_not_valid")) unless mentionable&.valid? end end diff --git a/app/models/navigation_link.rb b/app/models/navigation_link.rb index b12c5f05e..445526731 100644 --- a/app/models/navigation_link.rb +++ b/app/models/navigation_link.rb @@ -42,7 +42,7 @@ class NavigationLink < ApplicationRecord # @see NavigationLink.create_or_update_by_identity # @see NavigationLink#strip_local_hostname def self.normalize_url(url) - parsed_url = URI.parse(url) + parsed_url = Addressable::URI.parse(url) return url unless url.match?(/^#{URL.url}/i) parsed_url.path @@ -53,10 +53,10 @@ class NavigationLink < ApplicationRecord # We want to allow relative URLs (e.g. /contact) for navigation links while # still going through the normal validation process. def allow_relative_url - parsed_url = URI.parse(url) + parsed_url = Addressable::URI.parse(url) return unless parsed_url.relative? && url.starts_with?("/") - self.url = URI.parse(URL.url).merge(parsed_url).to_s + self.url = Addressable::URI.parse(URL.url).join(parsed_url).to_s end # When persisting to the database we store local links as relative URLs which diff --git a/app/models/organization.rb b/app/models/organization.rb index a77a3b169..ca4895632 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -6,10 +6,6 @@ class Organization < ApplicationRecord COLOR_HEX_REGEXP = /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/ INTEGER_REGEXP = /\A\d+\z/ SLUG_REGEXP = /\A[a-zA-Z0-9\-_]+\z/ - MESSAGES = { - integer_only: "Integer only. No sign allowed.", - reserved_word: "%s is a reserved word. Contact site admins for help registering your organization." - }.freeze acts_as_followable @@ -44,7 +40,7 @@ class Organization < ApplicationRecord validates :articles_count, presence: true validates :bg_color_hex, format: COLOR_HEX_REGEXP, allow_blank: true - validates :company_size, format: { with: INTEGER_REGEXP, message: MESSAGES[:integer_only], allow_blank: true } + validates :company_size, format: { with: INTEGER_REGEXP, message: :integer_only, allow_blank: true } validates :company_size, length: { maximum: 7 }, allow_nil: true validates :credits_count, presence: true validates :cta_body_markdown, length: { maximum: 256 } @@ -57,7 +53,7 @@ class Organization < ApplicationRecord validates :proof, length: { maximum: 1500 } validates :secret, length: { is: 100 }, allow_nil: true validates :secret, uniqueness: true - validates :slug, exclusion: { in: ReservedWords.all, message: MESSAGES[:reserved_word] } + validates :slug, exclusion: { in: ReservedWords.all, message: :reserved_word } validates :slug, format: { with: SLUG_REGEXP }, length: { in: 2..18 } validates :slug, presence: true, uniqueness: { case_sensitive: false } validates :spent_credits_count, presence: true @@ -80,6 +76,14 @@ class Organization < ApplicationRecord alias_attribute :old_old_username, :old_old_slug alias_attribute :website_url, :url + def self.integer_only + I18n.t("models.organization.integer_only") + end + + def self.reserved_word + I18n.t("models.organization.reserved_word") + end + def check_for_slug_change return unless slug_changed? diff --git a/app/models/page.rb b/app/models/page.rb index 8ada58bda..9289f8fd3 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -75,7 +75,7 @@ class Page < ApplicationRecord def body_present return unless body_markdown.blank? && body_html.blank? && body_json.blank? - errors.add(:body_markdown, "must exist if body_html or body_json doesn't exist.") + errors.add(:body_markdown, I18n.t("models.page.body_must_exist")) end # As there can only be one global landing page, we want to ensure that diff --git a/app/models/podcast.rb b/app/models/podcast.rb index 273731c69..65d04389d 100644 --- a/app/models/podcast.rb +++ b/app/models/podcast.rb @@ -21,7 +21,7 @@ class Podcast < ApplicationRecord presence: true, uniqueness: true, format: { with: /\A[a-zA-Z0-9\-_]+\Z/ }, - exclusion: { in: ReservedWords.all, message: "slug is reserved" } + exclusion: { in: ReservedWords.all, message: I18n.t("models.podcast.slug_is_reserved") } validates :slug, unique_cross_model_slug: true, if: :slug_changed? diff --git a/app/models/podcast_episode_appearance.rb b/app/models/podcast_episode_appearance.rb index 7c4d6f5ac..09e63b03f 100644 --- a/app/models/podcast_episode_appearance.rb +++ b/app/models/podcast_episode_appearance.rb @@ -6,5 +6,7 @@ class PodcastEpisodeAppearance < ApplicationRecord belongs_to :podcast_episode validates :podcast_episode_id, uniqueness: { scope: :user_id } validates :role, presence: true - validates :role, inclusion: { in: %w[host guest], message: "provided role is not valid" } + validates :role, + inclusion: { in: %w[host guest], + message: I18n.t("models.podcast_episode_appearance.provided_role_is_not_valid") } end diff --git a/app/models/poll_skip.rb b/app/models/poll_skip.rb index 0068c7355..7a65121c6 100644 --- a/app/models/poll_skip.rb +++ b/app/models/poll_skip.rb @@ -17,6 +17,6 @@ class PollSkip < ApplicationRecord return false unless poll return false unless poll.vote_previously_recorded_for?(user_id: user_id) - errors.add(:base, "cannot vote more than once in one poll") + errors.add(:base, I18n.t("models.poll_skip.cannot_vote_more_than_once")) end end diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb index 58ad0a1da..2c145e21c 100644 --- a/app/models/poll_vote.rb +++ b/app/models/poll_vote.rb @@ -30,7 +30,7 @@ class PollVote < ApplicationRecord return false unless poll return false unless poll.vote_previously_recorded_for?(user_id: user_id) - errors.add(:base, "cannot vote more than once in one poll") + errors.add(:base, I18n.t("models.poll_vote.cannot_vote_more_than_once")) end def touch_poll_votes_count diff --git a/app/models/profile_pin.rb b/app/models/profile_pin.rb index 8312dc61b..a952ee8fd 100644 --- a/app/models/profile_pin.rb +++ b/app/models/profile_pin.rb @@ -14,10 +14,10 @@ class ProfilePin < ApplicationRecord private def only_five_pins_per_profile - errors.add(:base, "cannot have more than five total pinned posts") if profile.profile_pins.size > 4 + errors.add(:base, I18n.t("models.profile_pin.only_five")) if profile.profile_pins.size > 4 end def pinnable_belongs_to_profile - errors.add(:pinnable_id, "must have proper permissions for pin") if pinnable.user_id != profile_id + errors.add(:pinnable_id, I18n.t("models.profile_pin.pin_unpermitted")) if pinnable.user_id != profile_id end end diff --git a/app/models/rating_vote.rb b/app/models/rating_vote.rb index adc41f2ea..d5fed1e7f 100644 --- a/app/models/rating_vote.rb +++ b/app/models/rating_vote.rb @@ -22,8 +22,8 @@ class RatingVote < ApplicationRecord end def permissions - return if user == article&.user || user&.trusted? || context != "explicit" + return unless context == "explicit" && !user&.trusted && user_id != article&.user_id - errors.add(:user_id, "is not permitted to take this action.") + errors.add(:user_id, I18n.t("models.rating_vote.not_permitted")) end end diff --git a/app/models/reaction.rb b/app/models/reaction.rb index ee69393ab..ed8ef0dd0 100644 --- a/app/models/reaction.rb +++ b/app/models/reaction.rb @@ -178,9 +178,10 @@ class Reaction < ApplicationRecord end def permissions - errors.add(:category, "is not valid.") if negative_reaction_from_untrusted_user? + errors.add(:category, I18n.t("models.reaction.is_not_valid")) if negative_reaction_from_untrusted_user? + return unless reactable_type == "Article" && !reactable&.published - errors.add(:reactable_id, "is not valid.") if reactable_type == "Article" && !reactable&.published + errors.add(:reactable_id, I18n.t("models.reaction.is_not_valid")) end def negative_reaction_from_untrusted_user? diff --git a/app/models/response_template.rb b/app/models/response_template.rb index efa0e24ff..8e2c2e398 100644 --- a/app/models/response_template.rb +++ b/app/models/response_template.rb @@ -11,19 +11,18 @@ class ResponseTemplate < ApplicationRecord CONTENT_TYPES = %w[plain_text html body_markdown].freeze COMMENT_CONTENT_TYPE = %w[body_markdown].freeze EMAIL_CONTENT_TYPES = %w[plain_text html].freeze - COMMENT_VALIDATION_MSG = "Comment templates must use Markdown as its content type.".freeze - EMAIL_VALIDATION_MSG = "Email templates must use plain text or HTML as its content type.".freeze - USER_NIL_TYPE_OF_MSG = "cannot have a user ID associated.".freeze validates :type_of, :content_type, :content, :title, presence: true validates :content, uniqueness: { scope: %i[user_id type_of content_type] } validates :type_of, inclusion: { in: TYPE_OF_TYPES } validates :content_type, inclusion: { in: CONTENT_TYPES } validates :content_type, - inclusion: { in: COMMENT_CONTENT_TYPE, message: COMMENT_VALIDATION_MSG }, + inclusion: { in: COMMENT_CONTENT_TYPE, + message: proc { I18n.t("models.response_template.comment_markdown") } }, if: -> { type_of&.include?("comment") } validates :content_type, - inclusion: { in: EMAIL_CONTENT_TYPES, message: EMAIL_VALIDATION_MSG }, + inclusion: { in: EMAIL_CONTENT_TYPES, + message: proc { I18n.t("models.response_template.email_text") } }, if: -> { type_of&.include?("email") } validate :user_nil_only_for_user_nil_types validate :template_count @@ -33,13 +32,13 @@ class ResponseTemplate < ApplicationRecord def user_nil_only_for_user_nil_types return unless user_id.present? && USER_NIL_TYPE_OF_TYPES.include?(type_of) - errors.add(:type_of, USER_NIL_TYPE_OF_MSG) + errors.add(:type_of, I18n.t("models.response_template.user_nil_only")) end def template_count return unless user - return if user.trusted? || user.response_templates.count <= 30 + return if user.trusted || user.response_templates.count <= 30 - errors.add(:user, "Response template limit of 30 per user has been reached") + errors.add(:user, I18n.t("models.response_template.limit_reached")) end end diff --git a/app/models/settings/campaign.rb b/app/models/settings/campaign.rb index 35480c865..c25a19685 100644 --- a/app/models/settings/campaign.rb +++ b/app/models/settings/campaign.rb @@ -5,7 +5,7 @@ module Settings # Define your settings setting :articles_expiry_time, type: :integer, default: 4 setting :articles_require_approval, type: :boolean, default: 0 - setting :call_to_action, type: :string, default: "Share your project" + setting :call_to_action, type: :string, default: -> { I18n.t("models.settings.campaign.share_your_project") } setting :featured_tags, type: :array, default: %w[] setting :hero_html_variant_name, type: :string, default: "" setting :sidebar_enabled, type: :boolean, default: 0 diff --git a/app/models/settings/community.rb b/app/models/settings/community.rb index 23d941735..0b7f4aa3b 100644 --- a/app/models/settings/community.rb +++ b/app/models/settings/community.rb @@ -10,11 +10,11 @@ module Settings setting( :community_name, type: :string, - default: ApplicationConfig["COMMUNITY_NAME"] || "New Forem", + default: ApplicationConfig["COMMUNITY_NAME"] || I18n.t("models.settings.community.new_forem"), validates: { format: { with: /\A[^[<|>]]+\Z/, - message: "may not include the \"<\" nor \">\" character" + message: I18n.t("models.settings.community.message") } }, ) diff --git a/app/models/settings/general.rb b/app/models/settings/general.rb index fa26d5083..8cb8fa790 100644 --- a/app/models/settings/general.rb +++ b/app/models/settings/general.rb @@ -48,7 +48,9 @@ module Settings type: :string, default: proc { URL.local_image("mascot.png") }, validates: { url: true } - setting :mascot_image_description, type: :string, default: "The community mascot" + setting :mascot_image_description, type: :string, default: lambda { + I18n.t("models.settings.general.the_community_mascot") + } setting :mascot_footer_image_url, type: :string, validates: { url: true } setting :mascot_footer_image_width, type: :integer, default: 52 setting :mascot_footer_image_height, type: :integer, default: 120 @@ -93,7 +95,7 @@ module Settings setting :twitter_hashtag, type: :string # Sponsors - setting :sponsor_headline, default: "Community Sponsors" + setting :sponsor_headline, default: -> { I18n.t("models.settings.general.community_sponsors") } # Tags setting :sidebar_tags, type: :array, default: %w[] diff --git a/app/models/settings/user_experience.rb b/app/models/settings/user_experience.rb index 65bb5eb53..334d5817d 100644 --- a/app/models/settings/user_experience.rb +++ b/app/models/settings/user_experience.rb @@ -21,7 +21,7 @@ module Settings setting :primary_brand_color_hex, type: :string, default: "#3b49df", validates: { format: { with: HEX_COLOR_REGEX, - message: "must be be a 3 or 6 character hex (starting with #)" + message: proc { I18n.t("models.settings.user_experience.message") } }, color_contrast: true } diff --git a/app/models/sponsorship.rb b/app/models/sponsorship.rb index 976d5977c..a5361c120 100644 --- a/app/models/sponsorship.rb +++ b/app/models/sponsorship.rb @@ -23,7 +23,7 @@ class Sponsorship < ApplicationRecord validates :sponsorable_type, inclusion: { in: SPONSORABLE_TYPES, allow_blank: true, - message: "is not a sponsorable type" + message: I18n.t("models.sponsorship.invalid_type") } validate :validate_tag_uniqueness, if: proc { level.to_s == "tag" } @@ -44,13 +44,14 @@ class Sponsorship < ApplicationRecord return unless self.class.where(sponsorable: sponsorable, level: :tag) .exists?(["expires_at > ? AND id != ?", Time.current, id.to_i]) - errors.add(:level, "The tag is already sponsored") + errors.add(:level, I18n.t("models.sponsorship.already_sponsored")) end def validate_level_uniqueness return unless self.class.where(organization: organization) .exists?(["level IN (?) AND expires_at > ? AND id != ?", METAL_LEVELS, Time.current, id.to_i]) - errors.add(:level, "You can have only one sponsorship of #{METAL_LEVELS.join(', ')}") + levels = METAL_LEVELS.map { |l| I18n.t("models.sponsorship.level.#{l}") }.to_sentence(locale: I18n.locale) + errors.add(:level, I18n.t("models.sponsorship.only_one_level", levels: levels)) end end diff --git a/app/models/tag.rb b/app/models/tag.rb index d153bd271..933a3c2a4 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -119,7 +119,7 @@ class Tag < ActsAsTaggableOn::Tag end def validate_name - errors.add(:name, "is too long (maximum is 30 characters)") if name.length > 30 + errors.add(:name, I18n.t("errors.messages.too_long", count: 30)) if name.length > 30 # [:alnum:] is not used here because it supports diacritical characters. # If we decide to allow diacritics in the future, we should replace the # following regex with [:alnum:]. @@ -258,7 +258,7 @@ class Tag < ActsAsTaggableOn::Tag def validate_alias_for return if Tag.exists?(name: alias_for) - errors.add(:tag, "alias_for must refer to an existing tag") + errors.add(:tag, I18n.t("models.tag.alias_for")) end def pound_it diff --git a/app/models/tag_adjustment.rb b/app/models/tag_adjustment.rb index 9b18bfcc4..79a998814 100644 --- a/app/models/tag_adjustment.rb +++ b/app/models/tag_adjustment.rb @@ -1,5 +1,6 @@ class TagAdjustment < ApplicationRecord - validates :tag_name, presence: true, uniqueness: { scope: :article_id, message: "can't be an already adjusted tag" } + validates :tag_name, presence: true, + uniqueness: { scope: :article_id, message: I18n.t("models.tag_adjustment.unique") } validates :reason_for_adjustment, presence: true validates :adjustment_type, inclusion: { in: %w[removal addition] }, presence: true validates :status, inclusion: { in: %w[committed pending committed_and_resolvable resolved] }, presence: true @@ -14,7 +15,7 @@ class TagAdjustment < ApplicationRecord private def user_permissions - errors.add(:user_id, "does not have privilege to adjust these tags") unless has_privilege_to_adjust? + errors.add(:user_id, I18n.t("models.tag_adjustment.unpermitted")) unless has_privilege_to_adjust? end def has_privilege_to_adjust? @@ -28,8 +29,10 @@ class TagAdjustment < ApplicationRecord tag.casecmp(tag_name).zero? end errors.add(:tag_id, - "selected for removal is not a current live tag.") + I18n.t("models.tag_adjustment.not_live")) end - errors.add(:base, "4 tags max per article.") if adjustment_type == "addition" && article.tag_list.count > 3 + return unless adjustment_type == "addition" && article.tag_list.count > 3 + + errors.add(:base, I18n.t("models.tag_adjustment.too_many_tags")) end end diff --git a/app/models/tweet.rb b/app/models/tweet.rb index 2966dd5f3..f5724e431 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -47,7 +47,7 @@ class Tweet < ApplicationRecord def fetch(status_id) retrieve_and_save_tweet(status_id) rescue TwitterClient::Errors::NotFound => e - raise e, "Tweet not found" + raise e, I18n.t("models.tweet.tweet_not_found") end def retrieve_and_save_tweet(status_id) diff --git a/app/models/user.rb b/app/models/user.rb index feb95642a..ba67f822a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,9 +26,6 @@ class User < ApplicationRecord USERNAME_MAX_LENGTH = 30 USERNAME_REGEXP = /\A[a-zA-Z0-9_]+\z/ - MESSAGES = { - reserved_username: "username is reserved" - }.freeze # follow the syntax in https://interledger.org/rfcs/0026-payment-pointers/#payment-pointer-syntax PAYMENT_POINTER_REGEXP = %r{ \A # start @@ -141,9 +138,12 @@ class User < ApplicationRecord validates :subscribed_to_user_subscriptions_count, presence: true validates :unspent_credits_count, presence: true validates :username, length: { in: 2..USERNAME_MAX_LENGTH }, format: USERNAME_REGEXP - validates :username, presence: true, exclusion: { in: ReservedWords.all, message: MESSAGES[:invalid_username] } + validates :username, presence: true, exclusion: { + in: ReservedWords.all, + message: proc { I18n.t("models.user.username_is_reserved") } + } validates :username, uniqueness: { case_sensitive: false, message: lambda do |_obj, data| - "#{data[:value]} is taken." + I18n.t("models.user.is_taken", username: (data[:value])) end }, if: :username_changed? # add validators for provider related usernames @@ -483,7 +483,7 @@ class User < ApplicationRecord end def non_banished_username - errors.add(:username, "has been banished.") if BanishedUser.exists?(username: username) + errors.add(:username, I18n.t("models.user.has_been_banished")) if BanishedUser.exists?(username: username) end def subscribe_to_mailchimp_newsletter @@ -662,7 +662,7 @@ class User < ApplicationRecord rate_limiter.track_limit_by_action(:send_email_confirmation) rate_limiter.check_limit!(:send_email_confirmation) rescue RateLimitChecker::LimitReached => e - errors.add(:email, "confirmation could not be sent. #{e.message}") + errors.add(:email, I18n.t("models.user.could_not_send", e_message: e.message)) end def update_rate_limit @@ -671,13 +671,13 @@ class User < ApplicationRecord rate_limiter.track_limit_by_action(:user_update) rate_limiter.check_limit!(:user_update) rescue RateLimitChecker::LimitReached => e - errors.add(:base, "User could not be saved. #{e.message}") + errors.add(:base, I18n.t("models.user.user_could_not_be_saved", e_message: e.message)) end def password_matches_confirmation return true if password == password_confirmation - errors.add(:password, "doesn't match password confirmation") + errors.add(:password, I18n.t("models.user.password_not_matched")) end def strip_payment_pointer diff --git a/app/models/user_block.rb b/app/models/user_block.rb index d11f467a8..0eec8f42b 100644 --- a/app/models/user_block.rb +++ b/app/models/user_block.rb @@ -30,7 +30,7 @@ class UserBlock < ApplicationRecord private def blocker_cannot_be_same_as_blocked - errors.add(:blocker_id, "can't be the same as the blocked_id") if blocker_id == blocked_id + errors.add(:blocker_id, I18n.t("models.user_block.cant_be_the_same")) if blocker_id == blocked_id end def bust_blocker_cache diff --git a/app/models/user_subscription.rb b/app/models/user_subscription.rb index 6e025e36f..1c31d63c6 100644 --- a/app/models/user_subscription.rb +++ b/app/models/user_subscription.rb @@ -52,7 +52,7 @@ class UserSubscription < ApplicationRecord return unless user_subscription_sourceable return if liquid_tags_used.include?(UserSubscriptionTag) - errors.add(:base, "User subscriptions are not enabled for the source.") + errors.add(:base, I18n.t("models.user_subscription.not_enabled")) end def liquid_tags_used @@ -68,7 +68,7 @@ class UserSubscription < ApplicationRecord def non_apple_auth_subscriber return unless subscriber_email&.end_with?("@privaterelay.appleid.com") - errors.add(:subscriber_email, "Can't subscribe with an Apple private relay. Please update email.") + errors.add(:subscriber_email, I18n.t("models.user_subscription.non_apple")) end def active_user_subscription_source @@ -88,6 +88,6 @@ class UserSubscription < ApplicationRecord return if source_active - errors.add(:base, "Source not found. Please make sure your #{user_subscription_sourceable_type} is active!") + errors.add(:base, I18n.t("models.user_subscription.source_not_found", source: user_subscription_sourceable_type)) end end diff --git a/app/models/users/deleted_user.rb b/app/models/users/deleted_user.rb index 72824bcd0..7a77653c0 100644 --- a/app/models/users/deleted_user.rb +++ b/app/models/users/deleted_user.rb @@ -16,7 +16,7 @@ module Users def self.id() = nil def self.darker_color() = Color::CompareHex.new(USER_COLORS).brightness def self.username() = "[deleted user]" - def self.name() = "[Deleted User]" + def self.name() = I18n.t("models.users.deleted_user.name") def self.summary() = nil def self.twitter_username() = nil def self.github_username() = nil diff --git a/app/models/users/setting.rb b/app/models/users/setting.rb index 91a7174b5..d6902a6f3 100644 --- a/app/models/users/setting.rb +++ b/app/models/users/setting.rb @@ -22,7 +22,8 @@ module Users validates :brand_color1, :brand_color2, - format: { with: HEX_COLOR_REGEXP, message: "is not a valid hex color" }, + format: { with: HEX_COLOR_REGEXP, + message: I18n.t("models.users.setting.invalid_hex") }, allow_nil: true validates :experience_level, numericality: { less_than_or_equal_to: 10 }, allow_blank: true validates :feed_referential_link, inclusion: { in: [true, false] } @@ -42,7 +43,7 @@ module Users valid = Feeds::ValidateUrl.call(feed_url) - errors.add(:feed_url, "is not a valid RSS/Atom feed") unless valid + errors.add(:feed_url, I18n.t("models.users.setting.invalid_rss")) unless valid rescue StandardError => e errors.add(:feed_url, e.message) end diff --git a/app/services/spam/handler.rb b/app/services/spam/handler.rb index a0b794bbc..ee22ee0d2 100644 --- a/app/services/spam/handler.rb +++ b/app/services/spam/handler.rb @@ -69,7 +69,7 @@ module Spam author_id: Settings::General.mascot_user_id, noteable: user, reason: "automatic_suspend", - content: "User suspended for too many spammy interactions, triggered by autovomit.", + content: I18n.t("models.comment.suspended_too_many"), ) end private_class_method :suspend! diff --git a/app/views/api/v0/comments/_comment.json.jbuilder b/app/views/api/v0/comments/_comment.json.jbuilder index d25ff827f..0a4ccc745 100644 --- a/app/views/api/v0/comments/_comment.json.jbuilder +++ b/app/views/api/v0/comments/_comment.json.jbuilder @@ -3,10 +3,10 @@ json.id_code comment.id_code_generated json.created_at utc_iso_timestamp(comment.created_at) if comment.deleted? - json.body_html "

#{Comment::TITLE_DELETED}

" + json.body_html "

#{Comment.title_deleted}

" json.set! :user, {} elsif comment.hidden_by_commentable_user? - json.body_html "

#{Comment::TITLE_HIDDEN}

" + json.body_html "

#{Comment.title_hidden}

" json.set! :user, {} else json.body_html comment.processed_html diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml new file mode 100644 index 000000000..47e01aec1 --- /dev/null +++ b/config/locales/models/en.yml @@ -0,0 +1,131 @@ +--- +en: + models: + api_secret: + api_limit_reached: API secret limit of 20 per user has been reached + application_record: + uninferrable: Could not infer a decorator for %{class}. + article: + a_post_by: A post by %{user_name} + video_unpermitted: cannot be added by member without permission + video_processing: cannot be set to true if video is still processing + cannot_search_tags_for: 'Cannot search tags for: %{tag}' + cannot_search_tags_for_tags: 'Cannot search tags for: %{tags}' + unique_coauthor: co-author IDs must be unique + too_many_tags: exceed the maximum of 4 tags + is_too_long: is too long. + invalid_date: must be entered in DD/MM/YYYY format with current or past date + series_unpermitted: must be one you have permission to post to + invalid_coauthor: must be valid user IDs + same_author: must not be the same user as the author + must_not_have_spaces: must not have spaces + published: Published + unique_url: has already been taken. Email %{email} for further details. + mention_too_many: + one: You cannot mention more than %{count} users in a post! + other: You cannot mention more than %{count} users in a post! + broadcast: + single_active: You can only have one active announcement broadcast + comment: + deleted: "[deleted]" + has_been_deleted: "%{type} has been deleted." + hidden: "[hidden by post author]" + is_not_valid: is not valid. + locked: the discussion is locked on this Post + type: + Article: Article + item: item + PodcastEpisode: Podcast episode + suspended_too_many: User suspended for too many spammy interactions, triggered by autovomit. + mention_too_many: + one: You cannot mention more than %{count} users in a comment! + other: You cannot mention more than %{count} users in a comment! + feedback_message: + reported: "(you) previously reported this URL." + github_issue: + issue_comment_not_found: Issue comment %{issue_id} not found + issue_not_found: Issue %{issue_id} not found + html_variant: + no_edits: cannot change once published and approved + identity: + no_email_msg: No email found. Please relink your %{provider} account to avoid errors. + listing: + too_many_tags: exceed the maximum of 8 tags + too_many_linebreaks: has too many linebreaks. No more than 12 allowed. + image_not_allowed: is not allowed to include images. + liquid_not_allowed: is not allowed to include liquid tags. + mention: + is_not_valid: is not valid. + organization: + integer_only: Integer only. No sign allowed. + reserved_word: "%s is a reserved word. Contact site admins for help registering your organization." + page: + body_must_exist: must exist if body_html or body_json doesn't exist. + podcast: + slug_is_reserved: slug is reserved + podcast_episode_appearance: + provided_role_is_not_valid: provided role is not valid + poll_skip: + cannot_vote_more_than_once: cannot vote more than once in one poll + poll_vote: + cannot_vote_more_than_once: cannot vote more than once in one poll + profile_pin: + only_five: cannot have more than five total pinned posts + pin_unpermitted: must have proper permissions for pin + rating_vote: + not_permitted: is not permitted to take this action. + reaction: + is_not_valid: is not valid. + response_template: + user_nil_only: cannot have a user ID associated. + comment_markdown: Comment templates must use Markdown as its content type. + email_text: Email templates must use plain text or HTML as its content type. + limit_reached: Response template limit of 30 per user has been reached + settings: + campaign: + share_your_project: Share your project + community: + message: may not include the "<" nor ">" character + new_forem: New Forem + user: user + general: + community_sponsors: Community Sponsors + the_community_mascot: The community mascot + user_experience: + message: 'must be be a 3 or 6 character hex (starting with #)' + sponsorship: + invalid_type: is not a sponsorable type + already_sponsored: The tag is already sponsored + only_one_level: You can have only one sponsorship of %{levels} + level: + gold: gold + silver: silver + bronze: bronze + tag: + alias_for: alias_for must refer to an existing tag + tag_adjustment: + too_many_tags: 4 tags max per article. + unpermitted: does not have privilege to adjust these tags + not_live: selected for removal is not a current live tag. + unique: can't be an already adjusted tag + tweet: + tweet_not_found: Tweet not found + user: + could_not_send: confirmation could not be sent. %{e_message} + password_not_matched: doesn't match password confirmation + has_been_banished: has been banished. + is_taken: "%{username} is taken." + user_could_not_be_saved: User could not be saved. %{e_message} + username_is_reserved: username is reserved + user_block: + cant_be_the_same: can't be the same as the blocked_id + user_subscription: + non_apple: Can't subscribe with an Apple private relay. Please update email. + source_not_found: Source not found. Please make sure your %{source} is active! + not_enabled: User subscriptions are not enabled for the source. + users: + deleted_user: + name: "[Deleted User]" + setting: + invalid_hex: is not a valid hex color + invalid_rss: is not a valid RSS/Atom feed diff --git a/config/locales/models/fr.yml b/config/locales/models/fr.yml new file mode 100644 index 000000000..b058d8344 --- /dev/null +++ b/config/locales/models/fr.yml @@ -0,0 +1,131 @@ +--- +fr: + models: + api_secret: + api_limit_reached: API secret limit of 20 per user has been reached + application_record: + uninferrable: Could not infer a decorator for %{class}. + article: + a_post_by: A post by %{user_name} + video_unpermitted: cannot be added by member without permission + video_processing: cannot be set to true if video is still processing + cannot_search_tags_for: 'Cannot search tags for: %{tag}' + cannot_search_tags_for_tags: 'Cannot search tags for: %{tags}' + unique_coauthor: co-author IDs must be unique + too_many_tags: exceed the maximum of 4 tags + is_too_long: is too long. + invalid_date: must be entered in DD/MM/YYYY format with current or past date + series_unpermitted: must be one you have permission to post to + invalid_coauthor: must be valid user IDs + same_author: must not be the same user as the author + must_not_have_spaces: must not have spaces + published: Published + unique_url: has already been taken. Email %{email} for further details. + mention_too_many: + one: You cannot mention more than %{count} users in a post! + other: You cannot mention more than %{count} users in a post! + broadcast: + single_active: You can only have one active announcement broadcast + comment: + deleted: "[deleted]" + has_been_deleted: "%{type} has been deleted." + hidden: "[hidden by post author]" + is_not_valid: is not valid. + locked: the discussion is locked on this Post + type: + Article: Article + item: item + PodcastEpisode: Podcast episode + suspended_too_many: User suspended for too many spammy interactions, triggered by autovomit. + mention_too_many: + one: You cannot mention more than %{count} users in a comment! + other: You cannot mention more than %{count} users in a comment! + feedback_message: + reported: "(you) previously reported this URL." + github_issue: + issue_comment_not_found: Issue comment %{issue_id} not found + issue_not_found: Issue %{issue_id} not found + html_variant: + no_edits: cannot change once published and approved + identity: + no_email_msg: No email found. Please relink your %{provider} account to avoid errors. + listing: + too_many_tags: exceed the maximum of 8 tags + too_many_linebreaks: has too many linebreaks. No more than 12 allowed. + image_not_allowed: is not allowed to include images. + liquid_not_allowed: is not allowed to include liquid tags. + mention: + is_not_valid: is not valid. + organization: + integer_only: Integer only. No sign allowed. + reserved_word: "%s is a reserved word. Contact site admins for help registering your organization." + page: + body_must_exist: must exist if body_html or body_json doesn't exist. + podcast: + slug_is_reserved: slug is reserved + podcast_episode_appearance: + provided_role_is_not_valid: provided role is not valid + poll_skip: + cannot_vote_more_than_once: cannot vote more than once in one poll + poll_vote: + cannot_vote_more_than_once: cannot vote more than once in one poll + profile_pin: + only_five: cannot have more than five total pinned posts + pin_unpermitted: must have proper permissions for pin + rating_vote: + not_permitted: is not permitted to take this action. + reaction: + is_not_valid: is not valid. + response_template: + user_nil_only: cannot have a user ID associated. + comment_markdown: Comment templates must use Markdown as its content type. + email_text: Email templates must use plain text or HTML as its content type. + limit_reached: Response template limit of 30 per user has been reached + settings: + campaign: + share_your_project: Share your project + community: + message: may not include the "<" nor ">" character + new_forem: New Forem + user: user + general: + community_sponsors: Community Sponsors + the_community_mascot: The community mascot + user_experience: + message: 'must be be a 3 or 6 character hex (starting with #)' + sponsorship: + invalid_type: is not a sponsorable type + already_sponsored: The tag is already sponsored + only_one_level: You can have only one sponsorship of %{levels} + level: + gold: gold + silver: silver + bronze: bronze + tag: + alias_for: alias_for must refer to an existing tag + tag_adjustment: + too_many_tags: 4 tags max per article. + unpermitted: does not have privilege to adjust these tags + not_live: selected for removal is not a current live tag. + unique: can't be an already adjusted tag + tweet: + tweet_not_found: Tweet not found + user: + could_not_send: confirmation could not be sent. %{e_message} + password_not_matched: doesn't match password confirmation + has_been_banished: has been banished. + is_taken: "%{username} is taken." + user_could_not_be_saved: User could not be saved. %{e_message} + username_is_reserved: username is reserved + user_block: + cant_be_the_same: can't be the same as the blocked_id + user_subscription: + non_apple: Can't subscribe with an Apple private relay. Please update email. + source_not_found: Source not found. Please make sure your %{source} is active! + not_enabled: User subscriptions are not enabled for the source. + users: + deleted_user: + name: "[Deleted User]" + setting: + invalid_hex: is not a valid hex color + invalid_rss: is not a valid RSS/Atom feed diff --git a/spec/models/feedback_message_spec.rb b/spec/models/feedback_message_spec.rb index 5f4cbcf2c..d410302e8 100644 --- a/spec/models/feedback_message_spec.rb +++ b/spec/models/feedback_message_spec.rb @@ -39,7 +39,7 @@ RSpec.describe FeedbackMessage, type: :model do it do expect(feedback_message).to validate_uniqueness_of(:reporter_id) .scoped_to(described_class::REPORTER_UNIQUENESS_SCOPE) - .with_message(described_class::REPORTER_UNIQUENESS_MSG) + .with_message(described_class.reporter_uniqueness_msg) end it { is_expected.to validate_length_of(:reported_url).is_at_most(250) } diff --git a/spec/models/response_template_spec.rb b/spec/models/response_template_spec.rb index 72ba11a80..e3d02f3d6 100644 --- a/spec/models/response_template_spec.rb +++ b/spec/models/response_template_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe ResponseTemplate, type: :model do - let(:comment_validation_message) { ResponseTemplate::COMMENT_VALIDATION_MSG } + let(:comment_validation_message) { I18n.t("models.response_template.comment_markdown") } it { is_expected.to validate_inclusion_of(:type_of).in_array(ResponseTemplate::TYPE_OF_TYPES) } it { is_expected.to validate_inclusion_of(:content_type).in_array(ResponseTemplate::CONTENT_TYPES) } @@ -25,7 +25,9 @@ RSpec.describe ResponseTemplate, type: :model do it "validates that there is no user ID associated" do response_template = build(:response_template, type_of: "mod_comment", content_type: "body_markdown", user_id: 1) expect(response_template.valid?).to eq false - expect(response_template.errors.messages[:type_of].to_sentence).to eq(ResponseTemplate::USER_NIL_TYPE_OF_MSG) + expect(response_template.errors.messages[:type_of].to_sentence).to eq( + I18n.t("models.response_template.user_nil_only"), + ) end end end diff --git a/spec/requests/admin/response_templates_spec.rb b/spec/requests/admin/response_templates_spec.rb index 92d27dac2..9e7ac56b4 100644 --- a/spec/requests/admin/response_templates_spec.rb +++ b/spec/requests/admin/response_templates_spec.rb @@ -59,7 +59,7 @@ RSpec.describe "/admin/advanced/response_templates", type: :request do title: "something" } } - expect(response.body).to include(ResponseTemplate::COMMENT_VALIDATION_MSG) + expect(response.body).to include(I18n.t("models.response_template.comment_markdown")) end end @@ -102,7 +102,7 @@ RSpec.describe "/admin/advanced/response_templates", type: :request do content_type: "html" } } - expect(response.body).to include(ResponseTemplate::COMMENT_VALIDATION_MSG) + expect(response.body).to include(I18n.t("models.response_template.comment_markdown")) end end diff --git a/spec/requests/api/v0/comments_spec.rb b/spec/requests/api/v0/comments_spec.rb index 6b9942297..a8bf130d1 100644 --- a/spec/requests/api/v0/comments_spec.rb +++ b/spec/requests/api/v0/comments_spec.rb @@ -118,7 +118,7 @@ RSpec.describe "Api::V0::Comments", type: :request do it "replaces the body_html" do get api_comments_path(a_id: article.id) - expect(find_child_comment(response)["body_html"]).to eq("

#{Comment::TITLE_DELETED}

") + expect(find_child_comment(response)["body_html"]).to eq("

#{Comment.title_deleted}

") end it "does not render the user information" do @@ -148,7 +148,7 @@ RSpec.describe "Api::V0::Comments", type: :request do it "replaces the body_html" do get api_comments_path(a_id: article.id) - expect(find_child_comment(response)["body_html"]).to eq("

#{Comment::TITLE_HIDDEN}

") + expect(find_child_comment(response)["body_html"]).to eq("

#{Comment.title_hidden}

") end it "does not render the user information" do diff --git a/spec/services/notifications/new_mention/send_spec.rb b/spec/services/notifications/new_mention/send_spec.rb index dbc8a01e5..44ff6a273 100644 --- a/spec/services/notifications/new_mention/send_spec.rb +++ b/spec/services/notifications/new_mention/send_spec.rb @@ -52,6 +52,7 @@ RSpec.describe Notifications::NewMention::Send, type: :service do user.notification_setting.update(mobile_mention_notifications: true) allow(PushNotifications::Send).to receive(:call) allow(I18n).to receive(:t).with("services.notifications.new_mention.new") + allow(I18n).to receive(:l) allow(I18n).to receive(:t).with("views.notifications.mention.article_mobile", user: mention.mentionable.user.username, title: anything).and_call_original