rails_translation_manager 0.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +23 -0
  5. data/Jenkinsfile +9 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +57 -1
  8. data/Rakefile +14 -9
  9. data/config/locales/plurals.rb +49 -0
  10. data/lib/rails_translation_manager/cleaner.rb +14 -0
  11. data/lib/rails_translation_manager/importer.rb +35 -19
  12. data/lib/rails_translation_manager/locale_checker/all_locales.rb +61 -0
  13. data/lib/rails_translation_manager/locale_checker/base_checker.rb +13 -0
  14. data/lib/rails_translation_manager/locale_checker/incompatible_plurals.rb +79 -0
  15. data/lib/rails_translation_manager/locale_checker/locale_checker_helper.rb +23 -0
  16. data/lib/rails_translation_manager/locale_checker/missing_english_locales.rb +31 -0
  17. data/lib/rails_translation_manager/locale_checker/missing_foreign_locales.rb +31 -0
  18. data/lib/rails_translation_manager/locale_checker/plural_forms.rb +16 -0
  19. data/lib/rails_translation_manager/locale_checker.rb +50 -0
  20. data/lib/rails_translation_manager/version.rb +1 -1
  21. data/lib/rails_translation_manager/yaml_writer.rb +17 -0
  22. data/lib/rails_translation_manager.rb +21 -2
  23. data/lib/tasks/translation.rake +37 -28
  24. data/lib/tasks/translation_helper.rb +11 -0
  25. data/rails_translation_manager.gemspec +4 -1
  26. data/spec/locales/cleaner/clean.yml +5 -0
  27. data/spec/locales/cleaner/with_whitespace.yml +5 -0
  28. data/spec/locales/importer/fr.csv +8 -0
  29. data/spec/locales/in_sync/cy/browse.yml +12 -0
  30. data/spec/locales/in_sync/en/browse.yml +8 -0
  31. data/spec/locales/out_of_sync/cy.yml +3 -0
  32. data/spec/locales/out_of_sync/en.yml +6 -0
  33. data/spec/rails_translation_manager/cleaner_spec.rb +13 -0
  34. data/spec/rails_translation_manager/importer_spec.rb +81 -0
  35. data/spec/rails_translation_manager/locale_checker/all_locales_spec.rb +24 -0
  36. data/spec/rails_translation_manager/locale_checker/incompatible_plurals_spec.rb +94 -0
  37. data/spec/rails_translation_manager/locale_checker/locale_checker_helper_spec.rb +61 -0
  38. data/spec/rails_translation_manager/locale_checker/missing_english_locales_spec.rb +72 -0
  39. data/spec/rails_translation_manager/locale_checker/missing_foreign_locales_spec.rb +80 -0
  40. data/spec/rails_translation_manager/locale_checker/plural_forms_spec.rb +27 -0
  41. data/spec/rails_translation_manager/locale_checker_spec.rb +68 -0
  42. data/spec/spec_helper.rb +19 -0
  43. data/spec/support/tasks.rb +7 -0
  44. data/spec/tasks/translation_spec.rb +123 -0
  45. data/test/rails_translation_manager/yaml_writer_test.rb +60 -0
  46. data/tmp/.gitkeep +0 -0
  47. metadata +104 -13
  48. data/lib/rails_translation_manager/validator.rb +0 -92
  49. data/test/rails_translation_manager/importer_test.rb +0 -166
  50. data/test/rails_translation_manager/validator_test.rb +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bcf654f074b115a7c80d173e821333d2b8b35b01
4
- data.tar.gz: 84cea8e22ca03313c47e7432be605cee30905391
2
+ SHA256:
3
+ metadata.gz: 0b6a096ee29746cead2529ea0cb83660c0e0217b710e085790969f203f246871
4
+ data.tar.gz: 3c13cb54190c17f1382246827c120cf928e7763a9de4b4724ec36ffe6005fc62
5
5
  SHA512:
6
- metadata.gz: 6823237f243a4127c4c14ece436ec69212c1e481a1d11d8329649cfbcdc61177bf73f0097c1978fed353c863c83949f9eee62f616c16086f28757ce6645b5f0b
7
- data.tar.gz: 2d8bc1c02272590a00a8bcbec3d002cec989f27c69e5025e65729ebf56c198089d0f29c8f7b87aaab5434642b28f2f11037a9596babdb1e096dd9ff8bda1a850
6
+ metadata.gz: 37abc2c32de6245dc3d1cd57da56f14ab3c68f88f9b3802a65735d3c36c02984f823d45ff177729daad6b2234929d03535ca28239a4188889a14be60f35875df
7
+ data.tar.gz: 73f08794d826203ba2be01695751d742bd7a546dee1bf9f8946c98cf57350a48651addd5eef53cfcd9b23303900d6efe0dcd41fb0ae81d67ba060ed851b5061a
data/.gitignore CHANGED
@@ -6,7 +6,7 @@
6
6
  /doc/
