rails_translation_manager 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +8 -0
  5. data/Rakefile +13 -7
  6. data/config/locales/plurals.rb +49 -0
  7. data/lib/rails_translation_manager/cleaner.rb +14 -0
  8. data/lib/rails_translation_manager/locale_checker/all_locales.rb +61 -0
  9. data/lib/rails_translation_manager/locale_checker/base_checker.rb +13 -0
  10. data/lib/rails_translation_manager/locale_checker/incompatible_plurals.rb +79 -0
  11. data/lib/rails_translation_manager/locale_checker/locale_checker_helper.rb +23 -0
  12. data/lib/rails_translation_manager/locale_checker/missing_english_locales.rb +31 -0
  13. data/lib/rails_translation_manager/locale_checker/missing_foreign_locales.rb +31 -0
  14. data/lib/rails_translation_manager/locale_checker/plural_forms.rb +16 -0
  15. data/lib/rails_translation_manager/locale_checker.rb +50 -0
  16. data/lib/rails_translation_manager/version.rb +1 -1
  17. data/lib/rails_translation_manager.rb +20 -0
  18. data/lib/tasks/translation.rake +13 -0
  19. data/lib/tasks/translation_helper.rb +11 -0
  20. data/rails_translation_manager.gemspec +4 -1
  21. data/spec/locales/cleaner/clean.yml +5 -0
  22. data/spec/locales/cleaner/with_whitespace.yml +5 -0
  23. data/spec/locales/in_sync/cy/browse.yml +12 -0
  24. data/spec/locales/in_sync/en/browse.yml +8 -0
  25. data/spec/locales/out_of_sync/cy.yml +3 -0
  26. data/spec/locales/out_of_sync/en.yml +6 -0
  27. data/spec/rails_translation_manager/cleaner_spec.rb +13 -0
  28. data/spec/rails_translation_manager/locale_checker/all_locales_spec.rb +24 -0
  29. data/spec/rails_translation_manager/locale_checker/incompatible_plurals_spec.rb +94 -0
  30. data/spec/rails_translation_manager/locale_checker/locale_checker_helper_spec.rb +61 -0
  31. data/spec/rails_translation_manager/locale_checker/missing_english_locales_spec.rb +72 -0
  32. data/spec/rails_translation_manager/locale_checker/missing_foreign_locales_spec.rb +80 -0
  33. data/spec/rails_translation_manager/locale_checker/plural_forms_spec.rb +27 -0
  34. data/spec/rails_translation_manager/locale_checker_spec.rb +68 -0
  35. data/spec/spec_helper.rb +19 -0
  36. data/spec/support/tasks.rb +7 -0
  37. data/spec/tasks/translation_spec.rb +38 -0
  38. data/tmp/.gitkeep +0 -0
  39. metadata +95 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9053fb88fb8e337bba059e6b16a13f16ffc058de0cc87b6be236c78d92a54dcd
4
- data.tar.gz: 626c3ea8e720db73bce5ca3618f58d4ce121b7b845dd66e282572c4f9830406a
3
+ metadata.gz: f2407e2bb0a7f4cf4628d9a9f35bcfb668b085c23bb92418f708c09f571c1b92
4
+ data.tar.gz: e0584a3f42216bdc0b9589ab6bb26e7246d6856e0b33f7599ac1e6eed0070af8
5
5
  SHA512:
6
- metadata.gz: 6d5334a90e4b52b7c4976846984dc83b9adb8ed2e3724eb181dde8f86118f72fc42375466285487f9eb3f1dafc78799662fe513cacdaad5902ededdbad54f2c1
7
- data.tar.gz: ae8c750799c3882ce9b11408968e8322ea37f0bffcb3ca76ef8d117de8ab8ce30e65661e4a5bb1e297db106ff29436c03bc169bf31b6187f9dec309da4bcab7d
6
+ metadata.gz: '0249a48778dade714f10f47d4185136da8a8a9d11313273d659817dba1968c01ed5756c42b87e0b1963d3f9a41c1316392317f7214137dca5af8e4962f311f4a'
7
+ data.tar.gz: 63553a493e8c36c8daa8fe0bdf388384c2bf80fca3e41fd038f4ed8f92078c4accfe18c9a9561c2dc9faa82a35e9cd4642c7b1894a21a1fbd2fb0302c18092a1
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.5
1
+ 2.6.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.0.0
2
+
3
+ Adds logic to verify locale files are in sync with each other and have the
4
+ correct plural forms. Also introduces RSpec into the Gem which will replace
5
+ Minitest in the coming iterations.
6
+
7
+ 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.
8
+
1
9
  ## 0.1.0
