mirror of
https://github.com/discourse/discourse.git
synced 2024-12-24 16:55:03 +08:00
d0df16535e
Followup to 78dacf773a
146 lines
4.1 KiB
Ruby
Executable File
146 lines
4.1 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_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 = [
|
|
"js.discourse_automation.scriptables.auto_responder.fields.word_answer_list.description",
|
|
]
|
|
|
|
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? }
|
|
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)
|
|
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
|