Skip to content

Commit

Permalink
Add check-consistent-interpolations
Browse files Browse the repository at this point in the history
Verify that non-base translations are using variable names defined
in the base translation.

Fix #303
  • Loading branch information
Gargron committed Oct 17, 2018
1 parent 4fbf49c commit 6211e82
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 1 deletion.
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ en:
%{value_or_default_or_human_key}
desc:
add_missing: add missing keys to locale data
check_consistent_interpolations: verify that all translations use correct interpolation variables
check_normalized: verify that all translation data is normalized
config: display i18n-tasks configuration
data: show locale data
Expand Down Expand Up @@ -102,6 +103,8 @@ en:
Google Translate returned no results. Make sure billing information is set at https://code.google.com/apis/console.
health:
no_keys_detected: No keys detected. Check data.read in config/i18n-tasks.yml.
inconsistent_interpolation:
none: No inconsistent interpolations found.
missing:
details_title: Value in other locales or source
none: No translations are missing.
Expand Down
4 changes: 4 additions & 0 deletions config/locales/ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ ru:
%{value_or_default_or_human_key}
desc:
add_missing: добавить недостающие ключи к переводам
check_consistent_interpolations: убедитесь, что во всех переводах используются правильные
интерполяционные переменные
check_normalized: проверить, что все файлы переводов нормализованы
config: показать конфигурацию
data: показать данные переводов
Expand Down Expand Up @@ -99,6 +101,8 @@ ru:
в https://code.google.com/apis/console.
health:
no_keys_detected: Ключи не обнаружены. Проверьте data.read в config/i18n-tasks.yml.
inconsistent_interpolation:
none: Не найдено несогласованных интерполяций.
missing:
details_title: На других языках или в коде
none: Всё переведено.
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/tasks/base_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require 'i18n/tasks/used_keys'
require 'i18n/tasks/ignore_keys'
require 'i18n/tasks/missing_keys'
require 'i18n/tasks/inconsistent_interpolation'
require 'i18n/tasks/unused_keys'
require 'i18n/tasks/translation'
require 'i18n/tasks/locale_pathname'
Expand All @@ -30,6 +31,7 @@ class BaseTask
include UsedKeys
include IgnoreKeys
include MissingKeys
include InconsistentInterpolation
include UnusedKeys
include Translation
include Logging
Expand Down
7 changes: 6 additions & 1 deletion lib/i18n/tasks/command/commands/health.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ def health(opt = {})
stats = i18n.forest_stats(forest)
fail CommandError, t('i18n_tasks.health.no_keys_detected') if stats[:key_count].zero?
terminal_report.forest_stats forest, stats
[missing(opt), unused(opt), check_normalized(opt)].detect { |result| result == :exit_1 }
[
missing(opt),
unused(opt),
check_normalized(opt),
check_consistent_interpolations(opt)
].detect { |result| result == :exit_1 }
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions lib/i18n/tasks/command/commands/inconsistent.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module I18n::Tasks
module Command
module Commands
module Inconsistent
include Command::Collection

cmd :check_consistent_interpolations,
pos: '[locale ...]',
desc: t('i18n_tasks.cmd.desc.check_consistent_interpolations'),
args: %i[locales out_format]

def check_consistent_interpolations(opt = {})
forest = i18n.inconsistent_interpolation(opt.slice(:locales, :base_locale))
print_forest forest, opt, :inconsistent_interpolation
:exit_1 unless forest.empty?
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/i18n/tasks/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require 'i18n/tasks/command/commands/health'
require 'i18n/tasks/command/commands/missing'
require 'i18n/tasks/command/commands/usages'
require 'i18n/tasks/command/commands/inconsistent'
require 'i18n/tasks/command/commands/eq_base'
require 'i18n/tasks/command/commands/data'
require 'i18n/tasks/command/commands/tree'
Expand All @@ -17,6 +18,7 @@ class Commands < Command::Commander
include Command::Commands::Health
include Command::Commands::Missing
include Command::Commands::Usages
include Command::Commands::Inconsistent
include Command::Commands::EqBase
include Command::Commands::Data
include Command::Commands::Tree
Expand Down
34 changes: 34 additions & 0 deletions lib/i18n/tasks/inconsistent_interpolation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module I18n::Tasks
module InconsistentInterpolation
VARIABLE_REGEX = /%{[^}]+}/

def inconsistent_interpolation(locales: nil, base_locale: nil)
locales ||= self.locales
base = base_locale || self.base_locale
tree = empty_forest

data[base].key_values.each do |key, value|
next if ignore_key?(key, :inconsistent) || !value.is_a?(String)