2
10
 
3
11
  Don't change the $LOAD_PATH in translation.rake.
data/Rakefile CHANGED
@@ -1,11 +1,17 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
3
2
 
4
- Rake::TestTask.new("test") do |t|
5
- t.description = "Run tests"
6
- t.libs << "test"
7
- t.test_files = FileList["test/**/*_test.rb"]
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']
8
12
  t.verbose = true
9
13
  end
10
14
 
11
- 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
@@ -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.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,9 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails_translation_manager/version"
2
4
  require "rails_translation_manager/railtie" if defined?(Rails)
3
5
  require "rails-i18n"
4
6
 
7
+ require "rails_translation_manager/locale_checker/base_checker"
8
+ require "rails_translation_manager/locale_checker/locale_checker_helper"
9
+ require "rails_translation_manager/locale_checker/missing_foreign_locales"
10
+ require "rails_translation_manager/locale_checker/missing_english_locales"
11
+ require "rails_translation_manager/locale_checker/plural_forms"
12
+ require "rails_translation_manager/locale_checker/incompatible_plurals"
13
+ require "rails_translation_manager/locale_checker/all_locales"
14
+ require "rails_translation_manager/locale_checker"
15
+ require "rails_translation_manager/cleaner"
16
+
5
17
  module RailsTranslationManager
6
18
  autoload :Exporter, "rails_translation_manager/exporter"
7
19
  autoload :Importer, "rails_translation_manager/importer"
8
20
  autoload :Stealer, "rails_translation_manager/stealer"
21
+
22
+ rails_i18n_path = Gem::Specification.find_by_name("rails-i18n").gem_dir
23
+ rails_translation_manager = Gem::Specification.find_by_name("rails_translation_manager").gem_dir
24
+
25
+ I18n.load_path.concat(
26
+ Dir["#{rails_i18n_path}/rails/pluralization/*.rb"],
27
+ ["#{rails_translation_manager}/config/locales/plurals.rb"]
28
+ )
9
29
  end
@@ -1,4 +1,6 @@
1
1
  require "rails_translation_manager"
2
+ require "i18n/tasks/cli"
3
+ require_relative "../tasks/translation_helper"
2
4
 
3
5
  namespace :translation do
4
6
 
@@ -83,4 +85,15 @@ namespace :translation do
83
85
  end
84
86
  end
85
87
 
88
+ desc "Add missing translations"
89
+ task(:add_missing, [:locale] => [:environment]) do |t, args|
90
+ I18n::Tasks::CLI.start(TranslationHelper.new(["add-missing", "--nil-value"], args[:locale]).with_optional_locale)
91
+ RailsTranslationManager::Cleaner.new(Rails.root.join("config", "locales")).clean
92
+ end
93
+
94
+ desc "Normalize translations"
95
+ task(:normalize, [:locale] => [:environment]) do |t, args|
96
+ I18n::Tasks::CLI.start(TranslationHelper.new(["normalize"], args[:locale]).with_optional_locale)
97
+ RailsTranslationManager::Cleaner.new(Rails.root.join("config", "locales")).clean
98
+ end
86
99
  end
@@ -0,0 +1,11 @@
1
+ class TranslationHelper
2
+
3
+ def initialize(task_options, locale)
4
+ @task_options = task_options
5
+ @locale = locale
6
+ end
7
+
8
+ def with_optional_locale
9
+ @locale ? @task_options << ["-l", @locale] : @task_options
10
+ end
11
+ end
@@ -20,8 +20,11 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "rails-i18n"
22
22
  spec.add_dependency "activesupport"
23
+ spec.add_dependency "i18n-tasks"
23
24
 
24
- spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "bundler"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
26
27
  spec.add_development_dependency "minitest"
28
+ spec.add_development_dependency "rspec"
29
+ spec.add_development_dependency "byebug"
27
30
  end
