1# frozen_string_literal: true 2 3module Gitlab 4 module I18n 5 class TranslationEntry 6 PERCENT_REGEX = /(?:^|[^%])%(?!{\w*}|[a-z%])/.freeze 7 ANGLE_BRACKET_REGEX = /[<>]/.freeze 8 9 attr_reader :nplurals, :entry_data 10 11 def initialize(entry_data:, nplurals:) 12 @entry_data = entry_data 13 @nplurals = nplurals 14 end 15 16 def msgid 17 @msgid ||= Array(entry_data[:msgid]).join 18 end 19 20 def plural_id 21 @plural_id ||= Array(entry_data[:msgid_plural]).join 22 end 23 24 def has_plural? 25 plural_id.present? 26 end 27 28 def singular_translation 29 all_translations.first.to_s if has_singular_translation? 30 end 31 32 def all_translations 33 @all_translations ||= translation_entries.map { |translation| Array(translation).join } 34 end 35 36 def translated? 37 all_translations.any? 38 end 39 40 def plural_translations 41 return [] unless has_plural? 42 return [] unless translated? 43 44 @plural_translations ||= if has_singular_translation? 45 all_translations.drop(1) 46 else 47 all_translations 48 end 49 end 50 51 def flag 52 entry_data[:flag] 53 end 54 55 def has_singular_translation? 56 nplurals > 1 || !has_plural? 57 end 58 59 def msgid_has_multiple_lines? 60 entry_data[:msgid].is_a?(Array) 61 end 62 63 def plural_id_has_multiple_lines? 64 entry_data[:msgid_plural].is_a?(Array) 65 end 66 67 def translations_have_multiple_lines? 68 translation_entries.any? { |translation| translation.is_a?(Array) } 69 end 70 71 def msgid_contains_unescaped_chars? 72 contains_unescaped_chars?(msgid) 73 end 74 75 def plural_id_contains_unescaped_chars? 76 contains_unescaped_chars?(plural_id) 77 end 78 79 def translations_contain_unescaped_chars? 80 all_translations.any? { |translation| contains_unescaped_chars?(translation) } 81 end 82 83 def contains_unescaped_chars?(string) 84 string =~ PERCENT_REGEX 85 end 86 87 def msgid_contains_potential_html? 88 contains_angle_brackets?(msgid) 89 end 90 91 def plural_id_contains_potential_html? 92 contains_angle_brackets?(plural_id) 93 end 94 95 def translations_contain_potential_html? 96 all_translations.any? { |translation| contains_angle_brackets?(translation) } 97 end 98 99 private 100 101 def contains_angle_brackets?(string) 102 string =~ ANGLE_BRACKET_REGEX 103 end 104 105 def translation_entries 106 @translation_entries ||= entry_data.fetch_values(*translation_keys) 107 .reject(&:empty?) 108 end 109 110 def translation_keys 111 @translation_keys ||= entry_data.keys.select { |key| key.to_s =~ /\Amsgstr(\[\d+\])?\z/ } 112 end 113 end 114 end 115end 116