base_variables = Set.new(value.scan(VARIABLE_REGEX))

(locales - [base]).each do |current_locale|
node = data[current_locale].first.children[key]

next if !node&.value&.is_a?(String) || base_variables == Set.new(node.value.scan(VARIABLE_REGEX))

tree.merge! inconsistent_interpolation_tree(current_locale, key)
end
end

tree
end

def inconsistent_interpolation_tree(locale, key)
data[locale].select_keys(root: false) { |x| x == key }
.set_root_key!(locale, type: :inconsistent_interpolation)
end
end
end
4 changes: 4 additions & 0 deletions lib/i18n/tasks/reports/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def missing_title(forest)
"Missing translations (#{forest.leaves.count || '∅'})"
end

def inconsistent_interpolation_title(forest)
"Inconsistent interpolations (#{forest.leaves.count || '∅'})"
end

def unused_title(key_values)
"Unused keys (#{key_values.count || '∅'})"
end
Expand Down
9 changes: 9 additions & 0 deletions lib/i18n/tasks/reports/terminal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ def missing_keys(forest = task.missing_keys)
end
end

def inconsistent_interpolation(forest = task.inconsistent_interpolation)
if forest.present?
print_title inconsistent_interpolation_title(forest)
show_tree(forest)
else
print_success I18n.t('i18n_tasks.inconsistent_interpolation.none')
end
end

def icon(type)
glyph = missing_type_info(type)[:glyph]
{ missing_used: Rainbow(glyph).red, missing_diff: Rainbow(glyph).yellow }[type]
Expand Down
29 changes: 29 additions & 0 deletions spec/commands/inconsistent_commands_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Inconsistent commands' do
delegate :run_cmd, :in_test_app_dir, to: :TestCodebase

let(:base_keys) { { 'a' => 'hello %{world}', 'b' => 'foo', 'c' => { 'd' => 'hello %{name}' }, 'e' => 'ok' } }
let(:test_keys) { { 'a' => 'hello', 'b' => 'foo %{bar}', 'c' => { 'd' => 'hola %{amigo}' }, 'e' => 'ok' } }

let(:wrong_subtree) { { 'es' => test_keys.slice('a', 'b', 'c') } }

around do |ex|
TestCodebase.setup(
'config/i18n-tasks.yml' => { base_locale: 'en', locales: %w[es] }.to_yaml,
'config/locales/en.yml' => { 'en' => base_keys }.to_yaml,
'config/locales/es.yml' => { 'es' => test_keys }.to_yaml
)

TestCodebase.in_test_app_dir { ex.call }
TestCodebase.teardown
end

describe '#check_consistent_interpolations' do
it 'returns inconsistent keys' do
expect(YAML.load(run_cmd('check-consistent-interpolations', '-fyaml'))).to eq(wrong_subtree)
end
end
end
31 changes: 31 additions & 0 deletions spec/inconsistent_interpolation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'InconsistentInterpolation' do
let!(:task) { I18n::Tasks::BaseTask.new }

let(:base_keys) { { 'a' => 'hello %{world}', 'b' => 'foo', 'c' => { 'd' => 'hello %{name}' }, 'e' => 'ok' } }
let(:test_keys) { { 'a' => 'hello', 'b' => 'foo %{bar}', 'c' => { 'd' => 'hola %{amigo}' }, 'e' => 'ok' } }

around do |ex|
TestCodebase.setup(
'config/i18n-tasks.yml' => { base_locale: 'en', locales: %w[es] }.to_yaml,
'config/locales/en.yml' => { 'en' => base_keys }.to_yaml,
'config/locales/es.yml' => { 'es' => test_keys }.to_yaml
)

TestCodebase.in_test_app_dir { ex.call }
TestCodebase.teardown
end

it '#inconsistent_interpolation' do
wrong = task.inconsistent_interpolation
leaves = wrong.leaves.to_a

expect(leaves.size).to eq 3
expect(leaves[0].full_key).to eq 'es.a'
expect(leaves[1].full_key).to eq 'es.b'
expect(leaves[2].full_key).to eq 'es.c.d'
end
end
4 changes: 4 additions & 0 deletions templates/config/i18n-tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ search:
# fr,es:
# - common.brand

## Exclude these keys from the `i18n-tasks check-consistent-interpolations` report:
# ignore_inconsistent:
# - 'activerecord.attributes.*'

## Ignore these keys completely:
# ignore:
# - kaminari.*
Expand Down

0 comments on commit 6211e82

Please sign in to comment.