7
7
  /pkg/
8
8
  /spec/reports/
9
- /tmp/
9
+ /tmp/*
10
10
  *.bundle
11
11
  *.so
12
12
  *.o
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.4
1
+ 2.6.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ ## 1.1.0
2
+
3
+ Allow multiple files per language to be imported. https://github.com/alphagov/rails_translation_manager/pull/20
4
+
5
+ ## 1.0.0
6
+
7
+ Adds logic to verify locale files are in sync with each other and have the
8
+ correct plural forms. Also introduces RSpec into the Gem which will replace
9
+ Minitest in the coming iterations.
10
+
11
+ Integrates RTM with I18n-tasks. Adds wrappers around `add-missing` & `normalize` tasks, adds Cleaner class to remove the whitespace added by i18n-tasks, adds tests and byebug gem as a debugging tool.
12
+
13
+ ## 0.1.0
14
+
15
+ Don't change the $LOAD_PATH in translation.rake.
16
+
17
+ ## 0.0.2
18
+
19
+ Added `steal` rake tasks to copy translations from another app.
20
+
21
+ ## 0.0.1
22
+
23
+ Initial release.
data/Jenkinsfile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env groovy
2
+
3
+ library("govuk")
4
+
5
+ node {
6
+ govuk.buildProject(
7
+ cleanWorkspace: true,
8
+ )
9
+ }
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  The MIT License (MIT)
2
- Copyright (C) 2015 HM Government (Government Digital Service)
2
+ Copyright (C) 2015 Crown Copyright (Government Digital Service)
3
3
 
4
4
  MIT License
5
5
 
data/README.md CHANGED
@@ -14,7 +14,7 @@ Support for translation workflow in rails applications.
14
14
  into a translated string of text, for example `Hello %{name}`
15
15
  would become `Hello Sarah` if the variable `name` had the
16
16
  value `Sarah`.
17
- - **YAML**: yet another markup language, a textual data format used for storing
17
+ - **YAML**: YAML Ain't Markup Language, a textual data format used (in this case) for storing
18
18
  translation strings in rails applications
19
19
 
20
20
  ## Technical documentation
@@ -124,6 +124,48 @@ $ rake translation:validate
124
124
  Success! No unexpected interpolation keys found.
125
125
  ```
126
126
 
127
+ ### Stealing translations from another app
128
+
129
+ A third feature is the ability to "steal" one or more locales from an existing
130
+ application. This functionality works by providing a mapping file, which defines
131
+ how the translation keys in the the source app's files map to those in the app
132
+ the gem is installed in.
133
+
134
+ For example, given a locale file like this in the app to "steal" from:
135
+
136
+ ```yaml
137
+ es:
138
+ document:
139
+ type:
140
+ case_study: Caso de estudio
141
+ consultation: Consulta
142
+ ```
143
+
144
+ and a mapping file like this:
145
+
146
+ ```yaml
147
+ document.type: content_item.format
148
+ ```
149
+
150
+ running `rake translation:steal[es,../other_app,mapping_file_path.yml]` will
151
+ result in the following locale file being created:
152
+
153
+ ```yaml
154
+ es:
155
+ content_item:
156
+ format:
157
+ case_study: Caso de estudio
158
+ consultation: Consulta
159
+ ```
160
+
161
+ The mapping file can live anywhere, as long as the full path (including filename)
162
+ is given in the rake task invocation.
163
+
164
+ The process will preserve data already in the output file if it is not
165
+ referenced in the mapping, but will always override data belonging to keys
166
+ that are in the mapping.
167
+
168
+
127
169
  ### Rake command reference
128
170
 
129
171
  #### Export a specific locale to CSV
@@ -150,6 +192,8 @@ rake translation:import[locale,path]
150
192
  rake translation:import:all[directory]
151
193
  ```
152
194
 
195
+ ####
196
+
153
197
  #### Regenerate all locales from the EN locale - run this after adding keys
154
198
 
155
199
  ```
@@ -162,6 +206,18 @@ rake translation:regenerate[directory]
162
206
  rake translation:validate
163
207
  ```
164
208
 
209
+ #### Steal a specific locale file from another app
210
+
211
+ ```
212
+ rake translation:steal[locale,source_app_path,mapping_file_path]
213
+ ```
214
+
215
+ #### Steal all locale files from another app
216
+
217
+ ```
218
+ rake translation:steal:all[source_app_path,mapping_file_path]
219
+ ```
220
+
165
221
  ### Running the test suite
166
222
 
167
223
  To run the test suite just run `bundle exec rake` from within the
data/Rakefile CHANGED
@@ -1,12 +1,17 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new("test") do |t|
5
- t.description = "Run tests"
6
- t.ruby_opts << "-rubygems"
7
- t.libs << "test"
8
- t.test_files = FileList["test/**/*_test.rb"]
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'bundler/gem_tasks'
5
+ require 'rake/testtask'
6
+ require 'rails_i18n'
7
+
8
+ Rake::TestTask.new('test') do |t|
9
+ t.description = 'Run tests'
10
+ t.libs << 'test'
11
+ t.test_files = FileList['test/**/*_test.rb']
9
12
  t.verbose = true
