Class: Gitlab::I18n::PoLinter
- Inherits:
-
Object
- Object
- Gitlab::I18n::PoLinter
- Includes:
- Utils::StrongMemoize
- Defined in:
- lib/gitlab/i18n/po_linter.rb
Constant Summary collapse
- VARIABLE_REGEX =
/%{\w*}|%[a-z]/
Instance Attribute Summary collapse
-
#locale ⇒ Object
readonly
Returns the value of attribute locale.
-
#metadata_entry ⇒ Object
readonly
Returns the value of attribute metadata_entry.
-
#po_path ⇒ Object
readonly
Returns the value of attribute po_path.
-
#translation_entries ⇒ Object
readonly
Returns the value of attribute translation_entries.
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:, locale: I18n.locale.to_s) ⇒ PoLinter
constructor
A new instance of PoLinter.
- #numbers_covering_all_plurals ⇒ Object
- #parse_po ⇒ Object
- #random_number ⇒ Object
- #random_string ⇒ 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
Constructor Details
Instance Attribute Details
#locale ⇒ Object (readonly)
Returns the value of attribute locale.
10 11 12 |
# File 'lib/gitlab/i18n/po_linter.rb', line 10 def locale @locale end |
#metadata_entry ⇒ Object (readonly)
Returns the value of attribute metadata_entry.
10 11 12 |
# File 'lib/gitlab/i18n/po_linter.rb', line 10 def @metadata_entry end |
#po_path ⇒ Object (readonly)
Returns the value of attribute po_path.
10 11 12 |
# File 'lib/gitlab/i18n/po_linter.rb', line 10 def po_path @po_path end |
#translation_entries ⇒ Object (readonly)
Returns the value of attribute translation_entries.
10 11 12 |
# File 'lib/gitlab/i18n/po_linter.rb', line 10 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 < .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
19 20 21 |
# File 'lib/gitlab/i18n/po_linter.rb', line 19 def errors @errors ||= validate_po end |
#fill_in_variables(variables) ⇒ Object
245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/gitlab/i18n/po_linter.rb', line 245 def fill_in_variables(variables) if variables.empty? [] elsif variables.any? { |variable| unnamed_variable?(variable) } variables.map do |variable| variable == '%d' ? random_number : random_string end else variables.each_with_object({}) do |variable, hash| variable_name = variable[/\w+/] hash[variable_name] = random_string end end end |
#index_for_pluralization(counter) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/gitlab/i18n/po_linter.rb', line 229 def index_for_pluralization(counter) # This calls the C function that defines the pluralization rule, it can # return a boolean (`false` represents 0, `true` represents 1) or an integer # that specifies the plural form to be used for the given number pluralization_result = FastGettext.pluralisation_rule.call(counter) 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
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/gitlab/i18n/po_linter.rb', line 33 def parse_po entries = SimplePoParser.parse(po_path) # The first entry is the metadata entry if there is one. # This is an entry when empty `msgid` 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: .expected_forms ) end nil rescue SimplePoParser::ParserError => e @translation_entries = [] e. end |
#random_number ⇒ Object
260 261 262 |
# File 'lib/gitlab/i18n/po_linter.rb', line 260 def random_number Random.rand(1000) end |
#random_string ⇒ Object
264 265 266 |
# File 'lib/gitlab/i18n/po_linter.rb', line 264 def random_string SecureRandom.alphanumeric(64) 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
299 300 301 |
# File 'lib/gitlab/i18n/po_linter.rb', line 299 def unnamed_variable?(variable_name) !variable_name.start_with?('%{') end |
#validate_entries ⇒ Object
57 58 59 60 61 62 63 64 65 66 |
# File 'lib/gitlab/i18n/po_linter.rb', line 57 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
68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/gitlab/i18n/po_linter.rb', line 68 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
303 304 305 |
# File 'lib/gitlab/i18n/po_linter.rb', line 303 def validate_flags(errors, entry) errors << "is marked #{entry.flag}" if entry.flag end |
#validate_html(errors, entry) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/gitlab/i18n/po_linter.rb', line 96 def validate_html(errors, entry) = '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? errors << end if entry.plural_id_contains_potential_html? errors << 'plural id ' + end if entry.translations_contain_potential_html? errors << 'translation ' + end end |
#validate_newlines(errors, entry) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/gitlab/i18n/po_linter.rb', line 123 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
113 114 115 116 117 118 119 120 121 |
# File 'lib/gitlab/i18n/po_linter.rb', line 113 def validate_number_of_plurals(errors, entry) return unless &.expected_forms return unless entry.translated? if entry.has_plural? && entry.all_translations.size != .expected_forms errors << "should have #{.expected_forms} "\ "#{'translations'.pluralize(.expected_forms)}" end end |
#validate_po ⇒ Object
23 24 25 26 27 28 29 30 31 |
# File 'lib/gitlab/i18n/po_linter.rb', line 23 def validate_po if (parse_error = parse_po) return 'PO-syntax errors' => [parse_error] end Gitlab::I18n.with_locale(locale) do validate_entries end end |
#validate_translation(errors, entry) ⇒ Object
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 160 def validate_translation(errors, entry) if entry.has_plural? translate_plural(entry) else translate_singular(entry) end # `sprintf` could raise an `ArgumentError` when invalid passing something # other than a Hash when using named variables # # `sprintf` could raise `TypeError` when passing a wrong type when using # unnamed variables # # FastGettext::Translation could raise `RuntimeError` (raised as a string), # or as subclassess `NoTextDomainConfigured` & `InvalidFormat` # # `FastGettext::Translation` could raise `ArgumentError` as subclassess # `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter` rescue ArgumentError, TypeError, RuntimeError => e errors << "Failure translating to #{locale}: #{e.}" end |
#validate_unescaped_chars(errors, entry) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/gitlab/i18n/po_linter.rb', line 82 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
268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/gitlab/i18n/po_linter.rb', line 268 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
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/gitlab/i18n/po_linter.rb', line 280 def validate_variable_usage(errors, translation, required_variables) # We don't need to validate when the message is empty. # In this case we fall back to the default, which has all the # 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
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/gitlab/i18n/po_linter.rb', line 137 def validate_variables(errors, entry) if entry.has_singular_translation? (errors, entry.msgid, entry.msgid) (errors, entry.msgid, entry.singular_translation) end if entry.has_plural? (errors, entry.plural_id, entry.plural_id) entry.plural_translations.each do |translation| (errors, entry.plural_id, translation) end end end |
#validate_variables_in_message(errors, message_id, message_translation) ⇒ Object
153 154 155 156 157 158 |
# File 'lib/gitlab/i18n/po_linter.rb', line 153 def (errors, , ) required_variables = .scan(VARIABLE_REGEX) validate_unnamed_variables(errors, required_variables) validate_variable_usage(errors, , required_variables) end |