i18n_flow 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4537799b78a2246ecd36a0cf9be738dc928e2422
4
- data.tar.gz: 1fc6adc2ef94f46a13714829dfbc677cb42778f9
3
+ metadata.gz: '08d2cf8d25372c57651808089a31dedc34a52f81'
4
+ data.tar.gz: 2a96774f923502e0dd534c8b632acf4f7e5d232f
5
5
  SHA512:
6
- metadata.gz: 923ce8ee56eb52fbe275a1c07657c163b8a319c7dc67aec80e9b0f45f6991e44ff43b4c4c04bcce70909c26eac6959fb5f22555a347feb3aaca09223f4eae92a
7
- data.tar.gz: 9d84ac662df036e5c2bbd56e3f4d9dda06ceb8e57e60b1b8670f8f01b2c37e6cfb0ce51001970a274722a3507fc4fd6a7ec243a06dcb4401e191d9ce7abf1f12
6
+ metadata.gz: '03463815dc69ad05dfb2e4b048201441e212a165947a546f4b8e1828b72f32cda5e1f16ab0d335fe9881bc949cfad7a5fa0eb6acf6ebc50a312cfa5d2080b17b'
7
+ data.tar.gz: 4bd655b11d659a008101bd28f113c65d8d03ea59ab7fe969c1c702e2dabacca9b52c20da33f4e2b4c3981f9b74af73cc87dbc11ae96fa269f2bcfd00fdc7f591
data/.gitignore CHANGED
@@ -5,3 +5,5 @@
5
5
  /pkg/
6
6
  /spec/reports/
7
7
  /tmp/
