diff --git a/lib/locale_file_walker.rb b/lib/locale_file_walker.rb new file mode 100644 index 00000000000..0ac3146e84e --- /dev/null +++ b/lib/locale_file_walker.rb @@ -0,0 +1,48 @@ +require 'psych' + +class LocaleFileWalker + protected + + def handle_document(document) + # we want to ignore the language (first key), so let's start at -1 + handle_nodes(document.root.children, -1, []) + end + + def handle_nodes(nodes, depth, parents) + if nodes + consecutive_scalars = 0 + nodes.each do |node| + consecutive_scalars = handle_node(node, depth, parents, consecutive_scalars) + end + end + end + + def handle_node(node, depth, parents, consecutive_scalars) + node_is_scalar = node.is_a?(Psych::Nodes::Scalar) + + if node_is_scalar + handle_scalar(node, depth, parents) if valid_scalar?(depth, consecutive_scalars) + elsif node.is_a?(Psych::Nodes::Alias) + handle_alias(node, depth, parents) + elsif node.is_a?(Psych::Nodes::Mapping) + handle_mapping(node, depth, parents) + handle_nodes(node.children, depth + 1, parents.dup) + end + + node_is_scalar ? consecutive_scalars + 1 : 0 + end + + def valid_scalar?(depth, consecutive_scalars) + depth >= 0 && consecutive_scalars.even? + end + + def handle_scalar(node, depth, parents) + parents[depth] = node.value + end + + def handle_alias(node, depth, parents) + end + + def handle_mapping(node, depth, parents) + end +end diff --git a/spec/integrity/i18n_spec.rb b/spec/integrity/i18n_spec.rb index 2525668f542..e7b9c08fec9 100644 --- a/spec/integrity/i18n_spec.rb +++ b/spec/integrity/i18n_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'locale_file_walker' describe "i18n integrity checks" do @@ -57,4 +58,40 @@ describe "i18n integrity checks" do end end + describe 'keys in English locale files' do + locale_files = ['config/locales', 'plugins/**/locales'] + .product(['server.en.yml', 'client.en.yml']) + .collect { |dir, filename| Dir["#{Rails.root}/#{dir}/#{filename}"] } + .flatten + .map { |path| Pathname.new(path).relative_path_from(Rails.root) } + + class DuplicateKeyFinder < LocaleFileWalker + def find_duplicates(filename) + @keys_with_count = {} + + document = Psych.parse_file(filename) + handle_document(document) + + @keys_with_count.delete_if { |key, count| count <= 1 }.keys + end + + protected + + def handle_scalar(node, depth, parents) + super(node, depth, parents) + + key = parents.join('.') + @keys_with_count[key] = @keys_with_count.fetch(key, 0) + 1 + end + end + + locale_files.each do |path| + context path do + it 'has no duplicate keys' do + duplicates = DuplicateKeyFinder.new.find_duplicates("#{Rails.root}/#{path}") + expect(duplicates).to be_empty + end + end + end + end end