forked from discourse/discourse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathi18n_lint.rb
executable file
·135 lines (106 loc) · 3.86 KB
/
i18n_lint.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 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
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}:",
invalid_message_format_one_key: "The following keys use 'one {1 foo}' instead of the generic 'one {# foo}':",
}
PLURALIZATION_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other']
ENGLISH_KEYS = ['one', 'other']
def initialize(filename)
@filename = filename
@errors = {}
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)
@errors[:invalid_relative_links] = []
@errors[:invalid_relative_image_sources] = []
@errors[:invalid_interpolation_key_format] = []
@errors[:invalid_message_format_one_key] = []
each_translation(yaml) do |key, value|
if value.match?(/href\s*=\s*["']\/[^\/]|\]\(\/[^\/]/i)
@errors[:invalid_relative_links] << key
end
if value.match?(/src\s*=\s*["']\/[^\/]/i)
@errors[:invalid_relative_image_sources] << key
end
if value.match?(/{{.+?}}/) && !key.end_with?("_MF")
@errors[:invalid_interpolation_key_format] << key
end
if key.end_with?("_MF") && value.match?(/one {.*?1.*?}/)
@errors[:invalid_message_format_one_key] << key
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)
@errors[:wrong_pluralization_keys] = []
@errors[:invalid_one_keys] = []
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