8
+
9
+ tags
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- i18n_flow (0.1.0)
4
+ i18n_flow (0.2.0)
5
5
  psych (>= 3.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -6,7 +6,7 @@ i18n_flow (beta)
6
6
  [![Build Status](https://travis-ci.org/creasty/i18n_flow.svg?branch=master)](https://travis-ci.org/creasty/i18n_flow)
7
7
  [![License](https://img.shields.io/github/license/creasty/i18n_flow.svg)](./LICENSE)
8
8
 
9
- **Manage translation status in YAML file.**
9
+ **Manage translation status in YAML file.**<br>
10
10
  With an official [tag](http://www.yaml.org/spec/1.2/spec.html#id2784064) feature, `i18n_flow` enables you to annotate status information directly in YAML file.
11
11
 
12
12
  ![](https://user-images.githubusercontent.com/1695538/36359417-6a976054-155e-11e8-914b-d6a10a8287fc.png)
@@ -23,6 +23,9 @@ Setup
23
23
  Add this line to your Gemfile:
24
24
 
25
25
  ```ruby
26
+ gem 'i18n_flow'
27
+
28
+ # To use the latest version:
26
29
  gem 'i18n_flow', github: 'creasty/i18n_flow'
27
30
  ```
28
31
 
@@ -61,6 +64,7 @@ Options:
61
64
 
62
65
  Commands:
63
66
  lint Validate files
67
+ format Format and correct errors
64
68
  search Search contents and keys
65
69
  copy Copy translations and mark as todo
66
70
  split Split a file into proper-sized files
data/lib/i18n_flow/cli.rb CHANGED
@@ -1,22 +1,24 @@
1
1
  require_relative 'util'
2
2
 
3
3
  class I18nFlow::CLI
4
- require_relative 'cli/lint_command'
5
- require_relative 'cli/split_command'
6
4
  require_relative 'cli/copy_command'
7
- require_relative 'cli/search_command'
8
- require_relative 'cli/version_command'
5
+ require_relative 'cli/format_command'
9
6
  require_relative 'cli/help_command'
7
+ require_relative 'cli/lint_command'
10
8
  require_relative 'cli/read_config_command'
9
+ require_relative 'cli/search_command'
10
+ require_relative 'cli/split_command'
11
+ require_relative 'cli/version_command'
11
12
 
12
13
  COMMANDS = {
14
+ 'copy' => CopyCommand,
15
+ 'format' => FormatCommand,
16
+ 'help' => HelpCommand,
13
17
  'lint' => LintCommand,
18
+ 'read_config' => ReadConfigCommand,
14
19
  'search' => SearchCommand,
15
20
  'split' => SplitCommand,
16
- 'copy' => CopyCommand,
17
21
  'version' => VersionCommand,
18
- 'help' => HelpCommand,
19
- 'read_config' => ReadConfigCommand,
20
22
  }
21
23
 
22
24
  attr_reader :args
@@ -2,6 +2,7 @@ require 'psych'
2
2
  require_relative 'command_base'
3
3
  require_relative '../util'
4
4
  require_relative '../parser'
5
+ require_relative '../yaml_ast_proxy'
5
6
 
6
7
  class I18nFlow::CLI
7
8
  class CopyCommand < CommandBase
@@ -12,7 +13,7 @@ class I18nFlow::CLI
12
13
 
13
14
  parser.parse!
14
15
 
15
- mark_as_todo(parser.root_proxy)
16
+ I18nFlow::YamlAstProxy.mark_as_todo(parser.root_proxy)
16
17
 
17
18
  if locale && first_key_node
18
19
  first_key_node.value = locale
@@ -35,11 +36,7 @@ class I18nFlow::CLI
35
36
 
36
37
  def first_key_node
37
38
  return @first_key_node if defined?(@first_key_node)
38
- @first_key_node = parser.root_proxy
39
- .send(:indexed_object)
40
- .node
41
- .tap { |n| break unless n.is_a?(Psych::Nodes::Mapping) }
42
- &.tap { |n| break n.children.first }
39
+ @first_key_node = I18nFlow::YamlAstProxy.first_key_node_of(parser.root_proxy)
43
40
  end
44
41
 
45
42
  private
@@ -47,23 +44,5 @@ class I18nFlow::CLI
47
44
  def parser
48
45
  @parser ||= I18nFlow::Parser.new(File.read(src_file), file_path: src_file)
49
46
  end
50
-
51
- def mark_as_todo(ast)
52
- if ast.alias?
53
- return
54
- end
55
- if ast.scalar?
56
- ast.node.tag = '!todo'
57
-
58
- # https://github.com/ruby/psych/blob/f30b65befa4f0a5a8548d482424a84a2383b0284/ext/psych/yaml/emitter.c#L1187
59
- ast.node.plain = ast.node.quoted = false
60
-
61
- return
62
- end
63
-
64
- ast.each do |k, v|
65
- mark_as_todo(v)
66
- end
67
- end
68
47
  end
69
48
  end
@@ -0,0 +1,57 @@
1
+ require_relative 'command_base'
2
+ require_relative 'color'
3
+ require_relative '../repository'
4
+ require_relative '../formatter'
5
+ require_relative '../corrector'
6
+
7
+ class I18nFlow::CLI
8
+ class FormatCommand < CommandBase
9
+ include I18nFlow::CLI::Color
10
+
11
+ def invoke!
12
+ puts color('==> Correcting', :yellow)
13
+ repository.asts_by_scope.each do |scope, locale_trees|
14
+ locale_pairs.each do |(master, slave)|
15
+ master_tree = locale_trees[master]
16
+ slave_tree = locale_trees[slave]
17
+ next unless master_tree && slave_tree
18
+
19
+ puts slave_tree.file_path
20
+ correct(slave_tree, master_tree)
21
+ end
22
+ end
23
+
24
+ puts
25
+ puts color('==> Formatting', :yellow)
26
+ repository.asts_by_path.each do |path, tree|
27
+ puts path
28
+
29
+ format(tree)
30
+
31
+ output_path = I18nFlow.config.base_path.join(tree.file_path)
32
+ File.write(output_path, tree.to_yaml)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def locale_pairs
39
+ I18nFlow.config.locale_pairs
40
+ end
41
+
42
+ def repository
43
+ @repository ||= I18nFlow::Repository.new(
44
+ base_path: I18nFlow.config.base_path,
45
+ glob_patterns: I18nFlow.config.glob_patterns,
46
+ )
47
+ end
48
+
49
+ def correct(slave_tree, master_tree)
50
+ I18nFlow::Corrector.new(slave_tree, master_tree).correct!
51
+ end
52
+
53
+ def format(tree)
54
+ I18nFlow::Formatter.new(tree).format!
55
+ end
56
+ end
57
+ end
@@ -15,6 +15,7 @@ Options:
15
15
 
16
16
  Commands:
17
17
  lint Validate files
18
+ format Format and correct errors
18
19
  search Search contents and keys
19
20
  copy Copy translations and mark as todo
20
21
  split Split a file into proper-sized files
@@ -27,9 +27,15 @@
27
27
  <%- when I18nFlow::Validator::InvalidTodoError -%>
28
28
  Todo cannot be annotated on a mapping/sequence
29
29
  <%- when I18nFlow::Validator::TodoContentError -%>
30
+ <%- if err.inverse -%>
31
+ It has "!todo" but the content diverges from the foreign file
32
+ foreign: <%= err.expect %>
33
+ master: <%= err.actual %>
34
+ <%- else -%>
30
35
  It has "!todo" but the content diverges from the master file
31
36
  master: <%= err.expect %>
32
37
  foreign: <%= err.actual %>
38
+ <%- end -%>
33
39
  <%- when I18nFlow::Validator::InvalidLocaleError -%>
34
40
  It has "!only" but the locale is invalid
35
41
  valid: [<%= err.expect.join(', ') %>]
@@ -32,9 +32,15 @@ An extra key found
32
32
  <%- when I18nFlow::Validator::InvalidTodoError -%>
33
33
  Todo cannot be annotated on a mapping/sequence
34
34
  <%- when I18nFlow::Validator::TodoContentError -%>
35
+ <%- if err.inverse -%>
36
+ It has "!todo" but the content diverges from the foreign file
37
+ foreign: <%= err.expect %>
38
+ master: <%= err.actual %>
39
+ <%- else -%>
35
40
  It has "!todo" but the content diverges from the master file
36
- master: `<%= err.expect %>`
37
- foreign: `<%= err.actual %>`
41
+ master: <%= err.expect %>
42
+ foreign: <%= err.actual %>
43
+ <%- end -%>
38
44
  <%- when I18nFlow::Validator::InvalidLocaleError -%>
39
45
  It has "!only" but the locale is invalid
40
46
  valid: `[<%= err.expect.join(', ') %>]`
@@ -0,0 +1,40 @@
1
+ require_relative 'validator/symmetry'
2
+ require_relative 'validator/errors'
3
+ require_relative 'yaml_ast_proxy'
4
+
5
+ class I18nFlow::Corrector
6
+ attr_reader :ast_1
7
+ attr_reader :ast_2
8
+
9
+ def initialize(ast_1, ast_2)
10
+ @ast_1 = ast_1
11
+ @ast_2 = ast_2
12
+ end
13
+
14
+ def correct!
15
+ n1, n2 = [ast_1, ast_2]
16
+ .map { |n| I18nFlow::YamlAstProxy.first_value_node_of(n) }
17
+ .map { |n| I18nFlow::YamlAstProxy.create(n) }
18
+
19
+ errors = I18nFlow::Validator::Symmetry.new(n2, n1)
20
+ .tap(&:validate!)
21
+ .errors
22
+
23
+ errors.each do |error|
24
+ case error
25
+ when I18nFlow::Validator::MissingKeyError
26
+ src_node = error.src_node.clone
27
+ I18nFlow::YamlAstProxy.mark_as_todo(src_node)
28
+ error.dest_node[error.dest_key] = src_node.node
29
+ when I18nFlow::Validator::ExtraKeyError
30
+ if error.dest_node.mapping?
31
+ error.dest_node.delete(error.dest_key)
32
+ elsif error.dest_node.sequence?
33
+ error.dest_node.delete_at(error.dest_key)
34
+ end
35
+ when I18nFlow::Validator::TodoContentError
36
+ error.dest_node.node.value = error.src_node.node.value
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ class I18nFlow::Formatter
2
+ attr_reader :ast
3
+
4
+ def initialize(ast)
5
+ @ast = ast
6
+ end
7
+
8
+ def format!
9
+ sort_keys(ast)
10
+ end
11
+
12
+ private
13
+
14
+ def sort_keys(node)
15
+ node.sort_keys! if node.mapping?
16
+ node.each do |_, val|
17
+ sort_keys(val) if val.mapping?
18
+ end
19
+ end
20
+ end
@@ -36,17 +36,34 @@ module I18nFlow::Validator
36
36
  end
37
37
 
38
38
  class MissingKeyError < Error
39
+ attr_reader :dest_node
40
+ attr_reader :dest_key
41
+ attr_reader :src_node
42
+
39
43
  def initialize(key, single: false)
40
44
  super(key)
41
45
  @single = single
42
46
  end
47
+
48
+ def set_correction_context(dest_node:, dest_key:, src_node:)
49
+ @dest_node, @dest_key, @src_node = dest_node, dest_key, src_node
50
+ self
51
+ end
43
52
  end
44
53
 
45
54
  class ExtraKeyError < Error
55
+ attr_reader :dest_node
56
+ attr_reader :dest_key
57
+
46
58
  def initialize(key, single: false)
47
59
  super(key)
48
60
  @single = single
49
61
  end
62
+
63
+ def set_correction_context(dest_node:, dest_key:)
64
+ @dest_node, @dest_key = dest_node, dest_key
65
+ self
66
+ end
50
67
  end
51
68
 
52
69
  class InvalidTodoError < Error
@@ -55,16 +72,25 @@ module I18nFlow::Validator
55
72
  class TodoContentError < Error
56
73
  attr_reader :expect
57
74
  attr_reader :actual
75
+ attr_reader :dest_node
76
+ attr_reader :src_node
77
+ attr_reader :inverse
58
78
 
59
- def initialize(key, expect:, actual:)
79
+ def initialize(key, expect:, actual:, inverse:)
60
80
  super(key)
61
81
  @expect = expect
62
82
  @actual = actual
83
+ @inverse = inverse
63
84
  end
64
85
 
65
86
  def data
66
87
  super + [expect, actual]
67
88
  end
89
+
90
+ def set_correction_context(dest_node:, src_node:)
91
+ @dest_node, @src_node = dest_node, src_node
92
+ self
93
+ end
68
94
  end
69
95
 
70
96
  class InvalidLocaleError < Error
@@ -39,7 +39,7 @@ module I18nFlow::Validator
39
39
  return
40
40
  end
41
41
 
42
- check_asymmetric_key(n1, n2, t2)&.tap do |err|
42
+ check_asymmetric_key(n1, n2, t2, key)&.tap do |err|
43
43
  errors << err if err
44
44
  return
45
45
  end
@@ -112,28 +112,35 @@ module I18nFlow::Validator
112
112
  InvalidTypeError.new(n2.full_key).set_location(n2)
113
113
  end
114
114
 
115
- def check_asymmetric_key(n1, n2, t2)
115
+ def check_asymmetric_key(n1, n2, t2, key)
116
116
  return false if n1&.ignored_violation == :key || n2&.ignored_violation == :key
117
117
  return if n1 && n2
118
118
 
119
119
  if n1
120
120
  full_key = [t2.locale, *n1.scopes.drop(1)].join('.')
121
- MissingKeyError.new(full_key).set_location(t2)
121
+ MissingKeyError.new(full_key)
122
+ .set_location(t2)
123
+ .set_correction_context(dest_node: t2, dest_key: key, src_node: n1)
122
124
  else
123
- ExtraKeyError.new(n2.full_key).set_location(n2)
125
+ ExtraKeyError.new(n2.full_key)
126
+ .set_location(n2)
127
+ .set_correction_context(dest_node: t2, dest_key: key)
124
128
  end
125
129
  end
126
130
 
127
131
  def check_todo_tag(n1, n2)
128
- return unless n2.marked_as_todo?
129
-
130
- if !n2.scalar?
131
- InvalidTodoError.new(n2.full_key).set_location(n2)
132
- elsif n2.value != n1.value
133
- TodoContentError.new(n2.full_key,
134
- expect: n1.value,
135
- actual: n2.value,
136
- ).set_location(n2)
132
+ if n1.scalar? && n1.marked_as_todo? && !n2.marked_as_todo? && n2.value != n1.value
133
+ TodoContentError.new(n1.full_key, expect: n2.value, actual: n1.value, inverse: true)
134
+ .set_location(n1)
135
+ .set_correction_context(dest_node: n1, src_node: n2)
136
+ elsif n2.marked_as_todo?
137
+ if !n2.scalar?
138
+ InvalidTodoError.new(n2.full_key).set_location(n2)
139
+ elsif n2.value != n1.value
140
+ TodoContentError.new(n2.full_key, expect: n1.value, actual: n2.value, inverse: false)
141
+ .set_location(n2)
142
+ .set_correction_context(dest_node: n2, src_node: n1)
143
+ end
137
144
  end
138
145
  end
139
146
 
@@ -1,6 +1,6 @@
1
1
  module I18nFlow
2
2
  MAJOR = 0
3
- MINOR = 1
3
+ MINOR = 2
4
4
  REVISION = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, REVISION].join('.')
@@ -54,4 +54,40 @@ module I18nFlow::YamlAstProxy
54
54
  stream.children << doc
55
55
  create(stream)
56
56
  end
57
+
58
+ def self.mark_as_todo(ast)
59
+ if ast.alias?
60
+ return
61
+ end
62
+ if ast.scalar?
63
+ ast.node.tag = '!todo'
64
+
65
+ # https://github.com/ruby/psych/blob/f30b65befa4f0a5a8548d482424a84a2383b0284/ext/psych/yaml/emitter.c#L1187
66
+ ast.node.plain = ast.node.quoted = false
67
+
68
+ return
69
+ end
70
+
71
+ ast.each do |k, v|
72
+ mark_as_todo(v)
73
+ end
74
+ end
75
+
76
+ def self.first_key_node_of(node)
77
+ first_node_of(node, 0)
78
+ end
79
+
80
+ def self.first_value_node_of(node)
81
+ first_node_of(node, 1)
82
+ end
83
+
84
+ private
85
+
86
+ def self.first_node_of(node, layout_offset)
87
+ node
88
+ .send(:indexed_object)
89
+ .node
90
+ .tap { |n| break unless n.is_a?(Psych::Nodes::Mapping) }
91
+ &.tap { |n| break n.children[layout_offset] }
92
+ end
57
93
  end
@@ -25,6 +25,12 @@ module I18nFlow::YamlAstProxy
25
25
  end
26
26
  alias []= set
27
27
 
28
+ def delete(key)
29
+ indexed_object.delete(key)
30
+ cache.delete(key)
31
+ synchronize!
32
+ end
33
+
28
34
  def batch
29
35
  @locked = true
30
36
  yield
@@ -49,6 +55,14 @@ module I18nFlow::YamlAstProxy
49
55
  end
50
56
  end
51
57
 
58
+ def sort_keys!
59
+ @indexed_object = indexed_object
60
+ .sort_by { |k, v| [v.is_a?(Psych::Nodes::Mapping) ? 1 : 0, k] }
61
+ .to_h
62
+ @cache = nil
63
+ synchronize!
64
+ end
65
+
52
66
  private
53
67
 
54
68
  def cache
@@ -10,7 +10,8 @@ module I18nFlow::YamlAstProxy
10
10
  TAG_TODO = /^!todo(?::([,a-zA-Z_-]+))?$/
11
11
  TAG_ONLY = /^!only(?::([,a-zA-Z_-]+))?$/
12
12
 
13
- attr_reader :node
13
+ attr_accessor :node
14
+ protected :node=
14
15
  attr_reader :parent
15
16
  attr_reader :scopes
16
17
  attr_reader :file_path
@@ -87,6 +88,12 @@ module I18nFlow::YamlAstProxy
87
88
  indexed_object.keys
88
89
  end
89
90
 
91
+ def clone
92
+ super.tap do |n|
93
+ n.node = node.clone
94
+ end
95
+ end
96
+
90
97
  private
91
98
 
92
99
  def identity_data
@@ -2,7 +2,7 @@ require_relative 'node'
2
2
 
3
3
  module I18nFlow::YamlAstProxy
4
4
  class Sequence < Node
5
- def_delegators :indexed_object, :==, :<<, :size
5
+ def_delegators :indexed_object, :==, :<<, :size, :delete_at
6
6
 
7
7
  def each
8
8
  indexed_object.each.with_index do |o, i|
@@ -0,0 +1,197 @@
1
+ require 'i18n_flow/corrector'
2
+
3
+ describe I18nFlow::Corrector do
4
+ def correct_ast(ast_1, ast_2 = nil)
5
+ I18nFlow::Corrector.new(ast_1, ast_2)
6
+ .tap(&:correct!)
7
+ end
8
+
9
+ describe '#correct!' do
10
+ it 'should complement missing keys and mark as !todo' do
11
+ ast_1 = parse_yaml(<<-YAML)
12
+ es:
13
+ alfa: 'a'
14
+ delta: 'd'
15
+ echo: 'e'
16
+ golf: 'g'
17
+ hotel: 'h'
18
+ YAML
19
+ ast_2 = parse_yaml(<<-YAML)
20
+ en:
21
+ alfa: 'A'
22
+ bravo: 'B'
23
+ charlie: 'C'
24
+ delta: 'D'
25
+ echo: 'E'
26
+ foxtrot: !only 'F'
27
+ golf: 'G'
28
+ hotel: 'H'
29
+ YAML
30
+ result = parse_yaml(<<-YAML)
31
+ es:
32
+ alfa: 'a'
33
+ delta: 'd'
34
+ echo: 'e'
35
+ golf: 'g'
36
+ hotel: 'h'
37
+ bravo: !todo 'B'
38
+ charlie: !todo 'C'
39
+ YAML
40
+
41
+ corrected = correct_ast(ast_1, ast_2)
42
+ expect(corrected.ast_1.to_yaml).to eq(result.to_yaml)
43
+ end
44
+
45
+ it 'should complement missing elements in a sequence' do
46
+ ast_1 = parse_yaml(<<-YAML)
47
+ es:
48
+ foo:
49
+ - 'one'
50
+ - 'two'
51
+ YAML
52
+ ast_2 = parse_yaml(<<-YAML)
53
+ en:
54
+ foo:
55
+ - 'ONE'
56
+ - 'TWO'
57
+ - 'THREE'
58
+ - !only 'FOUR'
59
+ YAML
60
+ result = parse_yaml(<<-YAML)
61
+ es:
62
+ foo:
63
+ - 'one'
64
+ - 'two'
65
+ - !todo 'THREE'
66
+ YAML
67
+
68
+ corrected = correct_ast(ast_1, ast_2)
69
+ expect(corrected.ast_1.to_yaml).to eq(result.to_yaml)
70
+ end
71
+
72
+ it 'should delete extra keys which are not marked as !only' do
73
+ ast_1 = parse_yaml(<<-YAML)
74
+ es:
75
+ alfa: 'a'
76
+ bravo: 'b'
77
+ charlie: 'c'
78
+ delta: 'd'
79
+ echo: 'e'
80
+ foxtrot: !only 'f'
81
+ golf: 'g'
82
+ hotel: 'h'
83
+ YAML
84
+ ast_2 = parse_yaml(<<-YAML)
85
+ en:
86
+ alfa: 'A'
87
+ bravo: 'B'
88
+ charlie: 'C'
89
+ golf: 'G'
90
+ hotel: 'H'
91
+ YAML
92
+ result = parse_yaml(<<-YAML)
93
+ es:
94
+ alfa: 'a'
95
+ bravo: 'b'
96
+ charlie: 'c'
97
+ foxtrot: !only 'f'
98
+ golf: 'g'
99
+ hotel: 'h'
100
+ YAML
101
+
102
+ corrected = correct_ast(ast_1, ast_2)
103
+ expect(corrected.ast_1.to_yaml).to eq(result.to_yaml)
104
+ end
105
+
106
+ it 'should delete extra elements in a sequence' do
107
+ ast_1 = parse_yaml(<<-YAML)
108
+ es:
109
+ foo:
110
+ - 'one'
111
+ - 'two'
112
+ - 'three'
113
+ - !only 'four'
114
+ YAML
115
+ ast_2 = parse_yaml(<<-YAML)
116
+ en:
117
+ foo:
118
+ - 'ONE'
119
+ - 'TWO'
120
+ YAML
121
+ result = parse_yaml(<<-YAML)
122
+ es:
123
+ foo:
124
+ - 'one'
125
+ - 'two'
126
+ - !only 'four'
127
+ YAML
128
+
129
+ corrected = correct_ast(ast_1, ast_2)
130
+ expect(corrected.ast_1.to_yaml).to eq(result.to_yaml)
131
+ end
132
+
133
+ it 'should update translations with !todo according to the source' do
134
+ ast_1 = parse_yaml(<<-YAML)
135
+ es:
136
+ foo: !todo 'outdated'
137
+ YAML
138
+ ast_2 = parse_yaml(<<-YAML)
139
+ en:
140
+ foo: 'latest'
141
+ YAML
142
+ result = parse_yaml(<<-YAML)
143
+ es:
144
+ foo: !todo 'latest'
145
+ YAML
146
+
147
+ corrected = correct_ast(ast_1, ast_2)
148
+ expect(corrected.ast_1.to_yaml).to eq(result.to_yaml)
149
+ end
150
+
151
+ it 'should update translations with !todo (works bidirectionally)' do
152
+ ast_1 = parse_yaml(<<-YAML)
153
+ es:
154
+ foo: 'latest'
155
+ YAML
156
+ ast_2 = parse_yaml(<<-YAML)
157
+ en:
158
+ foo: !todo 'outdated'
159
+ YAML
160
+ result_1 = parse_yaml(<<-YAML)
161
+ es:
162
+ foo: 'latest'
163
+ YAML
164
+ result_2 = parse_yaml(<<-YAML)
165
+ en:
166
+ foo: !todo 'latest'
167
+ YAML
168
+
169
+ corrected = correct_ast(ast_1, ast_2)
170
+ expect(corrected.ast_1.to_yaml).to eq(result_1.to_yaml)
171
+ expect(corrected.ast_2.to_yaml).to eq(result_2.to_yaml)
172
+ end
173
+
174
+ it 'should update translations with !todo (synchronize)' do
175
+ ast_1 = parse_yaml(<<-YAML)
176
+ es:
177
+ foo: !todo 'outdated'
178
+ YAML
179
+ ast_2 = parse_yaml(<<-YAML)
180
+ en:
181
+ foo: !todo 'latest'
182
+ YAML
183
+ result_1 = parse_yaml(<<-YAML)
184
+ es:
185
+ foo: !todo 'latest'
186
+ YAML
187
+ result_2 = parse_yaml(<<-YAML)
188
+ en:
189
+ foo: !todo 'latest'
190
+ YAML
191
+
192
+ corrected = correct_ast(ast_1, ast_2)
193
+ expect(corrected.ast_1.to_yaml).to eq(result_1.to_yaml)
194
+ expect(corrected.ast_2.to_yaml).to eq(result_2.to_yaml)
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,137 @@
1
+ require 'i18n_flow/formatter'
2
+
3
+ describe I18nFlow::Formatter do
4
+ def format_ast(ast)
5
+ I18nFlow::Formatter.new(ast)
6
+ .tap(&:format!)
7
+ .ast
8
+ end
9
+
10
+ describe '#format!' do
11
+ it 'should remove extra whitespaces' do
12
+ ast = parse_yaml(<<-YAML)
13
+ en:
14
+ alfa: 'A'
15
+
16
+ bravo: 'B'
17
+ charlie: 'C'
18
+ delta: 'D'
19
+ echo: 'E'
20
+ foxtrot: 'F'
21
+
22
+ golf: 'G'
23
+ hotel: 'H'
24
+ YAML
25
+ result = parse_yaml(<<-YAML)
26
+ en:
27
+ alfa: 'A'
28
+ bravo: 'B'
29
+ charlie: 'C'
30
+ delta: 'D'
31
+ echo: 'E'
32
+ foxtrot: 'F'
33
+ golf: 'G'
34
+ hotel: 'H'
35
+ YAML
36
+
37
+ formatted = format_ast(ast)
38
+ expect(formatted.to_yaml).to eq(result.to_yaml)
39
+ end
40
+
41
+ context 'sort keys' do
42
+ it 'should sort keys' do
43
+ ast = parse_yaml(<<-YAML)
44
+ en:
45
+ echo: 'E'
46
+ delta: 'D'
47
+ foxtrot: 'F'
48
+ alfa: 'A'
49
+ hotel: 'H'
50
+ bravo: 'B'
51
+ charlie: 'C'
52
+ golf: 'G'
53
+ YAML
54
+ result = parse_yaml(<<-YAML)
55
+ en:
56
+ alfa: 'A'
57
+ bravo: 'B'
58
+ charlie: 'C'
59
+ delta: 'D'
60
+ echo: 'E'
61
+ foxtrot: 'F'
62
+ golf: 'G'
63
+ hotel: 'H'
64
+ YAML
65
+
66
+ formatted = format_ast(ast)
67
+ expect(formatted.to_yaml).to eq(result.to_yaml)
68
+ end
69
+
70
+ it 'should sort keys recursively' do
71
+ ast = parse_yaml(<<-YAML)
72
+ en:
73
+ alfa: 'A'
74
+ bravo:
75
+ delta: 'D'
76
+ charlie: 'C'
77
+ echo:
78
+ foxtrot: 'F'
79
+ hotel: 'H'
80
+ golf: 'G'
81
+ YAML
82
+ result = parse_yaml(<<-YAML)
83
+ en:
84
+ alfa: 'A'
85
+ bravo:
86
+ charlie: 'C'
87
+ delta: 'D'
88
+ echo:
89
+ foxtrot: 'F'
90
+ golf: 'G'
91
+ hotel: 'H'
92
+ YAML
93
+
94
+ formatted = format_ast(ast)
95
+ expect(formatted.to_yaml).to eq(result.to_yaml)
96
+ end
97
+
98
+ it 'should move mappings at the bottom' do
99
+ ast = parse_yaml(<<-YAML)
100
+ en:
101
+ alfa: 'A'
102
+ bravo: 'B'
103
+ charlie:
104
+ - 'one'
105
+ - 'two'
106
+ delta: 'D'
107
+ echo: 'E'
108
+ foxtrot:
109
+ bar: 'bar'
110
+ foo: 'foo'
111
+ golf: 'G'
112
+ hotel:
113
+ baz: 'baz'
114
+ YAML
115
+ result = parse_yaml(<<-YAML)
116
+ en:
117
+ alfa: 'A'
118
+ bravo: 'B'
119
+ charlie:
120
+ - 'one'
121
+ - 'two'
122
+ delta: 'D'
123
+ echo: 'E'
124
+ golf: 'G'
125
+ foxtrot:
126
+ bar: 'bar'
127
+ foo: 'foo'
128
+ hotel:
129
+ baz: 'baz'
130
+ YAML
131
+
132
+ formatted = format_ast(ast)
133
+ expect(formatted.to_yaml).to eq(result.to_yaml)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -377,7 +377,7 @@ describe I18nFlow::Validator::Symmetry do
377
377
  ])
378
378
  end
379
379
 
380
- it 'should fail if texts are different' do
380
+ it 'should fail if texts are different from the master' do
381
381
  ast_1 = parse_yaml(<<-YAML)['en']
382
382
  en:
383
383
  key_1: text_1
@@ -394,7 +394,28 @@ describe I18nFlow::Validator::Symmetry do
394
394
  validator.validate!
395
395
 
396
396
  expect(validator.errors).to eq([
397
- I18nFlow::Validator::TodoContentError.new('ja.key_2', expect: 'text_2', actual: 'text_9'),
397
+ I18nFlow::Validator::TodoContentError.new('ja.key_2', expect: 'text_2', actual: 'text_9', inverse: false),
398
+ ])
399
+ end
400
+
401
+ it 'should fail if texts are different from the foreign' do
402
+ ast_1 = parse_yaml(<<-YAML)['en']
403
+ en:
404
+ key_1: text_1
405
+ key_2: !todo text_9
406
+ YAML
407
+ ast_2 = parse_yaml(<<-YAML)['ja']
408
+ ja:
409
+ key_1: text_1
410
+ key_2: text_2
411
+ YAML
412
+
413
+ allow(validator).to receive(:ast_1).and_return(ast_1)
414
+ allow(validator).to receive(:ast_2).and_return(ast_2)
415
+ validator.validate!
416
+
417
+ expect(validator.errors).to eq([
418
+ I18nFlow::Validator::TodoContentError.new('en.key_2', expect: 'text_2', actual: 'text_9', inverse: true),
398
419
  ])
399
420
  end
400
421
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuki Iwanaga
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-09 00:00:00.000000000 Z
11
+ date: 2019-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: psych
@@ -125,6 +125,7 @@ files:
125
125
  - lib/i18n_flow/cli/color.rb
126
126
  - lib/i18n_flow/cli/command_base.rb
127
127
  - lib/i18n_flow/cli/copy_command.rb
128
+ - lib/i18n_flow/cli/format_command.rb
128
129
  - lib/i18n_flow/cli/help_command.rb
129
130
  - lib/i18n_flow/cli/lint_command.rb
130
131
  - lib/i18n_flow/cli/lint_command/ascii.erb
@@ -140,6 +141,8 @@ files:
140
141
  - lib/i18n_flow/cli/split_command.rb
141
142
  - lib/i18n_flow/cli/version_command.rb
142
143
  - lib/i18n_flow/configuration.rb
144
+ - lib/i18n_flow/corrector.rb
145
+ - lib/i18n_flow/formatter.rb
143
146
  - lib/i18n_flow/parser.rb
144
147
  - lib/i18n_flow/repository.rb
145
148
  - lib/i18n_flow/search.rb
@@ -162,6 +165,8 @@ files:
162
165
  - spec/lib/i18n_flow/cli/help_command_spec.rb
163
166
  - spec/lib/i18n_flow/cli/version_command_spec.rb
164
167
  - spec/lib/i18n_flow/configuration_spec.rb
168
+ - spec/lib/i18n_flow/corrector_spec.rb
169
+ - spec/lib/i18n_flow/formatter_spec.rb
165
170
  - spec/lib/i18n_flow/repository_spec.rb
166
171
  - spec/lib/i18n_flow/splitter/merger_spec.rb
167
172
  - spec/lib/i18n_flow/util_spec.rb
@@ -201,6 +206,8 @@ test_files:
201
206
  - spec/lib/i18n_flow/cli/help_command_spec.rb
202
207
  - spec/lib/i18n_flow/cli/version_command_spec.rb
203
208
  - spec/lib/i18n_flow/configuration_spec.rb
209
+ - spec/lib/i18n_flow/corrector_spec.rb
210
+ - spec/lib/i18n_flow/formatter_spec.rb
204
211
  - spec/lib/i18n_flow/repository_spec.rb
205
212
  - spec/lib/i18n_flow/splitter/merger_spec.rb
206
213
  - spec/lib/i18n_flow/util_spec.rb