mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-01 05:51:37 +08:00
This commit adds a new "Email on flagged post" automation which can be configured to send emails to users when a post is flagged. The automation can be configured to trigger only when posts are flagged in certain categories or only when certain tags are present on the post's topic. The body of the email sent can also be configured with the following placeholders available: * topic_url * topic_title * post_url * post_number * flagger_username * flagged_username * flag_type * category * tags * post_excerpt
155 lines
4.5 KiB
Ruby
Executable file
155 lines
4.5 KiB
Ruby
Executable file
# frozen_string_literal: true
|
|
|
|
require "colored2"
|
|
require "psych"
|
|
|
|
class I18nLinter
|
|
def initialize(filenames_or_patterns)
|
|
@filenames = filenames_or_patterns.map { |fp| Dir[fp] }.flatten
|
|
@errors = {}
|
|
end
|
|
|
|
def run
|
|
has_errors = false
|
|
|
|
@filenames.each do |filename|
|
|
validator = LocaleFileValidator.new(filename)
|
|
|
|
if validator.has_errors?
|
|
validator.print_errors
|
|
has_errors = true
|
|
end
|
|
end
|
|
|
|
exit 1 if has_errors
|
|
end
|
|
end
|
|
|
|
class LocaleFileValidator
|
|
# Format: "banned phrase" => "recommendation"
|
|
BANNED_PHRASES = { "color scheme" => "color palette", "private message" => "personal message" }
|
|
|
|
ERROR_MESSAGES = {
|
|
invalid_relative_links:
|
|
"The following keys have relative links, but do not start with %{base_url} or %{base_path}:",
|
|
invalid_relative_image_sources:
|
|
"The following keys have relative image sources, but do not start with %{base_url} or %{base_path}:",
|
|
invalid_interpolation_key_format:
|
|
"The following keys use {{key}} instead of %{key} for interpolation keys:",
|
|
wrong_pluralization_keys:
|
|
"Pluralized strings must have only the sub-keys 'one' and 'other'.\nThe following keys have missing or additional keys:",
|
|
invalid_file_format:
|
|
"The file is not a valid YAML format or does not contain a valid locale structure.",
|
|
invalid_one_keys:
|
|
"The following keys contain the number 1 instead of the interpolation key %{count}:",
|
|
}.merge(
|
|
BANNED_PHRASES
|
|
.map do |banned, recommendation|
|
|
[
|
|
"banned_phrase_#{banned}",
|
|
"The following keys contain the banned phrase '#{banned}' (use '#{recommendation}' instead)",
|
|
]
|
|
end
|
|
.to_h,
|
|
)
|
|
|
|
PLURALIZATION_KEYS = %w[zero one two few many other]
|
|
ENGLISH_KEYS = %w[one other]
|
|
|
|
EXEMPTED_DOUBLE_CURLY_BRACKET_KEYS = %w[
|
|
js.discourse_automation.scriptables.auto_responder.fields.word_answer_list.description
|
|
discourse_automation.scriptables.email_on_flagged_post.default_template
|
|
]
|
|
|
|
def initialize(filename)
|
|
@filename = filename
|
|
@errors = {}
|
|
ERROR_MESSAGES.keys.each { |type| @errors[type] = [] }
|
|
end
|
|
|
|
def has_errors?
|
|
yaml = Psych.safe_load(File.read(@filename), aliases: true)
|
|
yaml = yaml[yaml.keys.first]
|
|
|
|
validate_pluralizations(yaml)
|
|
validate_content(yaml)
|
|
|
|
@errors.any? { |_, value| value.any? }
|
|
rescue StandardError => e
|
|
@errors[:invalid_file_format] = ["Failed to parse #{@filename}: #{e.message}"]
|
|
true
|
|
end
|
|
|
|
def print_errors
|
|
puts "", "Errors in #{@filename}".red
|
|
|
|
@errors.each do |type, keys|
|
|
next if keys.empty?
|
|
|
|
ERROR_MESSAGES[type].split("\n").each { |msg| puts " #{msg}" }
|
|
keys.each { |key| puts " * #{key}" }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def each_translation(hash, parent_key = "", &block)
|
|
hash.each do |key, value|
|
|
current_key = parent_key.empty? ? key : "#{parent_key}.#{key}"
|
|
|
|
if Hash === value
|
|
each_translation(value, current_key, &block)
|
|
else
|
|
yield(current_key, value.to_s)
|
|
end
|
|
end
|
|
end
|
|
|
|
def validate_content(yaml)
|
|
each_translation(yaml) do |key, value|
|
|
@errors[:invalid_relative_links] << key if value.match?(%r{href\s*=\s*["']/[^/]|\]\(/[^/]}i)
|
|
|
|
@errors[:invalid_relative_image_sources] << key if value.match?(%r{src\s*=\s*["']/[^/]}i)
|
|
|
|
if value.match?(/{{.+?}}/) && !key.end_with?("_MF") &&
|
|
!EXEMPTED_DOUBLE_CURLY_BRACKET_KEYS.include?(key)
|
|
@errors[:invalid_interpolation_key_format] << key
|
|
end
|
|
|
|
BANNED_PHRASES.keys.each do |banned|
|
|
@errors["banned_phrase_#{banned}"] << key if value.downcase.include?(banned.downcase)
|
|
end
|
|
end
|
|
end
|
|
|
|
def each_pluralization(hash, parent_key = "", &block)
|
|
hash.each do |key, value|
|
|
if Hash === value
|
|
current_key = parent_key.empty? ? key : "#{parent_key}.#{key}"
|
|
each_pluralization(value, current_key, &block)
|
|
elsif PLURALIZATION_KEYS.include? key
|
|
yield(parent_key, hash)
|
|
end
|
|
end
|
|
end
|
|
|
|
def validate_pluralizations(yaml)
|
|
if !yaml.is_a?(Hash)
|
|
@errors[:wrong_pluralization_keys] << ["Root of the locale file must be a hash"]
|
|
return
|
|
end
|
|
each_pluralization(yaml) do |key, hash|
|
|
# ignore errors from some ActiveRecord messages
|
|
next if key.include?("messages.restrict_dependent_destroy")
|
|
|
|
@errors[:wrong_pluralization_keys] << key if hash.keys.sort != ENGLISH_KEYS
|
|
|
|
one_value = hash["one"]
|
|
if one_value && one_value.include?("1") && !one_value.match?(/%{count}|{{count}}/)
|
|
@errors[:invalid_one_keys] << key
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
I18nLinter.new(ARGV).run
|