1# frozen_string_literal: true
2
3require "gettext_i18n_rails/tasks"
4
5namespace :gettext do
6  task :compile do
7    # See: https://gitlab.com/gitlab-org/gitlab-foss/issues/33014#note_31218998
8    FileUtils.touch(pot_file_path)
9
10    Rake::Task['gettext:po_to_json'].invoke
11  end
12
13  desc 'Regenerate gitlab.pot file'
14  task :regenerate do
15    ensure_locale_folder_presence!
16
17    # Clean up folders that do not contain a gitlab.po file
18    Pathname.new(locale_path).children.each do |child|
19      next unless child.directory?
20
21      folder_path = child.to_path
22
23      if File.exist?("#{folder_path}/gitlab.po")
24        # remove all translated files to speed up finding
25        FileUtils.rm Dir["#{folder_path}/gitlab.*"]
26      else
27        # remove empty translation folders so we don't generate un-needed .po files
28        puts "Deleting #{folder_path} as it does not contain a 'gitlab.po' file."
29
30        FileUtils.rm_r folder_path
31      end
32    end
33
34    # remove the `pot` file to ensure it's completely regenerated
35    FileUtils.rm_f(pot_file_path)
36
37    Rake::Task['gettext:find'].invoke
38
39    # leave only the required changes.
40    unless system(*%w(git -c core.hooksPath=/dev/null checkout -- locale/*/gitlab.po))
41      raise 'failed to cleanup generated locale/*/gitlab.po files'
42    end
43
44    raise 'gitlab.pot file not generated' unless File.exist?(pot_file_path)
45
46    # Remove timestamps from the pot file
47    pot_content = File.read pot_file_path
48    pot_content.gsub!(/^"POT?\-(?:Creation|Revision)\-Date\:.*\n/, '')
49    File.write pot_file_path, pot_content
50
51    puts <<~MSG
52    All done. Please commit the changes to `locale/gitlab.pot`.
53
54    MSG
55  end
56
57  desc 'Lint all po files in `locale/'
58  task lint: :environment do
59    require 'simple_po_parser'
60    require 'gitlab/utils'
61    require 'parallel'
62
63    FastGettext.silence_errors
64    files = Dir.glob(Rails.root.join('locale/*/gitlab.po'))
65
66    linters = files.map do |file|
67      locale = File.basename(File.dirname(file))
68
69      Gitlab::I18n::PoLinter.new(po_path: file, locale: locale)
70    end
71
72    linters.unshift(Gitlab::I18n::PoLinter.new(po_path: pot_file_path))
73
74    failed_linters = Parallel
75      .map(linters, progress: 'Linting po files') { |linter| linter if linter.errors.any? }
76      .compact
77
78    if failed_linters.empty?
79      puts 'All PO files are valid.'
80    else
81      failed_linters.each do |linter|
82        report_errors_for_file(linter.po_path, linter.errors)
83      end
84
85      raise "Not all PO-files are valid: #{failed_linters.map(&:po_path).to_sentence}"
86    end
87  end
88
89  task :updated_check do
90    # Removing all pre-translated files speeds up `gettext:find` as the
91    # files don't need to be merged.
92    # Having `LC_MESSAGES/gitlab.mo files present also confuses the output.
93    FileUtils.rm Dir['locale/**/gitlab.*']
94    FileUtils.rm_f pot_file_path
95
96    # `gettext:find` writes touches to temp files to `stderr` which would cause
97    # `static-analysis` to report failures. We can ignore these.
98    silence_stderr do
99      Rake::Task['gettext:find'].invoke
100    end
101
102    pot_diff = `git diff -- #{pot_file_path} | grep -E '^(\\+|-)msgid'`.strip
103
104    # reset the locale folder for potential next tasks
105    `git checkout -- locale`
106
107    if pot_diff.present?
108      raise <<~MSG
109        Changes in translated strings found, please update file `#{pot_file_path}` by running:
110
111          bin/rake gettext:regenerate
112
113        Then commit and push the resulting changes to `#{pot_file_path}`.
114
115        The diff was:
116
117        #{pot_diff}
118      MSG
119    end
120  end
121
122  private
123
124  # Customize list of translatable files
125  # See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files
126  def files_to_translate
127    folders = %W(ee app lib config #{locale_path}).join(',')
128    exts = %w(rb erb haml slim rhtml js jsx vue handlebars hbs mustache).join(',')
129
130    Dir.glob(
131      "{#{folders}}/**/*.{#{exts}}"
132    )
133  end
134
135  def report_errors_for_file(file, errors_for_file)
136    puts "Errors in `#{file}`:"
137
138    errors_for_file.each do |message_id, errors|
139      puts "  #{message_id}"
140      errors.each do |error|
141        spaces = ' ' * 4
142        error = error.lines.join("#{spaces}")
143        puts "#{spaces}#{error}"
144      end
145    end
146  end
147
148  def silence_stderr(&block)
149    old_stderr = $stderr.dup
150    $stderr.reopen(File::NULL)
151    $stderr.sync = true
152
153    yield
154  ensure
155    $stderr.reopen(old_stderr)
156    old_stderr.close
157  end
158
159  def ensure_locale_folder_presence!
160    unless Dir.exist?(locale_path)
161      raise <<~MSG
162      Cannot find '#{locale_path}' folder. Please ensure you're running this task from the gitlab repo.
163
164      MSG
165    end
166  end
167
168  def locale_path
169    @locale_path ||= Rails.root.join('locale')
170  end
171
172  def pot_file_path
173    @pot_file_path ||= File.join(locale_path, 'gitlab.pot')
174  end
175end
176