10
13
  end
11
14
 
12
- task :default => :test
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task default: %i[spec test]
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ {
4
+ # Dari - this isn't an iso code. Probably should be 'prs' as per ISO 639-3.
5
+ dr: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
6
+ # Latin America and Caribbean Spanish
7
+ "es-419": { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
8
+ # Scottish Gaelic
9
+ gd: { i18n: { plural: { keys: %i[one two few other],
10
+ rule: lambda do |n|
11
+ if [1, 11].include?(n)
12
+ :one
13
+ elsif [2, 12].include?(n)
14
+ :two
15
+ elsif [3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17, 18, 19].include?(n)
16
+ :few
17
+ else
18
+ :other
19
+ end
20
+ end } } },
21
+ # Gujarati
22
+ gu: { i18n: { plural: { keys: %i[one other], rule: ->(n) { [0, 1].include?(n) ? :one : :other } } } },
23
+ # Armenian
24
+ hy: { i18n: { plural: { keys: %i[one other], rule: ->(n) { [0, 1].include?(n) ? :one : :other } } } },
25
+ # Kazakh
26
+ kk: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
27
+ # Pashto
28
+ ps: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
29
+ # Punjabi Shahmukhi
30
+ "pa-pk": { i18n: { plural: { keys: %i[one other], rule: ->(n) { [0, 1].include?(n) ? :one : :other } } } },
31
+ # Sinhalese
32
+ si: { i18n: { plural: { keys: %i[one other], rule: ->(n) { [0, 1].include?(n) ? :one : :other } } } },
33
+ # Somali
34
+ so: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
35
+ # Albanian
36
+ sq: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
37
+ # Norwegian
38
+ no: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
39
+ # Tamil
40
+ ta: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
41
+ # Turkmen
42
+ tk: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
43
+ # Uzbek
44
+ uz: { i18n: { plural: { keys: %i[one other], rule: ->(n) { n == 1 ? :one : :other } } } },
45
+ # Chinese Hong Kong
46
+ 'zh-hk' => { i18n: { plural: { keys: %i[other], rule: -> { :other } } } },
47
+ # Chinese Taiwan
48
+ 'zh-tw' => { i18n: { plural: { keys: %i[other], rule: -> { :other } } } }
49
+ }
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsTranslationManager
4
+ class Cleaner
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def clean
11
+ `find #{@path} -type f -exec perl -p -i -e \"s/[ \t]$//g\" {} \\;`
12
+ end
13
+ end
14
+ end
@@ -1,42 +1,43 @@
1
1
  require "yaml"
2
2
  require "csv"
3
+ require_relative "yaml_writer"
3
4
 
4
5
  class RailsTranslationManager::Importer
5
- def initialize(locale, csv_path, import_directory)
6
- @csv_path = csv_path
6
+ include YAMLWriter
7
+
8
+ attr_reader :locale, :csv_path, :import_directory, :multiple_files_per_language
9
+
10
+ def initialize(locale:, csv_path:, import_directory:, multiple_files_per_language:)
7
11
  @locale = locale
12
+ @csv_path = csv_path
8
13
  @import_directory = import_directory
14
+ @multiple_files_per_language = multiple_files_per_language
9
15
  end
10
16
 
11
17
  def import
12
- csv = CSV.read(@csv_path, headers: true, header_converters: :downcase)
13
- data = {}
14
- csv.each do |row|
18
+ csv = CSV.read(csv_path, headers: true, header_converters: :downcase)
19
+
20
+ multiple_files_per_language ? import_csv_into_multiple_files(csv) : import_csv(csv)
21
+ end
22
+
23
+ private
24
+
25
+ def import_csv(csv, import_yml_path = File.join(import_directory, "#{locale}.yml"))
26
+ data = csv.each_with_object({}) do |row, hash|
15
27
  key = row["key"]
16
28
  key_parts = key.split(".")
17
29
  if key_parts.length > 1
18
- leaf_node = (data[key_parts.first] ||= {})
30
+ leaf_node = (hash[key_parts.first] ||= {})
19
31
  key_parts[1..-2].each do |part|
20
32
  leaf_node = (leaf_node[part] ||= {})
21
33
  end
22
34
  leaf_node[key_parts.last] = parse_translation(row["translation"])
23
35
  else
24
- data[key_parts.first] = parse_translation(row["translation"])
36
+ hash[key_parts.first] = parse_translation(row["translation"])
25
37
  end
26
38
  end
27
39
 
28
- File.open(import_yml_path, "w") do |f|
29
- yaml = {@locale.to_s => data}.to_yaml(separator: "")
30
- yaml_without_header = yaml.split("\n").map { |l| l.gsub(/\s+$/, '') }[1..-1].join("\n")
31
- f.write(yaml_without_header)
32
- f.puts
33
- end
34
- end
35
-
36
- private
37
-
38
- def import_yml_path
39
- File.join(@import_directory, "#{@locale}.yml")
40
+ write_yaml(import_yml_path, { locale.to_s => data })
40
41
  end
41
42
 
42
43
  def parse_translation(translation)
@@ -57,4 +58,19 @@ class RailsTranslationManager::Importer
57
58
  translation
58
59
  end
59
60
  end
61
+
62
+ def import_csv_into_multiple_files(csv)
63
+ group_csv_by_file(csv).each do |group|
64
+ language_dir = File.join(import_directory, locale)
65
+
66
+ Dir.mkdir(language_dir) unless Dir.exists?(language_dir)
67
+
68
+ import_yml_path = File.join(import_directory, locale, "#{group[0]}.yml")
69
+ import_csv(group[1], import_yml_path)
70
+ end
71
+ end
72
+
73
+ def group_csv_by_file(csv)
74
+ csv.group_by { |row| row["key"].split(".").first }
75
+ end
60
76
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AllLocales
4
+ attr_reader :locale_path
5
+
6
+ def initialize(locale_path)
7
+ @locale_path = locale_path
8
+ end
9
+
10
+ def generate
11
+ paths = locale_file_paths.compact
12
+
13
+ raise NoLocaleFilesFound, "No locale files found for the supplied path" if paths.blank?
14
+
15
+ paths.flat_map do |locale_group|
16
+ {
17
+ locale: locale_group[:locale],
18
+ keys: all_keys_for_locale(locale_group)
19
+ }
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def locale_file_paths
26
+ I18n.available_locales.map do |locale|
27
+ grouped_paths = Dir[locale_path].select do |path|
28
+ locale = locale.downcase
29
+
30
+ path =~ %r{/#{locale}/} || path =~ %r{/#{locale}\.yml$}
31
+ end
32
+
33
+ { locale: locale.downcase, paths: grouped_paths } if grouped_paths.present?
34
+ end
35
+ end
36
+
37
+ def all_keys_for_locale(locale_group)
38
+ locale_group[:paths].flat_map do |path|
39
+ keys_from_file(file_path: path)
40
+ end
41
+ end
42
+
43
+ def keys_from_file(locale_hash: nil, key_chain: nil, locale_keys: [], file_path: nil)
44
+ locale_hash ||= YAML.load_file(file_path)
45
+ keys = locale_hash.keys
46
+ keys.each do |key|
47
+ if locale_hash.fetch(key).is_a?(Hash)
48
+ keys_from_file(locale_hash: locale_hash.fetch(key), key_chain: "#{key_chain}.#{key}", locale_keys: locale_keys)
49
+ else
50
+ keys.each do |final_key|
51
+ locale_keys << "#{key_chain}.#{final_key}"
52
+ end
53
+ end
54
+ end
55
+
56
+ # remove locale prefix from keys, e.g: ".en.browse.page" -> "browse.page"
57
+ locale_keys.uniq.map { |key| key.split(".")[2..].join(".") }
58
+ end
59
+
60
+ class NoLocaleFilesFound < StandardError; end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BaseChecker
4
+ attr_reader :all_locales
5
+
6
+ def initialize(all_locales)
7
+ @all_locales = all_locales
8
+ end
9
+
10
+ def report
11
+ raise "You must define a `report` method in the child class!"
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IncompatiblePlurals < BaseChecker
4
+ include LocaleCheckerHelper
5
+
6
+ def report
7
+ format_plural_errors if plural_errors.present?
8
+ end
9
+
10
+ private
11
+
12
+ def format_plural_errors
13
+ <<~OUTPUT.chomp
14
+ \e[31m[ERROR]\e[0m Incompatible plural forms, for:
15
+
16
+ #{plural_errors.join("\n\n")}
17
+
18
+ \e[1mIf the keys reported above are not plurals, rename them avoiding plural keywords: #{PLURAL_KEYS}\e[22m
19
+ OUTPUT
20
+ end
21
+
22
+ def plural_errors
23
+ @plural_errors ||= grouped_plural_keys_for_locale.flat_map do |plurals|
24
+ plural_form = all_plural_forms[plurals[:locale]]
25
+
26
+ if plural_form.blank?
27
+ "- \e[31m[ERROR]\e[0m Please add plural form for '#{plurals[:locale]}' <link to future documentation>"
28
+ else
29
+ incompatible_plurals(plurals, plural_form)
30
+ end
31
+ end
32
+ end
33
+
34
+ def incompatible_plurals(plurals, plural_form)
35
+ error_messages = plurals[:groups].map do |plural_group|
36
+ actual_plural_form = plural_group[:keys].sort
37
+
38
+ next if actual_plural_form == plural_form.sort
39
+
40
+ "- '#{plurals[:locale]}', with parent '#{plural_group[:parent]}'. Expected: #{plural_form}, actual: #{actual_plural_form}"
41
+ end
42
+
43
+ error_messages.compact
44
+ end
45
+
46
+ def grouped_plural_keys_for_locale
47
+ all_locales.map do |locale|
48
+ {
49
+ locale: locale[:locale],
50
+ groups: grouped_plural_keys(only_plurals(locale[:keys]))
51
+ }
52
+ end
53
+ end
54
+
55
+ def grouped_plural_keys(keys)
56
+ # %w[parent.key parent.other_key] -> [{ parent: "parent", keys: %w[key other_key] }]
57
+ group_by_parent_keys(keys).map do |plural_key_group|
58
+ {
59
+ parent: plural_key_group.first,
60
+ keys: plural_key_group.last.map { |key_chain| key_chain.split(".").last.to_sym }
61
+ }
62
+ end
63
+ end
64
+
65
+ def group_by_parent_keys(keys)
66
+ # %w[parent.key parent.other_key] -> { "parent" => %w[parent.key parent.other_key] }
67
+ keys.group_by do |key|
68
+ key.split('.')[0..-2].join('.')
69
+ end
70
+ end
71
+
72
+ def only_plurals(keys)
73
+ keys.select { |key| key_is_plural?(key) }
74
+ end
75
+
76
+ def all_plural_forms
77
+ @all_plural_forms ||= PluralForms.all
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LocaleCheckerHelper
4
+ PLURAL_KEYS = %w[zero one two few many other].freeze
5
+
6
+ def exclude_plurals(keys)
7
+ keys.reject { |key| key_is_plural?(key) }
8
+ end
9
+
10
+ def group_keys(locales)
11
+ locales.keys.group_by do |key|
12
+ locales[key]
13
+ end
14
+ end
15
+
16
+ def english_keys_excluding_plurals(all_locales)
17
+ exclude_plurals(all_locales.find { |locale| locale[:locale] == :en }[:keys])
18
+ end
19
+
20
+ def key_is_plural?(key)
21
+ PLURAL_KEYS.include?(key.split(".").last)
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MissingEnglishLocales < BaseChecker
4
+ include LocaleCheckerHelper
5
+
6
+ def report
7
+ grouped_missing_keys = group_keys(missing_english_locales)
8
+
9
+ format_missing_english_locales(grouped_missing_keys) if grouped_missing_keys.present?
10
+ end
11
+
12
+ private
13
+
14
+ def format_missing_english_locales(keys)
15
+ formatted_keys = keys.to_a.map do |group|
16
+ "\e[1mMissing English keys:\e[22m #{group[0]}\n\e[1mFound in:\e[22m #{group[1]}"
17
+ end
18
+
19
+ "\e[31m[ERROR]\e[0m Missing English locales, either remove these keys from the foreign locales or add them to the English locales\n\n#{formatted_keys.join("\n\n")}"
20
+ end
21
+
22
+ def missing_english_locales
23
+ all_locales.each_with_object({}) do |locale, hsh|
24
+ missing_keys = exclude_plurals(locale[:keys]) - english_keys_excluding_plurals(all_locales)
25
+
26
+ next if missing_keys.blank?
27
+
28
+ hsh[locale[:locale]] = missing_keys
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MissingForeignLocales < BaseChecker
4
+ include LocaleCheckerHelper
5
+
6
+ def report
7
+ grouped_missing_keys = group_keys(missing_foreign_locales)
8
+
9
+ format_missing_foreign_locales(grouped_missing_keys) if grouped_missing_keys.present?
10
+ end
11
+
12
+ private
13
+
14
+ def format_missing_foreign_locales(keys)
15
+ formatted_keys = keys.to_a.map do |group|
16
+ "\e[1mMissing foreign keys:\e[22m #{group[0]}\n\e[1mAbsent from:\e[22m #{group[1]}"
17
+ end
18
+
19
+ "\e[31m[ERROR]\e[0m Missing foreign locales, either add these keys to the foreign locales or remove them from the English locales\e[0m\n\n#{formatted_keys.join("\n\n")}"
20
+ end
21
+
22
+ def missing_foreign_locales
23
+ all_locales.each_with_object({}) do |locale, hsh|
24
+ missing_keys = english_keys_excluding_plurals(all_locales) - exclude_plurals(locale[:keys])
25
+
26
+ next if missing_keys.blank?
27
+
28
+ hsh[locale[:locale]] = missing_keys
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PluralForms
4
+ def self.all
5
+ I18n.available_locales.each_with_object({}) do |locale, hsh|
6
+ begin
7
+ # fetches plural form (rule) from rails-i18n for locale
8
+ plural_form = I18n.with_locale(locale) { I18n.t!("i18n.plural.keys") }.sort
9
+ rescue I18n::MissingTranslationData
10
+ plural_form = nil
11
+ end
12
+
13
+ hsh[locale.downcase] = plural_form
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsTranslationManager
4
+ class LocaleChecker
5
+ attr_reader :locale_path
6
+
7
+ CHECKER_CLASSES = [MissingEnglishLocales,
8
+ MissingForeignLocales,
9
+ IncompatiblePlurals].freeze
10
+
11
+ def initialize(locale_path)
12
+ @locale_path = locale_path
13
+ end
14
+
15
+ def validate_locales
16
+ output_result
17
+ rescue AllLocales::NoLocaleFilesFound => e
18
+ puts e
19
+ false
20
+ end
21
+
22
+ private
23
+
24
+ def output_result
25
+ errors = checker_errors.compact
26
+
27
+ if errors.blank?
28
+ puts "Locale files are in sync, nice job!"
29
+ true
30
+ else
31
+ errors.each do |error_message|
32
+ puts "\n"
33
+ puts error_message
34
+ puts "\n"
35
+ end
36
+ false
37
+ end
38
+ end
39
+
40
+ def checker_errors
41
+ CHECKER_CLASSES.flat_map do |checker|
42
+ checker.new(all_locales).report
43
+ end
44
+ end
45
+
46
+ def all_locales
47
+ @all_locales ||= AllLocales.new(locale_path).generate
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsTranslationManager
2
- VERSION = "0.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,17 @@
1
+ require 'yaml'
2
+
3
+ module YAMLWriter
4
+
5
+ # `to_yaml` outputs an initial '---\n', which is supposed to be a document
6
+ # separator. We don't want this in the written locale files, so we serialize
7
+ # to a string, remove the header and any trailing whitehspace, and write to
8
+ # the file.
9
+ def write_yaml(filepath, data)
10
+ File.open(filepath, "w") do |f|
11
+ yaml = data.to_yaml(separator: "")
12
+ yaml_without_header = yaml.split("\n").map { |l| l.gsub(/\s+$/, '') }[1..-1].join("\n")
13
+ f.write(yaml_without_header)
14
+ f.puts
15
+ end
16
+ end
17
+ end