rails_translation_manager 0.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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