app/models i18n (#16124)
* app/models etc i18n * delete ja.yml * fix for PR review * fix for spec * delete ja.yml * fix for spec updated
This commit is contained in:
parent
c4778d832e
commit
e45536af37
44 changed files with 418 additions and 122 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 %<provider>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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: "%<value>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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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 "<p>#{Comment::TITLE_DELETED}</p>"
|
||||
json.body_html "<p>#{Comment.title_deleted}</p>"
|
||||
json.set! :user, {}
|
||||
elsif comment.hidden_by_commentable_user?
|
||||
json.body_html "<p>#{Comment::TITLE_HIDDEN}</p>"
|
||||
json.body_html "<p>#{Comment.title_hidden}</p>"
|
||||
json.set! :user, {}
|
||||
else
|
||||
json.body_html comment.processed_html
|
||||
|
|
|
|||
131
config/locales/models/en.yml
Normal file
131
config/locales/models/en.yml
Normal file
|
|
@ -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: "%<value>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
|
||||
131
config/locales/models/fr.yml
Normal file
131
config/locales/models/fr.yml
Normal file
|
|
@ -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: "%<value>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
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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("<p>#{Comment::TITLE_DELETED}</p>")
|
||||
expect(find_child_comment(response)["body_html"]).to eq("<p>#{Comment.title_deleted}</p>")
|
||||
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("<p>#{Comment::TITLE_HIDDEN}</p>")
|
||||
expect(find_child_comment(response)["body_html"]).to eq("<p>#{Comment.title_hidden}</p>")
|
||||
end
|
||||
|
||||
it "does not render the user information" do
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue