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 +4 -4
- data/.gitignore +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +5 -1
- data/lib/i18n_flow/cli.rb +9 -7
- data/lib/i18n_flow/cli/copy_command.rb +3 -24
- data/lib/i18n_flow/cli/format_command.rb +57 -0
- data/lib/i18n_flow/cli/help_command.rb +1 -0
- data/lib/i18n_flow/cli/lint_command/ascii.erb +6 -0
- data/lib/i18n_flow/cli/lint_command/markdown.erb +8 -2
- data/lib/i18n_flow/corrector.rb +40 -0
- data/lib/i18n_flow/formatter.rb +20 -0
- data/lib/i18n_flow/validator/errors.rb +27 -1
- data/lib/i18n_flow/validator/symmetry.rb +20 -13
- data/lib/i18n_flow/version.rb +1 -1
- data/lib/i18n_flow/yaml_ast_proxy.rb +36 -0
- data/lib/i18n_flow/yaml_ast_proxy/mapping.rb +14 -0
- data/lib/i18n_flow/yaml_ast_proxy/node.rb +8 -1
- data/lib/i18n_flow/yaml_ast_proxy/sequence.rb +1 -1
- data/spec/lib/i18n_flow/corrector_spec.rb +197 -0
- data/spec/lib/i18n_flow/formatter_spec.rb +137 -0
- data/spec/lib/i18n_flow/validator/symmetry_spec.rb +23 -2
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '08d2cf8d25372c57651808089a31dedc34a52f81'
|
4
|
+
data.tar.gz: 2a96774f923502e0dd534c8b632acf4f7e5d232f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '03463815dc69ad05dfb2e4b048201441e212a165947a546f4b8e1828b72f32cda5e1f16ab0d335fe9881bc949cfad7a5fa0eb6acf6ebc50a312cfa5d2080b17b'
|
7
|
+
data.tar.gz: 4bd655b11d659a008101bd28f113c65d8d03ea59ab7fe969c1c702e2dabacca9b52c20da33f4e2b4c3981f9b74af73cc87dbc11ae96fa269f2bcfd00fdc7f591
|
data/Gemfile.lock
CHANGED
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/
|
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
|
@@ -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:
|
37
|
-
foreign:
|
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)
|
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)
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
elsif n2.
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
|
data/lib/i18n_flow/version.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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.
|
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-
|
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
|