Class: Rubustrings::Action

Inherits:
Object
  • Object
show all
Defined in:
lib/rubustrings/action.rb

Instance Method Summary collapse

Instance Method Details

#add_line_numbers(file_data) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/rubustrings/action.rb', line 53

def add_line_numbers(file_data)
  line_num = 0
  result = ''
  file_data.each_line do |line|
    line_num += 1
    result += "#{line_num} #{line}"
  end
  result
end

#check_translation_length(translation_key, translation_value) ⇒ Object



140
141
142
# File 'lib/rubustrings/action.rb', line 140

def check_translation_length(translation_key, translation_value)
  translation_value.length / translation_key.length < 3
end

#log_output(level, file_name, line_number, message) ⇒ Object

Possible levels are :error, :result_error, :warning, :result_success, :info



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/rubustrings/action.rb', line 24

def log_output(level, file_name, line_number, message)
  message = message.chomp
  case level
  when :error
    puts "#{file_name}:#{line_number}: error: #{message}"
  when :warning
    puts "#{file_name}:#{line_number}: warning: #{message}"
  when :result_success
    puts "\nResult: ✓ #{message}".bold.green
  when :result_error
    puts "\nResult: ✘ #{message}".bold.red
  when :info
    puts message.to_s.blue
  end
end

#open_and_read_file(file_name) ⇒ Object



63
64
65
66
67
68
69
70
71
# File 'lib/rubustrings/action.rb', line 63

def open_and_read_file(file_name)
  return nil unless File.exist?(file_name)

  begin
    File.open(file_name, 'rb:utf-16:utf-8').read
  rescue
    File.open(file_name, 'rb:utf-8:utf-8').read
  end
end

#remove_comments_and_empty_lines(file_data) ⇒ Object



73
74
75
76
77
78
79
# File 'lib/rubustrings/action.rb', line 73

def remove_comments_and_empty_lines(file_data)
  multiline_comments_regex = %r{/\*.*?\*/}m
  empty_lines_regex = /^[1-9]\d* $\n/

  file_data_with_lines = add_line_numbers file_data
  file_data_with_lines.gsub(multiline_comments_regex, '').gsub(empty_lines_regex, '') if file_data
end

#validate(filenames, only_format) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/rubustrings/action.rb', line 7

def validate(filenames, only_format)
  abort 'No strings file provided' unless filenames
  filenames.each do |file_name|
    log_output(:info, '', 0, "Processing file: \"#{file_name}\"\n")
    result = validate_localizable_string_file file_name, only_format

    if result
      log_output(:result_success, file_name, 0, 'Strings file validated succesfully')
      return true
    else
      log_output(:result_error, file_name, 0, 'Some errors detected')
      return false
    end
  end
end

#validate_format(line) ⇒ Object



81
82
83
84
# File 'lib/rubustrings/action.rb', line 81

def validate_format(line)
  localizable_strings_format_regex = /^\"((?:\\.|[^\\"])*?)\"\s=\s\"((?:\\.|[^\\"])*?)\";/
  localizable_strings_format_regex.match line
end

#validate_localizable_string_file(file_name, only_format) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rubustrings/action.rb', line 40

def validate_localizable_string_file(file_name, only_format) 
  file_data = open_and_read_file file_name
  cleaned_strings = remove_comments_and_empty_lines file_data

  return log_output(:error, file_name, 0, "no translations found in file: #{file_name}") if cleaned_strings.empty?

  validation_result = true
  cleaned_strings.each_line do |line|
    validation_result &= validate_translation_line file_name, line, only_format
  end
  validation_result
end

#validate_special_beginning(translation_key, translation_value) ⇒ Object



126
127
128
129
130
131
# File 'lib/rubustrings/action.rb', line 126

def validate_special_beginning(translation_key, translation_value)
  beginning_regex = /^(?:\s|\n|\r)/

  return true unless translation_key =~ beginning_regex || translation_value =~ beginning_regex
  translation_key.chars.first == translation_value.chars.first
end

#validate_special_characters(translation_key, translation_value) ⇒ Object



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
# File 'lib/rubustrings/action.rb', line 86

def validate_special_characters(translation_key, translation_value)
  # Remove %% to avoid ambiguous scenarios with adjacent formats like "%s%%s"
  translation_key = translation_key.gsub("%%", " ")
  translation_value = translation_value.gsub("%%", " ")

  variables_regex = /\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?(hh|ll|[hlLzjt])?([b-fiosuxX@])/
  position_index = 0
  length_index = 7
  format_index = 8

  # sort by according to parameter field, if specified
  key_variables = translation_key.scan(variables_regex).stable_sort_by{ |r| r[position_index].to_i }
  value_variables = translation_value.scan(variables_regex).stable_sort_by{ |r| r[position_index].to_i }

  return true unless key_variables.any? || value_variables.any?
  return false unless key_variables.count == value_variables.count

  # we should not have any parameter fields in the keys
  return false unless key_variables.last[position_index] == nil

  # if we do have parameter fields, we need to include all of them
  if value_variables[0][position_index] != nil
    return false unless value_variables.last[position_index] != nil
    validation_result = true
    value_variables.each_with_index { |v, idx|
      if v[position_index].to_i != idx + 1
        validation_result = false
      end
    }
    return false unless validation_result
  else
    return false unless value_variables.last[position_index] == nil
  end

  # remove parameter field
  key_variables = key_variables.map{ |v| [v[length_index], v[format_index]] }
  value_variables = value_variables.map{ |v| [v[length_index], v[format_index]] }
  key_variables == value_variables
end

#validate_special_ending(translation_key, translation_value) ⇒ Object



133
134
135
136
137
138
# File 'lib/rubustrings/action.rb', line 133

def validate_special_ending(translation_key, translation_value)
  ending_regex = /(?:\s|\n|\r)$/

  return true unless translation_key =~ ending_regex || translation_value =~ ending_regex
  translation_key.chars.last == translation_value.chars.last
end

#validate_translation_line(file_name, line, only_format) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rubustrings/action.rb', line 144

def validate_translation_line(file_name, line, only_format)
  line_number = 0

  empty_regex = /^\d+\s*\n?$/
  return true if empty_regex.match line

  numbered_line_regex = /^(\d+) (.*)/
  numbered_line_match = numbered_line_regex.match line

  return log_output(:error, file_name, line_number, 'internal error') unless numbered_line_match
  line_number = numbered_line_match[1]
  line = numbered_line_match[2]

  match = validate_format line
  return log_output(:error, file_name, line_number, "invalid format: #{line}") unless match

  return true if only_format

  match_key = match[1]
  match_value = match[2]

  log_output(:warning, file_name, line_number, "no translated string: #{line}") if match_value.empty?

  log_output(:warning, file_name, line_number, "translation significantly large: #{line}") unless check_translation_length match_key, match_value

  validation_special_characters = validate_special_characters match_key, match_value
  log_output(:error, file_name, line_number, "variables mismatch: #{line}") unless validation_special_characters

  validation_special_beginning = validate_special_beginning match_key, match_value
  log_output(:error, file_name, line_number, "beginning mismatch: #{line}") unless validation_special_beginning

  validation_special_ending = validate_special_ending match_key, match_value
  log_output(:error, file_name, line_number, "ending mismatch: #{line}") unless validation_special_ending

  validation_special_characters && validation_special_beginning && validation_special_ending
end