i18n_flow 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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