Class: Gitlab::I18n::PoLinter
Constant Summary
collapse
- VARIABLE_REGEX =
/%{\w*}|%[a-z]/.freeze
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#calculate_numbers_covering_all_plurals ⇒ Object
-
#errors ⇒ Object
-
#fill_in_variables(variables) ⇒ Object
-
#index_for_pluralization(counter) ⇒ Object
-
#initialize(po_path:, html_todolist:, locale: I18n.locale.to_s) ⇒ PoLinter
constructor
A new instance of PoLinter.
-
#numbers_covering_all_plurals ⇒ Object
-
#parse_po ⇒ Object
-
#translate_plural(entry) ⇒ Object
-
#translate_singular(entry) ⇒ Object
-
#unnamed_variable?(variable_name) ⇒ Boolean
-
#validate_entries ⇒ Object
-
#validate_entry(entry) ⇒ Object
-
#validate_flags(errors, entry) ⇒ Object
-
#validate_html(errors, entry) ⇒ Object
-
#validate_newlines(errors, entry) ⇒ Object
-
#validate_number_of_plurals(errors, entry) ⇒ Object
-
#validate_po ⇒ Object
-
#validate_translation(errors, entry) ⇒ Object
-
#validate_unescaped_chars(errors, entry) ⇒ Object
-
#validate_unnamed_variables(errors, variables) ⇒ Object
-
#validate_variable_usage(errors, translation, required_variables) ⇒ Object
-
#validate_variables(errors, entry) ⇒ Object
-
#validate_variables_in_message(errors, message_id, message_translation) ⇒ Object
#clear_memoization, #strong_memoize, #strong_memoized?
Constructor Details
#initialize(po_path:, html_todolist:, locale: I18n.locale.to_s) ⇒ PoLinter
Returns a new instance of PoLinter.
12
13
14
15
16
|
# File 'lib/gitlab/i18n/po_linter.rb', line 12
def initialize(po_path:, html_todolist:, locale: I18n.locale.to_s)
@po_path = po_path
@locale = locale
@html_todolist = html_todolist
end
|
Instance Attribute Details
#html_todolist ⇒ Object
Returns the value of attribute html_todolist
8
9
10
|
# File 'lib/gitlab/i18n/po_linter.rb', line 8
def html_todolist
@html_todolist
end
|
#locale ⇒ Object
Returns the value of attribute locale
8
9
10
|
# File 'lib/gitlab/i18n/po_linter.rb', line 8
def locale
@locale
end
|
#metadata_entry ⇒ Object
Returns the value of attribute metadata_entry
8
9
10
|
# File 'lib/gitlab/i18n/po_linter.rb', line 8
def metadata_entry
@metadata_entry
end
|
#po_path ⇒ Object
Returns the value of attribute po_path
8
9
10
|
# File 'lib/gitlab/i18n/po_linter.rb', line 8
def po_path
@po_path
end
|
#translation_entries ⇒ Object
Returns the value of attribute translation_entries
8
9
10
|
# File 'lib/gitlab/i18n/po_linter.rb', line 8
def translation_entries
@translation_entries
end
|
Instance Method Details
#calculate_numbers_covering_all_plurals ⇒ Object
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
# File 'lib/gitlab/i18n/po_linter.rb', line 210
def calculate_numbers_covering_all_plurals
required_numbers = []
discovered_indexes = []
counter = 0
while discovered_indexes.size < metadata_entry.forms_to_test && counter < Gitlab::I18n::MetadataEntry::MAX_FORMS_TO_TEST
index_for_count = index_for_pluralization(counter)
unless discovered_indexes.include?(index_for_count)
discovered_indexes << index_for_count
required_numbers << counter
end
counter += 1
end
required_numbers
end
|
#errors ⇒ Object
18
19
20
|
# File 'lib/gitlab/i18n/po_linter.rb', line 18
def errors
@errors ||= validate_po
end
|
#fill_in_variables(variables) ⇒ Object
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
# File 'lib/gitlab/i18n/po_linter.rb', line 247
def fill_in_variables(variables)
if variables.empty?
[]
elsif variables.any? { |variable| unnamed_variable?(variable) }
variables.map do |variable|
variable == '%d' ? Random.rand(1000) : Gitlab::Utils.random_string
end
else
variables.inject({}) do |hash, variable|
variable_name = variable[/\w+/]
hash[variable_name] = Gitlab::Utils.random_string
hash
end
end
end
|
#index_for_pluralization(counter) ⇒ Object
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
|
# File 'lib/gitlab/i18n/po_linter.rb', line 229
def index_for_pluralization(counter)
pluralization_result = Gitlab::I18n.with_locale(locale) do
FastGettext.pluralisation_rule.call(counter)
end
case pluralization_result
when false
0
when true
1
else
pluralization_result
end
end
|
#numbers_covering_all_plurals ⇒ Object
206
207
208
|
# File 'lib/gitlab/i18n/po_linter.rb', line 206
def numbers_covering_all_plurals
@numbers_covering_all_plurals ||= calculate_numbers_covering_all_plurals
end
|
#parse_po ⇒ Object
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
# File 'lib/gitlab/i18n/po_linter.rb', line 30
def parse_po
entries = SimplePoParser.parse(po_path)
if entries.first[:msgid].empty?
@metadata_entry = Gitlab::I18n::MetadataEntry.new(entries.shift)
else
return 'Missing metadata entry.'
end
@translation_entries = entries.map do |entry_data|
Gitlab::I18n::TranslationEntry.new(
entry_data: entry_data,
nplurals: metadata_entry.expected_forms,
html_allowed: html_todolist.fetch(entry_data[:msgid], false)
)
end
nil
rescue SimplePoParser::ParserError => e
@translation_entries = []
e.message
end
|
#translate_plural(entry) ⇒ Object
195
196
197
198
199
200
201
202
203
204
|
# File 'lib/gitlab/i18n/po_linter.rb', line 195
def translate_plural(entry)
numbers_covering_all_plurals.map do |number|
translation = FastGettext::Translation.n_(entry.msgid, entry.plural_id, number)
index = index_for_pluralization(number)
used_variables = index == 0 ? entry.msgid.scan(VARIABLE_REGEX) : entry.plural_id.scan(VARIABLE_REGEX)
variables = fill_in_variables(used_variables)
translation % variables if variables.any?
end
end
|
#translate_singular(entry) ⇒ Object
182
183
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/gitlab/i18n/po_linter.rb', line 182
def translate_singular(entry)
used_variables = entry.msgid.scan(VARIABLE_REGEX)
variables = fill_in_variables(used_variables)
translation = if entry.msgid.include?('|')
FastGettext::Translation.s_(entry.msgid)
else
FastGettext::Translation._(entry.msgid)
end
translation % variables if used_variables.any?
end
|
#unnamed_variable?(variable_name) ⇒ Boolean
294
295
296
|
# File 'lib/gitlab/i18n/po_linter.rb', line 294
def unnamed_variable?(variable_name)
!variable_name.start_with?('%{')
end
|
#validate_entries ⇒ Object
55
56
57
58
59
60
61
62
63
64
|
# File 'lib/gitlab/i18n/po_linter.rb', line 55
def validate_entries
errors = {}
translation_entries.each do |entry|
errors_for_entry = validate_entry(entry)
errors[entry.msgid] = errors_for_entry if errors_for_entry.any?
end
errors
end
|
#validate_entry(entry) ⇒ Object
66
67
68
69
70
71
72
73
74
75
76
77
78
|
# File 'lib/gitlab/i18n/po_linter.rb', line 66
def validate_entry(entry)
errors = []
validate_flags(errors, entry)
validate_variables(errors, entry)
validate_newlines(errors, entry)
validate_number_of_plurals(errors, entry)
validate_unescaped_chars(errors, entry)
validate_html(errors, entry)
validate_translation(errors, entry)
errors
end
|
#validate_flags(errors, entry) ⇒ Object
298
299
300
|
# File 'lib/gitlab/i18n/po_linter.rb', line 298
def validate_flags(errors, entry)
errors << "is marked #{entry.flag}" if entry.flag
end
|
#validate_html(errors, entry) ⇒ Object
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# File 'lib/gitlab/i18n/po_linter.rb', line 94
def validate_html(errors, entry)
common_message = 'contains < or >. Use variables to include HTML in the string, or the < and > codes ' \
'for the symbols. For more info see: https://docs.gitlab.com/ee/development/i18n/externalization.html#html'
if entry.msgid_contains_potential_html? && !entry.msgid_html_allowed?
errors << common_message
end
if entry.plural_id_contains_potential_html? && !entry.plural_id_html_allowed?
errors << 'plural id ' + common_message
end
if entry.translations_contain_potential_html? && !entry.translations_html_allowed?
errors << 'translation ' + common_message
end
end
|
#validate_newlines(errors, entry) ⇒ Object
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# File 'lib/gitlab/i18n/po_linter.rb', line 121
def validate_newlines(errors, entry)
if entry.msgid_has_multiple_lines?
errors << 'is defined over multiple lines, this breaks some tooling.'
end
if entry.plural_id_has_multiple_lines?
errors << 'plural is defined over multiple lines, this breaks some tooling.'
end
if entry.translations_have_multiple_lines?
errors << 'has translations defined over multiple lines, this breaks some tooling.'
end
end
|
#validate_number_of_plurals(errors, entry) ⇒ Object
111
112
113
114
115
116
117
118
119
|
# File 'lib/gitlab/i18n/po_linter.rb', line 111
def validate_number_of_plurals(errors, entry)
return unless metadata_entry&.expected_forms
return unless entry.translated?
if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_forms
errors << "should have #{metadata_entry.expected_forms} "\
"#{'translations'.pluralize(metadata_entry.expected_forms)}"
end
end
|
#validate_po ⇒ Object
22
23
24
25
26
27
28
|
# File 'lib/gitlab/i18n/po_linter.rb', line 22
def validate_po
if (parse_error = parse_po)
return 'PO-syntax errors' => [parse_error]
end
validate_entries
end
|
#validate_translation(errors, entry) ⇒ Object
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
# File 'lib/gitlab/i18n/po_linter.rb', line 158
def validate_translation(errors, entry)
Gitlab::I18n.with_locale(locale) do
if entry.has_plural?
translate_plural(entry)
else
translate_singular(entry)
end
end
rescue ArgumentError, TypeError, RuntimeError => e
errors << "Failure translating to #{locale}: #{e.message}"
end
|
#validate_unescaped_chars(errors, entry) ⇒ Object
80
81
82
83
84
85
86
87
88
89
90
91
92
|
# File 'lib/gitlab/i18n/po_linter.rb', line 80
def validate_unescaped_chars(errors, entry)
if entry.msgid_contains_unescaped_chars?
errors << 'contains unescaped `%`, escape it using `%%`'
end
if entry.plural_id_contains_unescaped_chars?
errors << 'plural id contains unescaped `%`, escape it using `%%`'
end
if entry.translations_contain_unescaped_chars?
errors << 'translation contains unescaped `%`, escape it using `%%`'
end
end
|
#validate_unnamed_variables(errors, variables) ⇒ Object
263
264
265
266
267
268
269
270
271
272
273
|
# File 'lib/gitlab/i18n/po_linter.rb', line 263
def validate_unnamed_variables(errors, variables)
unnamed_variables, named_variables = variables.partition { |name| unnamed_variable?(name) }
if unnamed_variables.any? && named_variables.any?
errors << 'is combining named variables with unnamed variables'
end
if unnamed_variables.size > 1
errors << 'is combining multiple unnamed variables'
end
end
|
#validate_variable_usage(errors, translation, required_variables) ⇒ Object
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
|
# File 'lib/gitlab/i18n/po_linter.rb', line 275
def validate_variable_usage(errors, translation, required_variables)
return if translation.empty?
found_variables = translation.scan(VARIABLE_REGEX)
missing_variables = required_variables - found_variables
if missing_variables.any?
errors << "<#{translation}> is missing: [#{missing_variables.to_sentence}]"
end
unknown_variables = found_variables - required_variables
if unknown_variables.any?
errors << "<#{translation}> is using unknown variables: [#{unknown_variables.to_sentence}]"
end
end
|
#validate_variables(errors, entry) ⇒ Object
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# File 'lib/gitlab/i18n/po_linter.rb', line 135
def validate_variables(errors, entry)
if entry.has_singular_translation?
validate_variables_in_message(errors, entry.msgid, entry.msgid)
validate_variables_in_message(errors, entry.msgid, entry.singular_translation)
end
if entry.has_plural?
validate_variables_in_message(errors, entry.plural_id, entry.plural_id)
entry.plural_translations.each do |translation|
validate_variables_in_message(errors, entry.plural_id, translation)
end
end
end
|
#validate_variables_in_message(errors, message_id, message_translation) ⇒ Object
151
152
153
154
155
156
|
# File 'lib/gitlab/i18n/po_linter.rb', line 151
def validate_variables_in_message(errors, message_id, message_translation)
required_variables = message_id.scan(VARIABLE_REGEX)
validate_unnamed_variables(errors, required_variables)
validate_variable_usage(errors, message_translation, required_variables)
end
|