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