Skip to content

Commit

Permalink
20321 Fix liquid tag consistency not being validated in interpolation…
Browse files Browse the repository at this point in the history
…s check

Note that interpolation checks can be skipped on specific keys using
the 'ignore_inconsistent_interpolations' property in the yaml config
file

Ref bookingexperts/support#20321
  • Loading branch information
tom-brouwer-bex committed Feb 6, 2024
1 parent 6b7fb51 commit 0b427c7
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 14 deletions.
34 changes: 30 additions & 4 deletions lib/i18n/tasks/interpolations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
module I18n::Tasks
module Interpolations
class << self
attr_accessor :variable_regex
attr_accessor :variable_regex, :tag_pairs, :tag_with_localized_value_regex
end
@variable_regex = /%{[^}]+}/.freeze
@variable_regex = /%\{[^}]+\}|\{\{.*?\}\}|\{%.*?%\}/.freeze
@tag_pairs = [
['{{', '}}'],
['%{', '}'],
['{%', '%}']
].freeze
@tag_with_localized_value_regex = /\{\{\s?(("[^"]+")|('[^']+'))\s?\|.*?\}\}/

def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disable Metrics/AbcSize

Check failure on line 16 in lib/i18n/tasks/interpolations.rb

View workflow job for this annotation

GitHub Actions / lint

Metrics/CyclomaticComplexity: Cyclomatic complexity for inconsistent_interpolations is too high. [14/12]

Check failure on line 16 in lib/i18n/tasks/interpolations.rb

View workflow job for this annotation

GitHub Actions / lint

Metrics/PerceivedComplexity: Perceived complexity for inconsistent_interpolations is too high. [14/12]
locales ||= self.locales
Expand All @@ -16,12 +22,14 @@ def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disabl
data[base_locale].key_values.each do |key, value|
next if !value.is_a?(String) || ignore_key?(key, :inconsistent_interpolations)

base_vars = Set.new(value.scan(variable_regex))
base_vars = Set.new(
value.scan(variable_regex).map { |interpolation| normalize(interpolation) }
)
(locales - [base_locale]).each do |current_locale|
node = data[current_locale].first.children[key]
next unless node&.value.is_a?(String)

if base_vars != Set.new(node.value.scan(variable_regex))
if base_vars != Set.new(node.value.scan(variable_regex).map { |interpolation| normalize(interpolation) })
result.merge!(node.walk_to_root.reduce(nil) { |c, p| [p.derive(children: c)] })
end
end
Expand All @@ -30,5 +38,23 @@ def inconsistent_interpolations(locales: nil, base_locale: nil) # rubocop:disabl
result.each { |root| root.data[:type] = :inconsistent_interpolations }
result
end

def normalize(interpolation)
normalized = nil
if (match = interpolation.match(I18n::Tasks::Interpolations.tag_with_localized_value_regex))
interpolation = interpolation.sub(match[1], 'localized input')
end
I18n::Tasks::Interpolations.tag_pairs.each do |start, end_|
next unless interpolation.start_with?(start)

normalized = interpolation.delete_prefix(start).delete_suffix(end_).strip
normalized = start + normalized + end_
break
end

fail 'No start/end tag pair detected' if normalized.nil?

normalized
end
end
end
67 changes: 57 additions & 10 deletions spec/interpolations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
RSpec.describe 'Interpolations' 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,
Expand All @@ -19,13 +16,63 @@
TestCodebase.teardown
end

it '#inconsistent_interpolation' do
wrong = task.inconsistent_interpolations
leaves = wrong.leaves.to_a
context 'ruby string interpolations' do
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' } }

it 'detects inconsistent interpolations' do
wrong = task.inconsistent_interpolations
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

context 'liquid tags' do
let(:base_keys) do
{
a: 'hello {{ world }}',
b: 'foo',
c: {
d: 'hello {{ name }}'
},
e: 'ok',
f: 'inconsistent {{ whitespace}}',
g: 'includes a {% comment %}',
h: 'wrong {% comment %}',
i: 'with localized value: {{ "thanks" | owner_invoice }}',
j: 'with wrong function: {{ "thanks" | owner_invoices }}'
}
end
let(:test_keys) do
{
a: 'hello',
b: 'foo {{ bar }}',
c: {
d: 'hola {{ amigo }}'
},
e: 'ok',
f: '{{whitespace }} inconsistentes',
g: 'incluye un {% comment %}',
h: '{% commentario %} equivocado',
i: 'con valor localizado: {{ "gracias" | owner_invoice }}',
j: 'con función incorrecta: {{ "thanks" | owner_invoice }}'
}
end

it 'detects inconsistent interpolations' do
wrong = task.inconsistent_interpolations
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'
expect(leaves.size).to eq 5
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'
expect(leaves[3].full_key).to eq 'es.h'
expect(leaves[4].full_key).to eq 'es.j'
end
end
end

0 comments on commit 0b427c7

Please sign in to comment.