From f9aec5c09cb16e7b5de83cb8edf41cc06f160f32 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 24 Oct 2018 06:11:30 +0200 Subject: [PATCH] Add missing-plural-keys Fix #308 --- config/locales/en.yml | 4 ++ config/locales/ru.yml | 1 + i18n-tasks.gemspec | 1 + lib/i18n/tasks.rb | 6 +++ lib/i18n/tasks/command/commands/missing.rb | 11 ++++ lib/i18n/tasks/plural_keys.rb | 23 ++++++++ lib/i18n/tasks/reports/base.rb | 4 ++ lib/i18n/tasks/reports/terminal.rb | 18 +++++++ spec/plural_keys_spec.rb | 62 +++++++++++++++------- 9 files changed, 112 insertions(+), 18 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index b5eb4c4a..c83d0bbb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -46,6 +46,7 @@ en: health: is everything OK? irb: start REPL session within i18n-tasks context missing: show missing translations + missing_plural_keys: show missing pluralizations mv: rename/merge the keys in locale data that match the given pattern normalize: 'normalize translation data: sort and move to the right files' remove_unused: remove unused keys @@ -108,6 +109,9 @@ en: missing: details_title: Value in other locales or source none: No translations are missing. + missing_plural_keys: + details_title: Missing plural forms + none: All pluralizations are complete. remove_unused: confirm: one: "%{count} translation will be removed from %{locales}." diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 22c71955..ecabfb43 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -44,6 +44,7 @@ ru: health: Всё ОК? irb: начать REPL сессию в контексте i18n-tasks missing: показать недостающие переводы + missing_plural_keys: показать недостающие плюрализации mv: переименовать / объединить ключи, которые соответствуют заданному шаблону normalize: нормализовать файлы переводов (сортировка и распределение) remove_unused: удалить неиспользуемые ключи diff --git a/i18n-tasks.gemspec b/i18n-tasks.gemspec index 3a295d07..96286a46 100644 --- a/i18n-tasks.gemspec +++ b/i18n-tasks.gemspec @@ -38,6 +38,7 @@ TEXT s.add_dependency 'erubi' s.add_dependency 'highline', '>= 2.0.0' s.add_dependency 'i18n' + s.add_dependency 'rails-i18n' s.add_dependency 'parser', '>= 2.2.3.0' s.add_dependency 'rainbow', '>= 2.2.2', '< 4.0' s.add_dependency 'terminal-table', '>= 1.5.1' diff --git a/lib/i18n/tasks.rb b/lib/i18n/tasks.rb index f7950486..2d94e775 100644 --- a/lib/i18n/tasks.rb +++ b/lib/i18n/tasks.rb @@ -64,6 +64,12 @@ module Data # Add internal locale data to i18n gem load path require 'i18n' + Dir[File.join(I18n::Tasks.gem_path, 'config', 'locales', '*.yml')].each do |locale_file| I18n.config.load_path << locale_file end + +# Load pluralization data +require 'rails-i18n' +I18n.enforce_available_locales = false +RailsI18n::Railtie.add('rails/pluralization/*.rb') diff --git a/lib/i18n/tasks/command/commands/missing.rb b/lib/i18n/tasks/command/commands/missing.rb index e32de015..ff9b1ad2 100644 --- a/lib/i18n/tasks/command/commands/missing.rb +++ b/lib/i18n/tasks/command/commands/missing.rb @@ -33,6 +33,17 @@ def missing(opt = {}) :exit_1 unless forest.empty? end + cmd :missing_plural_keys, + pos: '[locale ...]', + desc: t('i18n_tasks.cmd.desc.missing_plural_keys'), + args: %i[locales out_format] + + def missing_plural_keys(opt = {}) + forest = i18n.missing_plural_keys(opt.slice(:locales)) + print_forest forest, opt, :missing_plural_keys + :exit_1 unless forest.empty? + end + cmd :translate_missing, pos: '[locale ...]', desc: t('i18n_tasks.cmd.desc.translate_missing'), diff --git a/lib/i18n/tasks/plural_keys.rb b/lib/i18n/tasks/plural_keys.rb index d9d0c5e4..83d38ba6 100644 --- a/lib/i18n/tasks/plural_keys.rb +++ b/lib/i18n/tasks/plural_keys.rb @@ -16,6 +16,29 @@ def collapse_plural_nodes!(tree) tree end + def missing_plural_keys(locales: nil) + locales ||= self.locales + + locales.inject(empty_forest) do |tree, locale| + return tree unless I18n.exists?(:'i18n.plural.keys', locale) + + required_keys = Set.new(I18n.t(:'i18n.plural.keys', locale: locale, resolve: false)) + + data[locale].leaves.map(&:parent).compact.uniq.each do |node| + children = node.children + present_keys = Set.new(children.to_hash.keys.map(&:to_sym)) + next if !plural_forms?(children) || present_keys >= required_keys + node.value = children.to_hash + node.children = nil + node.data[:missing_keys] = (required_keys - present_keys).to_a + tree.merge!(node.walk_to_root.reduce(nil) { |c, p| [p.derive(children: c)] }) + end + + tree.each { |root| root.data[:type] = :missing_plural_keys } + tree + end + end + # @param [String] key i18n key # @param [String] locale to pull key data from # @return [String] the base form if the key is a specific plural form (e.g. apple for apple.many), the key otherwise. diff --git a/lib/i18n/tasks/reports/base.rb b/lib/i18n/tasks/reports/base.rb index 0424aed7..71baf4f0 100644 --- a/lib/i18n/tasks/reports/base.rb +++ b/lib/i18n/tasks/reports/base.rb @@ -21,6 +21,10 @@ def missing_title(forest) "Missing translations (#{forest.leaves.count || '∅'})" end + def missing_plural_keys_title(forest) + "Missing plural keys (#{forest.leaves.count || '∅'})" + end + def inconsistent_interpolations_title(forest) "Inconsistent interpolations (#{forest.leaves.count || '∅'})" end diff --git a/lib/i18n/tasks/reports/terminal.rb b/lib/i18n/tasks/reports/terminal.rb index d579422b..3704dd75 100644 --- a/lib/i18n/tasks/reports/terminal.rb +++ b/lib/i18n/tasks/reports/terminal.rb @@ -34,6 +34,24 @@ def inconsistent_interpolations(forest = task.inconsistent_interpolations) end end + def missing_plural_keys(forest = task.missing_plural_keys) + forest = collapse_missing_tree! forest + if forest.present? + print_title missing_plural_keys_title(forest) + print_table headings: [Rainbow(I18n.t('i18n_tasks.common.locale')).cyan.bright, + Rainbow(I18n.t('i18n_tasks.common.key')).cyan.bright, + I18n.t('i18n_tasks.missing_plural_keys.details_title')] do |t| + t.rows = sort_by_attr!(forest_to_attr(forest)).map do |a| + [{ value: Rainbow(format_locale(a[:locale])).cyan, alignment: :center }, + format_key(a[:key], a[:data]), + a[:data][:missing_keys].join(', ')] + end + end + else + print_success I18n.t('i18n_tasks.missing_plural_keys.none') + end + end + def icon(type) glyph = missing_type_info(type)[:glyph] { missing_used: Rainbow(glyph).red, missing_diff: Rainbow(glyph).yellow }[type] diff --git a/spec/plural_keys_spec.rb b/spec/plural_keys_spec.rb index a419a4c2..df9aba61 100644 --- a/spec/plural_keys_spec.rb +++ b/spec/plural_keys_spec.rb @@ -4,22 +4,39 @@ RSpec.describe 'Plural keys' do let(:task) { ::I18n::Tasks::BaseTask.new } - before do - TestCodebase.setup('config/locales/en.yml' => '') - TestCodebase.in_test_app_dir do - tree = ::I18n::Tasks::Data::Tree::Siblings.from_nested_hash('en' => { - 'regular_key' => 'a', - 'plural_key' => { - 'one' => 'one', 'other' => '%{count}' - }, - 'not_really_plural' => { - 'one' => 'a', - 'green' => 'b' - } - }) - task.data['en'] = tree - task.data['en'] - end + + let(:base_keys) do + { + regular_key: 'a', + + plural_key: { + one: 'one', + other: '%{count}' + }, + + not_really_plural: { + one: 'a', + green: 'b' + }, + + nested: { + plural_key: { + zero: 'none', + one: 'one', + other: '%{count}' + } + } + } + end + + around do |ex| + TestCodebase.setup( + 'config/i18n-tasks.yml' => { base_locale: 'en', locales: %w[en ar] }.to_yaml, + 'config/locales/en.yml' => { en: base_keys }.to_yaml, + 'config/locales/ar.yml' => { ar: base_keys }.to_yaml + ) + TestCodebase.in_test_app_dir { ex.call } + TestCodebase.teardown end describe '#depluralize_key' do @@ -40,7 +57,16 @@ def depluralize(key) end end - after do - TestCodebase.teardown + describe '#missing_plural_keys' do + it 'returns keys with missing pluralizations' do + wrong = task.missing_plural_keys + leaves = wrong.leaves.to_a + + expect(leaves.size).to eq 2 + expect(leaves[0].full_key).to eq 'ar.plural_key' + expect(leaves[0].data[:missing_keys]).to eq [:zero, :two, :few, :many] + expect(leaves[1].full_key).to eq 'ar.nested.plural_key' + expect(leaves[1].data[:missing_keys]).to eq [:two, :few, :many] + end end end