@@ -0,0 +1,5 @@
1
+ ---
2
+ en:
3
+ test: key
4
+ key_1: foo
5
+ key_2: bar
@@ -0,0 +1,5 @@
1
+ ---
2
+ en:
3
+ test: key
4
+ key_1: foo
5
+ key_2: bar
@@ -0,0 +1,12 @@
1
+ ---
2
+ cy:
3
+ in_sync:
4
+ key: bwyd
5
+ second_key: cath
6
+ some_plurals:
7
+ few:
8
+ many:
9
+ one:
10
+ other:
11
+ two:
12
+ zero:
@@ -0,0 +1,8 @@
1
+ ---
2
+ cy:
3
+ in_sync:
4
+ key: food
5
+ second_key: cat
6
+ some_plurals:
7
+ one:
8
+ other:
@@ -0,0 +1,3 @@
1
+ ---
2
+ cy:
3
+ other_test: key
@@ -0,0 +1,6 @@
1
+ ---
2
+ en:
3
+ test: key
4
+ wrong_plural:
5
+ one: correct
6
+ zero: failure
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe RailsTranslationManager::Cleaner do
4
+
5
+ before(:each) do
6
+ FileUtils.cp('spec/locales/cleaner/with_whitespace.yml', 'tmp/')
7
+ end
8
+
9
+ it 'removes whitespace at the end of the line' do
10
+ described_class.new('tmp').clean
11
+ expect(FileUtils.compare_file('tmp/with_whitespace.yml', 'spec/locales/cleaner/clean.yml')).to be_truthy
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe AllLocales do
6
+ context "when there are locale files" do
7
+ it "generates an array of hashes for all locales" do
8
+ expect(described_class.new("spec/locales/out_of_sync/*.yml").generate)
9
+ .to eq(
10
+ [
11
+ { keys: %w[test wrong_plural wrong_plural.one wrong_plural.zero], locale: :en },
12
+ { keys: %w[other_test], locale: :cy }
13
+ ]
14
+ )
15
+ end
16
+ end
17
+
18
+ context "when there aren't any locale files present" do
19
+ it "raises an error" do
20
+ expect { described_class.new("path/to/none/existant/locale/files").generate }
21
+ .to raise_error(AllLocales::NoLocaleFilesFound, 'No locale files found for the supplied path')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe IncompatiblePlurals do
6
+ context "when there are missing plurals" do
7
+ let(:all_locales) do
8
+ [
9
+ {
10
+ locale: :en,
11
+ keys: %w[browse.some_key.other browse.some_other_key.one]
12
+ }
13
+ ]
14
+ end
15
+
16
+ before do
17
+ allow(PluralForms).to receive(:all).and_return({ en: %i[one other] })
18
+ end
19
+
20
+ it "returns the missing plural forms" do
21
+ expect(described_class.new(all_locales).report)
22
+ .to eq(
23
+ <<~OUTPUT.chomp
24
+ \e[31m[ERROR]\e[0m Incompatible plural forms, for:
25
+
26
+ - 'en', with parent 'browse.some_key'. Expected: [:one, :other], actual: [:other]
27
+
28
+ - 'en', with parent 'browse.some_other_key'. Expected: [:one, :other], actual: [:one]
29
+
30
+ \e[1mIf the keys reported above are not plurals, rename them avoiding plural keywords: #{LocaleCheckerHelper::PLURAL_KEYS}\e[22m
31
+ OUTPUT
32
+ )
33
+ end
34
+ end
35
+
36
+ context "when there aren't any missing plurals" do
37
+ let(:all_locales) do
38
+ [
39
+ {
40
+ locale: :en,
41
+ keys: %w[browse.some_key.other browse.some_key.one]
42
+ }
43
+ ]
44
+ end
45
+
46
+ before do
47
+ allow(PluralForms).to receive(:all).and_return({ en: %i[one other] })
48
+ end
49
+
50
+ it "returns nil" do
51
+ expect(described_class.new(all_locales).report)
52
+ .to be_nil
53
+ end
54
+ end
55
+
56
+ context "when plural forms aren't available for the locales" do
57
+ let(:all_locales) do
58
+ [
59
+ {
60
+ locale: :en,
61
+ keys: %w[browse.some_key.one browse.some_key.other]
62
+ },
63
+ {
64
+ locale: :cy,
65
+ keys: %w[browse.some_key.few
66
+ browse.some_key.many
67
+ browse.some_key.one
68
+ browse.some_key.other
69
+ browse.some_key.two
70
+ browse.some_key.zero]
71
+ }
72
+ ]
73
+ end
74
+
75
+ before do
76
+ allow(PluralForms).to receive(:all).and_return({ cy: nil, en: nil })
77
+ end
78
+
79
+ it "outputs the missing plural forms" do
80
+ expect(described_class.new(all_locales).report)
81
+ .to eq(
82
+ <<~OUTPUT.chomp
83
+ \e[31m[ERROR]\e[0m Incompatible plural forms, for:
84
+
85
+ - \e[31m[ERROR]\e[0m Please add plural form for 'en' <link to future documentation>
86
+
87
+ - \e[31m[ERROR]\e[0m Please add plural form for 'cy' <link to future documentation>
88
+
89
+ \e[1mIf the keys reported above are not plurals, rename them avoiding plural keywords: #{LocaleCheckerHelper::PLURAL_KEYS}\e[22m
90
+ OUTPUT
91
+ )
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe LocaleCheckerHelper do
6
+ include LocaleCheckerHelper
7
+
8
+ describe "#exclude_plurals" do
9
+ it "excludes plural groups" do
10
+ expect(exclude_plurals(%w[key.one key.other]))
11
+ .to eq([])
12
+ end
13
+
14
+ it "doesn't exclude non-plurals" do
15
+ expect(exclude_plurals(%w[thing.bing red.blue]))
16
+ .to eq(%w[thing.bing red.blue])
17
+ end
18
+ end
19
+
20
+ describe "#group_keys" do
21
+ it "groups locales by keys" do
22
+ keys = {
23
+ en: %w[key.first key.second],
24
+ fr: %w[key.first key.second],
25
+ es: %w[key.second]
26
+ }
27
+
28
+ expect(group_keys(keys))
29
+ .to eq(
30
+ {
31
+ %w[key.first key.second] => %i[en fr],
32
+ %w[key.second] => %i[es]
33
+ }
34
+ )
35
+ end
36
+ end
37
+
38
+ describe "#english_keys_excluding_plurals" do
39
+ it "returns english keys excluding plurals" do
40
+ keys = [
41
+ { locale: :en, keys: %w[key.english plural.other] },
42
+ { locale: :fr, keys: %w[key.french] }
43
+ ]
44
+
45
+ expect(english_keys_excluding_plurals(keys))
46
+ .to eq(%w[key.english])
47
+ end
48
+ end
49
+
50
+ describe "#key_is_plural?" do
51
+ it "returns true if key is a plural keyword" do
52
+ expect(key_is_plural?("other"))
53
+ .to eq(true)
54
+ end
55
+
56
+ it "returns false if key isn't a plural keyword" do
57
+ expect(key_is_plural?("random"))
58
+ .to eq(false)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe MissingEnglishLocales do
6
+ context "where there are missing English locales" do
7
+ let(:all_locales) do
8
+ [
9
+ {
10
+ locale: :en,
11
+ keys: %w[browse.same_key]
12
+ },
13
+ {
14
+ locale: :cy,
15
+ keys: %w[browse.same_key browse.extra_key]
16
+ }
17
+ ]
18
+ end
19
+
20
+ it "outputs the missing locales" do
21
+ expect(described_class.new(all_locales).report)
22
+ .to eq(
23
+ <<~OUTPUT.chomp
24
+ \e[31m[ERROR]\e[0m Missing English locales, either remove these keys from the foreign locales or add them to the English locales
25
+
26
+ \e[1mMissing English keys:\e[22m ["browse.extra_key"]
27
+ \e[1mFound in:\e[22m [:cy]
28
+ OUTPUT
29
+ )
30
+ end
31
+ end
32
+
33
+ context "where there aren't missing English locales" do
34
+ let(:all_locales) do
35
+ [
36
+ {
37
+ locale: :en,
38
+ keys: %w[browse.same_key]
39
+ },
40
+ {
41
+ locale: :cy,
42
+ keys: %w[browse.same_key]
43
+ }
44
+ ]
45
+ end
46
+
47
+ it 'outputs nil' do
48
+ expect(described_class.new(all_locales).report)
49
+ .to be_nil
50
+ end
51
+ end
52
+
53
+ context "when there are plurals" do
54
+ let(:all_locales) do
55
+ [
56
+ {
57
+ locale: :en,
58
+ keys: []
59
+ },
60
+ {
61
+ locale: :cy,
62
+ keys: %w[browse.plurals.one browse.fake_plurals.other]
63
+ }
64
+ ]
65
+ end
66
+
67
+ it "doesn't include them in the report" do
68
+ expect(described_class.new(all_locales).report)
69
+ .to be_nil
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe MissingForeignLocales do
6
+ context "when there are missing foreign locales" do
7
+ let(:all_locales) do
8
+ [
9
+ {
10
+ locale: :en,
11
+ keys: %w[browse.same_key browse.extra_nested_key.nest]
12
+ },
13
+ {
14
+ locale: :cy,
15
+ keys: %w[browse.same_key]
16
+ },
17
+ {
18
+ locale: :fr,
19
+ keys: %w[browse.same_key]
20
+ }
21
+ ]
22
+ end
23
+
24
+ before do
25
+ I18n.available_locales << %i[fr]
26
+ end
27
+
28
+ it "outputs the missing locales and groups them by common keys" do
29
+ expect(described_class.new(all_locales).report)
30
+ .to eq(
31
+ <<~OUTPUT.chomp
32
+ \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
33
+
34
+ \e[1mMissing foreign keys:\e[22m ["browse.extra_nested_key.nest"]
35
+ \e[1mAbsent from:\e[22m [:cy, :fr]
36
+ OUTPUT
37
+ )
38
+ end
39
+ end
40
+
41
+ context "when there aren't missing foreign locales" do
42
+ let(:all_locales) do
43
+ [
44
+ {
45
+ locale: :en,
46
+ keys: %w[browse.same_key]
47
+ },
48
+ {
49
+ locale: :cy,
50
+ keys: %w[browse.same_key]
51
+ }
52
+ ]
53
+ end
54
+
55
+ it "outputs nil" do
56
+ expect(described_class.new(all_locales).report)
57
+ .to be_nil
58
+ end
59
+ end
60
+
61
+ context "when there are plurals" do
62
+ let(:all_locales) do
63
+ [
64
+ {
65
+ locale: :en,
66
+ keys: %w[browse.plurals.one browse.fake_plurals.other]
67
+ },
68
+ {
69
+ locale: :cy,
70
+ keys: []
71
+ }
72
+ ]
73
+ end
74
+
75
+ it "doesn't include them in the report" do
76
+ expect(described_class.new(all_locales).report)
77
+ .to be_nil
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe PluralForms do
6
+ it "returns plural forms" do
7
+ expect(described_class.all).to include(
8
+ { cy: %i[few many one other two zero], en: %i[one other] }
9
+ )
10
+ end
11
+
12
+ context "when there are missing plural forms" do
13
+ around do |example|
14
+ load_path = I18n.load_path
15
+ # strips I18n of all pluralization files (rules)
16
+ I18n.load_path = I18n.load_path.flatten.reject { |path| path =~ /plural/ }
17
+
18
+ example.run
19
+
20
+ I18n.load_path = load_path
21
+ end
22
+
23
+ it "returns nil for associated locales" do
24
+ expect(described_class.all).to include({ cy: nil, en: nil })
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe RailsTranslationManager::LocaleChecker do
6
+ context "when the locales are valid" do
7
+ it "calls each checker class" do
8
+ described_class::CHECKER_CLASSES.each do |checker|
9
+ expect_any_instance_of(checker).to receive(:report)
10
+ end
11
+
12
+ described_class.new("spec/locales/in_sync/**/*.yml").validate_locales
13
+ end
14
+
15
+ it "outputs a confirmation" do
16
+ expect { described_class.new("spec/locales/in_sync/**/*.yml").validate_locales }
17
+ .to output("Locale files are in sync, nice job!\n").to_stdout
18
+ end
19
+
20
+ it "returns true" do
21
+ expect(described_class.new("spec/locales/in_sync/**/*.yml").validate_locales)
22
+ .to eq(true)
23
+ end
24
+ end
25
+
26
+ context "when the locales are not valid" do
27
+ it "calls each checker class" do
28
+ described_class::CHECKER_CLASSES.each do |checker|
29
+ expect_any_instance_of(checker).to receive(:report)
30
+ end
31
+
32
+ described_class.new("spec/locales/out_of_sync/*.yml").validate_locales
33
+ end
34
+
35
+ it "outputs the report" do
36
+ printed = capture_stdout do
37
+ described_class.new("spec/locales/out_of_sync/*.yml").validate_locales
38
+ end
39
+
40
+ expect(printed.scan(/\[ERROR\]/).count).to eq(3)
41
+ end
42
+
43
+ it "returns false" do
44
+ expect(described_class.new("spec/locales/out_of_sync/*.yml").validate_locales)
45
+ .to eq(false)
46
+ end
47
+ end
48
+
49
+ context "when the locale path doesn't relate to any YAML files" do
50
+ it "doesn't call any checker classes" do
51
+ described_class::CHECKER_CLASSES.each do |checker|
52
+ expect_any_instance_of(checker).to_not receive(:report)
53
+ end
54
+
55
+ described_class.new("some/random/path").validate_locales
56
+ end
57
+
58
+ it "outputs an error message" do
59
+ expect { described_class.new("some/random/path").validate_locales }
60
+ .to output("No locale files found for the supplied path\n").to_stdout
61
+ end
62
+
63
+ it "returns false" do
64
+ expect(described_class.new("some/random/path").validate_locales)
65
+ .to eq(false)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_translation_manager"
4
+
5
+ RSpec.configure do |config|
6
+ config.before do
7
+ I18n.available_locales = %i[en cy]
8
+ config.before { allow($stdout).to receive(:puts) }
9
+ end
10
+
11
+ def capture_stdout(&blk)
12
+ old = $stdout
13
+ $stdout = fake = StringIO.new
14
+ blk.call
15
+ fake.string
16
+ ensure
17
+ $stdout = old
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:suite) do
5
+ Dir.glob('lib/tasks/*.rake').each { |r| Rake::DefaultLoader.new.load r }
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require_relative '../../spec/support/tasks'
3
+
4
+ describe 'rake tasks' do
5
+ before do
6
+ fake_rails = double()
7
+ fake_rails.stub(:root) { Pathname.new('spec') }
8
+ stub_const("Rails", fake_rails)
9
+ end
10
+
11
+ describe 'translation:add_missing', type: :task do
12
+ let(:task) { Rake::Task["translation:add_missing"] }
13
+
14
+ it 'is executed' do
15
+ expect { task.execute }.to output.to_stdout
16
+ end
17
+
18
+ it 'triggers i18n task and allows to receive the right arguments' do
19
+ allow(I18n::Tasks::CLI).to receive(:start)
20
+ task.execute
21
+ expect(I18n::Tasks::CLI).to have_received(:start).with(["add-missing", "--nil-value"])
22
+ end
23
+ end
24
+
25
+ describe 'translation:normalize', type: :task do
26
+ let(:task) { Rake::Task["translation:normalize"] }
27
+
28
+ it 'is executed' do
29
+ expect { task.execute }.to_not output.to_stdout
30
+ end
31
+
32
+ it 'triggers i18n task and allows to receive the right arguments' do
33
+ allow(I18n::Tasks::CLI).to receive(:start)
34
+ task.execute
35
+ expect(I18n::Tasks::CLI).to have_received(:start).with(["normalize"])
36
+ end
37
+ end
38
+ end
data/tmp/.gitkeep ADDED
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_translation_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edd Sowden
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-24 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails-i18n
@@ -38,20 +38,34 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: i18n-tasks
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '1.7'
61
+ version: '0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '1.7'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,34 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: byebug
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description: ''
84
126
  email:
85
127
  - edd.sowden@digital.cabinet-office.gov.uk
@@ -97,22 +139,51 @@ files:
97
139
  - LICENSE.txt
98
140
  - README.md
99
141
  - Rakefile
142
+ - config/locales/plurals.rb
100
143
  - lib/rails_translation_manager.rb
144
+ - lib/rails_translation_manager/cleaner.rb
101
145
  - lib/rails_translation_manager/exporter.rb
102
146
  - lib/rails_translation_manager/importer.rb
147
+ - lib/rails_translation_manager/locale_checker.rb
148
+ - lib/rails_translation_manager/locale_checker/all_locales.rb
149
+ - lib/rails_translation_manager/locale_checker/base_checker.rb
150
+ - lib/rails_translation_manager/locale_checker/incompatible_plurals.rb
151
+ - lib/rails_translation_manager/locale_checker/locale_checker_helper.rb
152
+ - lib/rails_translation_manager/locale_checker/missing_english_locales.rb
153
+ - lib/rails_translation_manager/locale_checker/missing_foreign_locales.rb
154
+ - lib/rails_translation_manager/locale_checker/plural_forms.rb
103
155
  - lib/rails_translation_manager/railtie.rb
104
156
  - lib/rails_translation_manager/stealer.rb
105
157
  - lib/rails_translation_manager/validator.rb
106
158
  - lib/rails_translation_manager/version.rb
107
159
  - lib/rails_translation_manager/yaml_writer.rb
108
160
  - lib/tasks/translation.rake
161
+ - lib/tasks/translation_helper.rb
109
162
  - rails_translation_manager.gemspec
163
+ - spec/locales/cleaner/clean.yml
164
+ - spec/locales/cleaner/with_whitespace.yml
165
+ - spec/locales/in_sync/cy/browse.yml
166
+ - spec/locales/in_sync/en/browse.yml
167
+ - spec/locales/out_of_sync/cy.yml
168
+ - spec/locales/out_of_sync/en.yml
169
+ - spec/rails_translation_manager/cleaner_spec.rb
170
+ - spec/rails_translation_manager/locale_checker/all_locales_spec.rb
171
+ - spec/rails_translation_manager/locale_checker/incompatible_plurals_spec.rb
172
+ - spec/rails_translation_manager/locale_checker/locale_checker_helper_spec.rb
173
+ - spec/rails_translation_manager/locale_checker/missing_english_locales_spec.rb
174
+ - spec/rails_translation_manager/locale_checker/missing_foreign_locales_spec.rb
175
+ - spec/rails_translation_manager/locale_checker/plural_forms_spec.rb
176
+ - spec/rails_translation_manager/locale_checker_spec.rb
177
+ - spec/spec_helper.rb
178
+ - spec/support/tasks.rb
179
+ - spec/tasks/translation_spec.rb
110
180
  - test/rails_translation_manager/exporter_test.rb
111
181
  - test/rails_translation_manager/importer_test.rb
112
182
  - test/rails_translation_manager/stealer_test.rb
113
183
  - test/rails_translation_manager/validator_test.rb
114
184
  - test/rails_translation_manager/yaml_writer_test.rb
115
185
  - test/test_helper.rb
186
+ - tmp/.gitkeep
116
187
  homepage: ''
117
188
  licenses:
118
189
  - MIT
@@ -132,12 +203,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
203
  - !ruby/object:Gem::Version
133
204
  version: '0'
134
205
  requirements: []
135
- rubyforge_project:
136
- rubygems_version: 2.7.6
206
+ rubygems_version: 3.0.3
137
207
  signing_key:
138
208
  specification_version: 4
139
209
  summary: Tasks to manage translation files
140
210
  test_files:
211
+ - spec/locales/cleaner/clean.yml
212
+ - spec/locales/cleaner/with_whitespace.yml
213
+ - spec/locales/in_sync/cy/browse.yml
214
+ - spec/locales/in_sync/en/browse.yml
215
+ - spec/locales/out_of_sync/cy.yml
216
+ - spec/locales/out_of_sync/en.yml
217
+ - spec/rails_translation_manager/cleaner_spec.rb
218
+ - spec/rails_translation_manager/locale_checker/all_locales_spec.rb
219
+ - spec/rails_translation_manager/locale_checker/incompatible_plurals_spec.rb
220
+ - spec/rails_translation_manager/locale_checker/locale_checker_helper_spec.rb
221
+ - spec/rails_translation_manager/locale_checker/missing_english_locales_spec.rb
222
+ - spec/rails_translation_manager/locale_checker/missing_foreign_locales_spec.rb
223
+ - spec/rails_translation_manager/locale_checker/plural_forms_spec.rb
224
+ - spec/rails_translation_manager/locale_checker_spec.rb
225
+ - spec/spec_helper.rb
226
+ - spec/support/tasks.rb
227
+ - spec/tasks/translation_spec.rb
141
228
  - test/rails_translation_manager/exporter_test.rb
142
229
  - test/rails_translation_manager/importer_test.rb
143
230
  - test/rails_translation_manager/stealer_test.rb