langulator 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +105 -34
- data/langulator.gemspec +1 -1
- data/lib/langulator/aggregate.rb +124 -0
- data/lib/langulator/loader.rb +9 -10
- data/lib/langulator.rb +5 -34
- data/spec/aggregate_spec.rb +141 -0
- data/spec/fixtures/translations.yml +12 -0
- data/spec/loader_spec.rb +3 -6
- metadata +9 -12
- data/lib/langulator/munger.rb +0 -44
- data/lib/langulator/untangler.rb +0 -34
- data/spec/langulator_spec.rb +0 -87
- data/spec/munger_spec.rb +0 -102
- data/spec/untangler_spec.rb +0 -75
data/README.md
CHANGED
@@ -4,6 +4,16 @@ Manage your i18n.
|
|
4
4
|
|
5
5
|
Or rather: compile it into a single managable file and give it to the translator, so you don't have to deal with it.
|
6
6
|
|
7
|
+
For example, given that you've written the original translations in English and also need French and Norwegian versions, and that you have multiple paths that contain i18n yml files named per the convention `<iso-language-code>.yml` (don't get me started on the Norwegian iso language code), then this will give you a single output file where each key is listed with the translations immediately below it.
|
8
|
+
|
9
|
+
If you have partial translations in the target languages, these will be included.
|
10
|
+
|
11
|
+
Any extraneous translations (i.e. keys that may have been in use previously but are no longer referenced in the original language) will be discarded.
|
12
|
+
|
13
|
+
Any missing translations will be given keys with an empty spot, ready for translation.
|
14
|
+
|
15
|
+
It handles arbitrarily deep nestings.
|
16
|
+
|
7
17
|
## Caveats
|
8
18
|
|
9
19
|
If you need something other than US-ASCII, you may want to consider using ruby 1.9.3.
|
@@ -30,65 +40,126 @@ Or install it yourself as:
|
|
30
40
|
|
31
41
|
$ gem install langulator
|
32
42
|
|
43
|
+
|
33
44
|
## Usage
|
34
45
|
|
35
|
-
|
46
|
+
### Compile
|
36
47
|
|
37
|
-
|
48
|
+
* load individual translations
|
49
|
+
* combine into aggregated translations
|
50
|
+
* write to an aggregate file
|
38
51
|
|
39
|
-
|
52
|
+
Langulator.compile(:source_language => :en, :target_languages => [:fr, :no], :base_path => '**/i18n/', :to => '/tmp/translations.yml')
|
40
53
|
|
41
|
-
|
54
|
+
Input:
|
42
55
|
|
43
|
-
|
56
|
+
# config/i18n/en.yml
|
57
|
+
---
|
58
|
+
food:
|
59
|
+
breakfast: Eggs
|
60
|
+
lunch: Sandwich
|
61
|
+
dinner:
|
62
|
+
main_course: Steak
|
63
|
+
desert: Chocolate mousse
|
64
|
+
|
65
|
+
# config/i18n/fr.yml
|
66
|
+
---
|
67
|
+
food:
|
68
|
+
breakfast: Des oeufs
|
69
|
+
thank_you: Merci
|
44
70
|
|
45
|
-
|
71
|
+
# missing no.yml file
|
46
72
|
|
47
|
-
|
73
|
+
Outputs:
|
48
74
|
|
49
|
-
|
75
|
+
# tmp/translations.yml
|
76
|
+
config/i18n/:
|
77
|
+
food:
|
78
|
+
breakfast:
|
79
|
+
en: Eggs
|
80
|
+
fr: Des oeufs
|
81
|
+
no:
|
82
|
+
lunch:
|
83
|
+
en: Sandwich
|
84
|
+
fr:
|
85
|
+
no:
|
86
|
+
dinner:
|
87
|
+
main_course:
|
88
|
+
en: Steak
|
89
|
+
fr:
|
90
|
+
no:
|
91
|
+
desert:
|
92
|
+
en: Chocolate pudding
|
93
|
+
fr:
|
94
|
+
no:
|
95
|
+
|
96
|
+
### Decompile
|
97
|
+
|
98
|
+
* load an aggregate file
|
99
|
+
* separate into individual translations
|
100
|
+
* write to individual translation files
|
101
|
+
|
102
|
+
Langulator.decompile(:from => './tmp/translations.yml', :languages => [:en, :fr, :no])
|
50
103
|
|
51
104
|
Input:
|
52
105
|
|
53
|
-
|
106
|
+
config/i18n/:
|
107
|
+
food:
|
108
|
+
breakfast:
|
109
|
+
en: Eggs
|
110
|
+
fr: Des oeufs
|
111
|
+
no: Egg
|
112
|
+
lunch:
|
113
|
+
en: Sandwich
|
114
|
+
fr: Sandwich
|
115
|
+
no: Ostesmørbrød
|
116
|
+
dinner:
|
117
|
+
main_course:
|
118
|
+
en: Steak
|
119
|
+
fr: Biffteak
|
120
|
+
no: Steak
|
121
|
+
desert:
|
122
|
+
en: Chocolate mousse
|
123
|
+
fr: Mousse au chocolat
|
124
|
+
no: Sjokolademousse
|
125
|
+
|
126
|
+
Output:
|
127
|
+
|
128
|
+
# config/i18n/en.yml
|
54
129
|
---
|
55
130
|
food:
|
56
131
|
breakfast: Eggs
|
57
132
|
lunch: Sandwich
|
58
133
|
dinner:
|
59
134
|
main_course: Steak
|
60
|
-
desert: Chocolate
|
135
|
+
desert: Chocolate mousse
|
61
136
|
|
62
|
-
# fr.yml
|
137
|
+
# config/i18n/fr.yml
|
63
138
|
---
|
64
139
|
food:
|
65
140
|
breakfast: Des oeufs
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
Langulator.write(:language => 'en', :alternates => ['fr', 'de'], :base_path => '**/i18n/', :to => '/tmp/translations.yml')
|
71
|
-
|
72
|
-
Outputs:
|
141
|
+
lunch: Sandwich
|
142
|
+
dinner:
|
143
|
+
main_course: Biffteak
|
144
|
+
desert: Mousse au chocolat
|
73
145
|
|
146
|
+
# config/i18n/no.yml
|
147
|
+
---
|
74
148
|
food:
|
75
|
-
breakfast:
|
76
|
-
|
77
|
-
fr: Des oeufs
|
78
|
-
de:
|
79
|
-
lunch:
|
80
|
-
en: Sandwich
|
81
|
-
fr:
|
82
|
-
de:
|
149
|
+
breakfast: Egg
|
150
|
+
lunch: Sandwich
|
83
151
|
dinner:
|
84
|
-
main_course:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
152
|
+
main_course: Steak
|
153
|
+
desert: Sjokolademousse
|
154
|
+
|
155
|
+
## TODO
|
156
|
+
|
157
|
+
* handle yml files that are namespaced by the language, e.g.
|
158
|
+
```
|
159
|
+
---
|
160
|
+
en:
|
161
|
+
stuff: "whatever"
|
162
|
+
```
|
92
163
|
|
93
164
|
## Contributing
|
94
165
|
|
data/langulator.gemspec
CHANGED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'langulator/loader'
|
2
|
+
|
3
|
+
module Langulator
|
4
|
+
class Aggregate
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def from_file(options)
|
8
|
+
translations = YAML.load(File.read(options[:from]))
|
9
|
+
new options.merge(:aggregate_translations => translations)
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_files(options)
|
13
|
+
loader = Loader.new(options)
|
14
|
+
new options.merge(:individual_translations => loader.translations)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :languages, :source_language, :target_languages, :aggregate_file_path
|
19
|
+
def initialize(options = {})
|
20
|
+
@aggregate_file_path = options[:to]
|
21
|
+
@aggregate = options[:aggregate_translations]
|
22
|
+
@individual_translations = options[:individual_translations]
|
23
|
+
@source_language = options[:source_language]
|
24
|
+
@target_languages = Array(options[:target_languages])
|
25
|
+
@languages = options[:languages] || [source_language] + target_languages
|
26
|
+
end
|
27
|
+
|
28
|
+
def individual_translations
|
29
|
+
@individual_translations ||= separate
|
30
|
+
end
|
31
|
+
|
32
|
+
def aggregate
|
33
|
+
@aggregate ||= combine
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract(language, tangled)
|
37
|
+
separated = {}
|
38
|
+
tangled.keys.each do |key|
|
39
|
+
values = tangled[key]
|
40
|
+
if translations?(values)
|
41
|
+
separated[key] = values[language]
|
42
|
+
else
|
43
|
+
separated[key] = extract(language, values)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
separated
|
47
|
+
end
|
48
|
+
|
49
|
+
def compile
|
50
|
+
write(aggregate_file_path, aggregate)
|
51
|
+
end
|
52
|
+
|
53
|
+
def decompile
|
54
|
+
individual_translations.each do |language, translations|
|
55
|
+
translations.each do |path, translation|
|
56
|
+
filename = "#{path}#{language}.yml"
|
57
|
+
write filename, translation
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: find a good name for this
|
63
|
+
def to_aggregate(language, translations)
|
64
|
+
dictionary = {}
|
65
|
+
translations.each do |key, value|
|
66
|
+
dictionary[key] ||= {}
|
67
|
+
if value.is_a?(Hash)
|
68
|
+
dictionary[key] = to_aggregate(language, value)
|
69
|
+
else
|
70
|
+
dictionary[key][language] = value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
dictionary
|
74
|
+
end
|
75
|
+
|
76
|
+
def insert(language, translations, dictionary)
|
77
|
+
dictionary.dup.each do |key, value|
|
78
|
+
if value.is_a?(Hash)
|
79
|
+
insert(language, (translations || {})[key], value)
|
80
|
+
else
|
81
|
+
dictionary[language] = translations
|
82
|
+
end
|
83
|
+
end
|
84
|
+
dictionary
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def translations?(values)
|
90
|
+
!values.keys.select {|key| languages.include?(key) }.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def write(filename, content)
|
94
|
+
File.open(filename, 'w:utf-8') do |file|
|
95
|
+
file.write content.to_yaml
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def separate
|
100
|
+
separated = {}
|
101
|
+
languages.each do |language|
|
102
|
+
separated[language] = extract(language, aggregate)
|
103
|
+
end
|
104
|
+
separated
|
105
|
+
end
|
106
|
+
|
107
|
+
def combine
|
108
|
+
source_translations = individual_translations[source_language]
|
109
|
+
|
110
|
+
target_translations = {}
|
111
|
+
target_languages.each do |language|
|
112
|
+
target_translations[language] = individual_translations[language]
|
113
|
+
end
|
114
|
+
|
115
|
+
dictionary = to_aggregate(source_language, source_translations)
|
116
|
+
target_translations.each do |language, translations|
|
117
|
+
dictionary = insert(language, translations, dictionary)
|
118
|
+
end
|
119
|
+
|
120
|
+
dictionary
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/lib/langulator/loader.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
1
|
module Langulator
|
2
2
|
class Loader
|
3
3
|
|
4
|
-
attr_reader :base_path, :
|
4
|
+
attr_reader :base_path, :languages
|
5
5
|
def initialize(options = {})
|
6
6
|
@base_path = options[:base_path]
|
7
|
-
@
|
8
|
-
@alternates = options[:alternates]
|
7
|
+
@languages = [options[:source_language]] + options[:target_languages]
|
9
8
|
end
|
10
9
|
|
11
10
|
def paths
|
12
|
-
@paths
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
unless @paths
|
12
|
+
filename = "#{languages.first}.yml"
|
13
|
+
@paths = Dir.glob("#{base_path}#{filename}").map {|file| file.gsub(filename, '') }.sort
|
14
|
+
end
|
15
|
+
@paths
|
17
16
|
end
|
18
17
|
|
19
|
-
def
|
18
|
+
def translations
|
20
19
|
translations = {}
|
21
|
-
|
20
|
+
languages.each do |language|
|
22
21
|
translations[language] = load_translations(language)
|
23
22
|
end
|
24
23
|
translations
|
data/lib/langulator.rb
CHANGED
@@ -1,46 +1,17 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'yaml'
|
3
|
-
require 'langulator/
|
4
|
-
require 'langulator/munger'
|
5
|
-
require 'langulator/untangler'
|
3
|
+
require 'langulator/aggregate'
|
6
4
|
|
7
5
|
module Langulator
|
8
|
-
class << self
|
9
|
-
def munge(options = {})
|
10
|
-
loader = Loader.new(options)
|
11
|
-
munger = Munger.new(:language => loader.origin, :translations => loader.source_translations, :alternates => loader.destination_translations)
|
12
|
-
|
13
|
-
munger.munge
|
14
|
-
end
|
15
6
|
|
7
|
+
class << self
|
16
8
|
def compile(options)
|
17
|
-
|
18
|
-
translations = munge(options)
|
19
|
-
write filename, translations
|
20
|
-
end
|
21
|
-
|
22
|
-
def untangle(aggregate, options)
|
23
|
-
Untangler.new(aggregate, options).untangle
|
9
|
+
Aggregate.from_files(options).compile
|
24
10
|
end
|
25
11
|
|
26
12
|
def decompile(options)
|
27
|
-
|
28
|
-
aggregate = YAML.load(File.read(options[:file]))
|
29
|
-
translations = untangle(aggregate, options)
|
30
|
-
|
31
|
-
translations.each do |language, data|
|
32
|
-
data.each do |path, translation|
|
33
|
-
filename = "#{path}#{language}.yml"
|
34
|
-
write filename, translation
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
def write(filename, content)
|
41
|
-
File.open(filename, 'w:utf-8') do |file|
|
42
|
-
file.write content.to_yaml
|
43
|
-
end
|
13
|
+
Aggregate.from_file(options).decompile
|
44
14
|
end
|
45
15
|
end
|
16
|
+
|
46
17
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'langulator/aggregate'
|
2
|
+
|
3
|
+
describe Langulator::Aggregate do
|
4
|
+
|
5
|
+
let(:aggregate) do
|
6
|
+
{
|
7
|
+
"spec/fixtures/" => {
|
8
|
+
"words" => {
|
9
|
+
"affirmative" => {
|
10
|
+
:klingon => "HISlaH",
|
11
|
+
:lolcode => "YA RLY"
|
12
|
+
},
|
13
|
+
"negative" => {
|
14
|
+
:klingon => "ghobe'",
|
15
|
+
:lolcode => "NO WAI"
|
16
|
+
},
|
17
|
+
"hello" => {
|
18
|
+
:klingon => "nuqneH",
|
19
|
+
:lolcode => "O HAI"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:klingon) { {"spec/fixtures/" => {"words" => {"affirmative" => "HISlaH", "negative" => "ghobe'", "hello" => "nuqneH"}}} }
|
27
|
+
let(:lolcode) { {"spec/fixtures/" => {"words" => {"affirmative" => "YA RLY", "negative" => "NO WAI", "hello" => "O HAI"}}} }
|
28
|
+
|
29
|
+
describe "combining individual translations" do
|
30
|
+
let(:klingon_as_aggregate) do
|
31
|
+
{
|
32
|
+
"spec/fixtures/" => {
|
33
|
+
"words" => {
|
34
|
+
"affirmative" => { :klingon => "HISlaH" },
|
35
|
+
"negative" => { :klingon => "ghobe'" },
|
36
|
+
"hello" => { :klingon => "nuqneH" }
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:outfile) { 'spec/fixtures/output.yml' }
|
43
|
+
|
44
|
+
let(:compile_options) do
|
45
|
+
{
|
46
|
+
:source_language => :klingon,
|
47
|
+
:target_languages => :lolcode,
|
48
|
+
:individual_translations => {:klingon => klingon, :lolcode => lolcode},
|
49
|
+
:to => outfile
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
subject { Langulator::Aggregate.new(compile_options) }
|
54
|
+
|
55
|
+
its(:source_language) { should eq(:klingon) }
|
56
|
+
its(:target_languages) { should eq([:lolcode]) }
|
57
|
+
its(:languages) { should eq([:klingon, :lolcode]) }
|
58
|
+
its(:individual_translations) { should eq({:klingon => klingon, :lolcode => lolcode}) }
|
59
|
+
its(:aggregate_file_path) { should eq(outfile) }
|
60
|
+
its(:aggregate) { should eq(aggregate) }
|
61
|
+
|
62
|
+
it "remappes the source language to initialize aggregate" do
|
63
|
+
subject.to_aggregate(:klingon, subject.individual_translations[:klingon]).should eq(klingon_as_aggregate)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "inserts a target language into the aggregate" do
|
67
|
+
subject.insert(:lolcode, lolcode, klingon_as_aggregate).should eq(aggregate)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "loads an aggregate" do
|
71
|
+
subject = Langulator::Aggregate.from_file(:from => 'spec/fixtures/translations.yml', :languages => [:klingon, :lolcode])
|
72
|
+
subject.aggregate.should eq(aggregate)
|
73
|
+
end
|
74
|
+
|
75
|
+
context "writing an aggregate" do
|
76
|
+
before(:each) do
|
77
|
+
FileUtils.rm(outfile) if File.exists? outfile
|
78
|
+
end
|
79
|
+
|
80
|
+
after(:each) do
|
81
|
+
FileUtils.rm(outfile) if File.exists? outfile
|
82
|
+
end
|
83
|
+
|
84
|
+
it "compiles" do
|
85
|
+
subject.compile
|
86
|
+
YAML.load(File.read(outfile)).should eq(aggregate)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "de-aggregating translations" do
|
92
|
+
subject { Langulator::Aggregate.new(:languages => [:english]) }
|
93
|
+
|
94
|
+
it 'extracts English' do
|
95
|
+
input = {:rock => {:english => "rock"}, :paper => {:english => "paper"}}
|
96
|
+
expected_output = {:rock => "rock", :paper => "paper"}
|
97
|
+
subject.extract(:english, input).should eq(expected_output)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "extracts complicated English" do
|
101
|
+
input = {:a => {:really => {:deeply => {:nested => {:game => {:rock => {:english => "rock"}, :paper => {:english => "paper"}}}}}}}
|
102
|
+
expected_output = {:a => {:really => {:deeply => {:nested => {:game => {:rock => "rock", :paper => "paper"}}}}}}
|
103
|
+
subject.extract(:english, input).should eq(expected_output)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "with aggregated data" do
|
108
|
+
subject { Langulator::Aggregate.new(:aggregate_translations => aggregate, :languages => [:klingon, :lolcode]) }
|
109
|
+
|
110
|
+
it "filters out the klingon" do
|
111
|
+
subject.individual_translations[:klingon].should eq(klingon)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "filters out the lolcode" do
|
115
|
+
subject.individual_translations[:lolcode].should eq(lolcode)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "writing individual translations" do
|
120
|
+
let(:klingon_file) { "spec/fixtures/klingon.yml" }
|
121
|
+
let(:lolcode_file) { "spec/fixtures/lolcode.yml" }
|
122
|
+
|
123
|
+
subject { Langulator::Aggregate.new(:aggregate_translations => aggregate, :languages => [:klingon, :lolcode]) }
|
124
|
+
|
125
|
+
before(:each) do
|
126
|
+
FileUtils.rm(klingon_file) if File.exists? klingon_file
|
127
|
+
FileUtils.rm(lolcode_file) if File.exists? lolcode_file
|
128
|
+
end
|
129
|
+
|
130
|
+
after(:each) do
|
131
|
+
FileUtils.rm(klingon_file) if File.exists? klingon_file
|
132
|
+
FileUtils.rm(lolcode_file) if File.exists? lolcode_file
|
133
|
+
end
|
134
|
+
|
135
|
+
it "decompiles" do
|
136
|
+
subject.decompile
|
137
|
+
YAML.load(File.read(klingon_file)).should eq(klingon["spec/fixtures/"])
|
138
|
+
YAML.load(File.read(lolcode_file)).should eq(lolcode["spec/fixtures/"])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/spec/loader_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'yaml'
|
2
3
|
require 'langulator/loader'
|
3
4
|
|
4
5
|
describe Langulator::Loader do
|
@@ -40,10 +41,6 @@ describe Langulator::Loader do
|
|
40
41
|
}
|
41
42
|
end
|
42
43
|
|
43
|
-
subject { Langulator::Loader.new(:base_path => 'spec/fixtures/**/', :
|
44
|
-
|
45
|
-
its(:paths) { should eq(['spec/fixtures/', 'spec/fixtures/lang/']) }
|
46
|
-
|
47
|
-
its(:source_translations) { should eq(english) }
|
48
|
-
its(:destination_translations) { should eq({'norsk' => norwegian, 'francais' => french}) }
|
44
|
+
subject { Langulator::Loader.new(:base_path => 'spec/fixtures/**/', :source_language => 'english', :target_languages => ['norsk', 'francais']) }
|
45
|
+
its(:translations) { should eq({'english' => english, 'norsk' => norwegian, 'francais' => french}) }
|
49
46
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: langulator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70204764441280 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70204764441280
|
25
25
|
description: Manage, maintain, and munge your i18n files.
|
26
26
|
email:
|
27
27
|
- katrina.owen@gmail.com
|
@@ -38,16 +38,14 @@ files:
|
|
38
38
|
- bin/langulator
|
39
39
|
- langulator.gemspec
|
40
40
|
- lib/langulator.rb
|
41
|
+
- lib/langulator/aggregate.rb
|
41
42
|
- lib/langulator/loader.rb
|
42
|
-
-
|
43
|
-
- lib/langulator/untangler.rb
|
43
|
+
- spec/aggregate_spec.rb
|
44
44
|
- spec/fixtures/english.yml
|
45
45
|
- spec/fixtures/lang/english.yml
|
46
46
|
- spec/fixtures/norsk.yml
|
47
|
-
- spec/
|
47
|
+
- spec/fixtures/translations.yml
|
48
48
|
- spec/loader_spec.rb
|
49
|
-
- spec/munger_spec.rb
|
50
|
-
- spec/untangler_spec.rb
|
51
49
|
homepage: http://github.com/kytrinyx/langulator
|
52
50
|
licenses: []
|
53
51
|
post_install_message:
|
@@ -73,10 +71,9 @@ signing_key:
|
|
73
71
|
specification_version: 3
|
74
72
|
summary: Tasks to keep your i18n files managable.
|
75
73
|
test_files:
|
74
|
+
- spec/aggregate_spec.rb
|
76
75
|
- spec/fixtures/english.yml
|
77
76
|
- spec/fixtures/lang/english.yml
|
78
77
|
- spec/fixtures/norsk.yml
|
79
|
-
- spec/
|
78
|
+
- spec/fixtures/translations.yml
|
80
79
|
- spec/loader_spec.rb
|
81
|
-
- spec/munger_spec.rb
|
82
|
-
- spec/untangler_spec.rb
|
data/lib/langulator/munger.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
module Langulator
|
2
|
-
class Munger
|
3
|
-
|
4
|
-
attr_reader :source_language, :source_translations, :alternate_translations
|
5
|
-
def initialize(options = {})
|
6
|
-
@source_language = options[:language]
|
7
|
-
@source_translations = options[:translations]
|
8
|
-
@alternate_translations = options[:alternates]
|
9
|
-
end
|
10
|
-
|
11
|
-
def munge
|
12
|
-
dictionary = transform(source_language, source_translations)
|
13
|
-
alternate_translations.each do |language, translations|
|
14
|
-
dictionary = insert(language, translations, dictionary)
|
15
|
-
end
|
16
|
-
dictionary
|
17
|
-
end
|
18
|
-
|
19
|
-
def transform(language, translations)
|
20
|
-
dictionary = {}
|
21
|
-
translations.each do |key, value|
|
22
|
-
dictionary[key] ||= {}
|
23
|
-
if value.is_a?(Hash)
|
24
|
-
dictionary[key] = transform(language, value)
|
25
|
-
else
|
26
|
-
dictionary[key][language] = value
|
27
|
-
end
|
28
|
-
end
|
29
|
-
dictionary
|
30
|
-
end
|
31
|
-
|
32
|
-
def insert(language, translations, dictionary)
|
33
|
-
dictionary.dup.each do |key, value|
|
34
|
-
if value.is_a?(Hash)
|
35
|
-
insert(language, (translations || {})[key], value)
|
36
|
-
else
|
37
|
-
dictionary[language] = translations
|
38
|
-
end
|
39
|
-
end
|
40
|
-
dictionary
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
data/lib/langulator/untangler.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module Langulator
|
2
|
-
class Untangler
|
3
|
-
attr_reader :aggregate, :languages
|
4
|
-
def initialize(aggregate, options = {})
|
5
|
-
@aggregate = aggregate
|
6
|
-
@languages = options[:languages]
|
7
|
-
end
|
8
|
-
|
9
|
-
def untangle
|
10
|
-
untangled = {}
|
11
|
-
languages.each do |language|
|
12
|
-
untangled[language] = extract(language, aggregate)
|
13
|
-
end
|
14
|
-
untangled
|
15
|
-
end
|
16
|
-
|
17
|
-
def extract(language, tangled)
|
18
|
-
untangled = {}
|
19
|
-
tangled.keys.each do |key|
|
20
|
-
values = tangled[key]
|
21
|
-
if translations?(values)
|
22
|
-
untangled[key] = values[language]
|
23
|
-
else
|
24
|
-
untangled[key] = extract(language, values)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
untangled
|
28
|
-
end
|
29
|
-
|
30
|
-
def translations?(values)
|
31
|
-
!values.keys.select {|key| languages.include?(key) }.empty?
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/spec/langulator_spec.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'langulator'
|
3
|
-
|
4
|
-
describe Langulator do
|
5
|
-
let(:options) { {:base_path => 'spec/fixtures/**/', :origin => 'english', :alternates => ['norsk', 'francais']} }
|
6
|
-
|
7
|
-
let(:aggregate_incomplete) do
|
8
|
-
{
|
9
|
-
"spec/fixtures/" => {
|
10
|
-
"food" => {
|
11
|
-
"breakfast" => {"english" => "yoghurt", "norsk" => nil, "francais" => nil},
|
12
|
-
"lunch" => {"english" => "sandwich", "norsk" => "smørbrød", "francais" => nil},
|
13
|
-
"dinner" => {
|
14
|
-
"main_course" => {"english" => "steak", "norsk" => nil, "francais" => nil},
|
15
|
-
"side" => {"english" => "baked potato", "norsk" => nil, "francais" => nil},
|
16
|
-
"desert" => {"english" => "chocolate mousse", "norsk" => nil, "francais" => nil}
|
17
|
-
}
|
18
|
-
}
|
19
|
-
},
|
20
|
-
"spec/fixtures/lang/" => {
|
21
|
-
"volume" => {
|
22
|
-
"sound" => {"english" => "loud", "norsk" => nil, "francais" => nil},
|
23
|
-
"liquid" => {"english" => "sloshing", "norsk" => nil, "francais" => nil},
|
24
|
-
"hair" => {"english" => "because I'm worth it", "norsk" => nil, "francais" => nil}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
it "loads and munges" do
|
31
|
-
Langulator.compile(options.merge(:to => './tmp/translations.yml')) #.should eq(aggregate_incomplete)
|
32
|
-
end
|
33
|
-
|
34
|
-
let(:aggregate_complete) do
|
35
|
-
{
|
36
|
-
"spec/fixtures/" => {
|
37
|
-
"food" => {
|
38
|
-
"breakfast" => {"english" => "yoghurt", "norsk" => "joggurt", "francais" => "yaourt"},
|
39
|
-
"lunch" => {"english" => "sandwich", "norsk" => "smørbrød", "francais" => "sandwich"},
|
40
|
-
"dinner" => {
|
41
|
-
"main_course" => {"english" => "steak", "norsk" => "steak", "francais" => "biffteak"},
|
42
|
-
"side" => {"english" => "baked potato", "norsk" => "bakt potet", "francais" => "pomme de terre au four"},
|
43
|
-
"desert" => {"english" => "chocolate mousse", "norsk" => "sjokolademousse", "francais" => "mousse au chocolat"}
|
44
|
-
}
|
45
|
-
}
|
46
|
-
},
|
47
|
-
"spec/fixtures/lang/" => {
|
48
|
-
"volume" => {
|
49
|
-
"sound" => {"english" => "loud", "norsk" => "høyt", "francais" => "fort"},
|
50
|
-
"liquid" => {"english" => "sloshing", "norsk" => "skvulper", "francais" => "agité"},
|
51
|
-
"hair" => {"english" => "because I'm worth it", "norsk" => "for det er jeg verdt", "francais" => "parce que je le vaux bien"}
|
52
|
-
}
|
53
|
-
}
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
|
-
it "untangles" do
|
58
|
-
expected = {
|
59
|
-
"english" => {
|
60
|
-
"spec/fixtures/" => {
|
61
|
-
"food" => {"breakfast" => "yoghurt", "lunch" => "sandwich", "dinner" => {"main_course" => "steak", "side" => "baked potato", "desert" => "chocolate mousse"}}
|
62
|
-
},
|
63
|
-
"spec/fixtures/lang/" => {
|
64
|
-
"volume" => {"sound" => "loud", "liquid" => "sloshing", "hair" => "because I'm worth it"}
|
65
|
-
}
|
66
|
-
},
|
67
|
-
"norsk" => {
|
68
|
-
"spec/fixtures/" => {
|
69
|
-
"food" => {"breakfast" => "joggurt", "lunch" => "smørbrød", "dinner" => {"main_course" => "steak", "side" => "bakt potet", "desert" => "sjokolademousse"}}
|
70
|
-
},
|
71
|
-
"spec/fixtures/lang/" => {
|
72
|
-
"volume" => {"sound" => "høyt", "liquid" => "skvulper", "hair" => "for det er jeg verdt"}
|
73
|
-
}
|
74
|
-
},
|
75
|
-
"francais" => {
|
76
|
-
"spec/fixtures/" => {
|
77
|
-
"food" => {"breakfast" => "yaourt", "lunch" => "sandwich", "dinner" => {"main_course" => "biffteak", "side" => "pomme de terre au four", "desert" => "mousse au chocolat"}}
|
78
|
-
},
|
79
|
-
"spec/fixtures/lang/" => {
|
80
|
-
"volume" => {"sound" => "fort", "liquid" => "agité", "hair" => "parce que je le vaux bien"}
|
81
|
-
}
|
82
|
-
}
|
83
|
-
}
|
84
|
-
Langulator.untangle(aggregate_complete, :languages => ["english", "norsk", "francais"]).should eq(expected)
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
data/spec/munger_spec.rb
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
require 'langulator/munger'
|
2
|
-
|
3
|
-
describe Langulator::Munger do
|
4
|
-
|
5
|
-
let(:english) do
|
6
|
-
{
|
7
|
-
:game => {
|
8
|
-
:rock => "rock",
|
9
|
-
:paper => "paper",
|
10
|
-
:scissors => "scissors",
|
11
|
-
:other => {:deeply => "nested"}
|
12
|
-
}
|
13
|
-
}
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:english_remapped) do
|
17
|
-
{
|
18
|
-
:game => {
|
19
|
-
:rock => {:english => "rock"},
|
20
|
-
:paper => {:english => "paper"},
|
21
|
-
:scissors => {:english => "scissors"},
|
22
|
-
:other => {
|
23
|
-
:deeply => {:english => "nested"}
|
24
|
-
}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
let(:french) { {:game => {:paper => 'papier', :other => {:not => 'included'}}} }
|
30
|
-
|
31
|
-
let(:combined) do
|
32
|
-
{
|
33
|
-
:game => {
|
34
|
-
:rock => {
|
35
|
-
:english => "rock",
|
36
|
-
:french => nil
|
37
|
-
},
|
38
|
-
:paper => {
|
39
|
-
:english => "paper",
|
40
|
-
:french => "papier"
|
41
|
-
},
|
42
|
-
:scissors => {
|
43
|
-
:english => "scissors",
|
44
|
-
:french => nil
|
45
|
-
},
|
46
|
-
:other => {
|
47
|
-
:deeply => {
|
48
|
-
:english => "nested",
|
49
|
-
:french => nil
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
let(:empty_target) do
|
57
|
-
{
|
58
|
-
:game => {
|
59
|
-
:rock => {
|
60
|
-
:english => "rock",
|
61
|
-
:norwegian => nil
|
62
|
-
},
|
63
|
-
:paper => {
|
64
|
-
:english => "paper",
|
65
|
-
:norwegian => nil
|
66
|
-
},
|
67
|
-
:scissors => {
|
68
|
-
:english => "scissors",
|
69
|
-
:norwegian => nil
|
70
|
-
},
|
71
|
-
:other => {
|
72
|
-
:deeply => {
|
73
|
-
:english => "nested",
|
74
|
-
:norwegian => nil
|
75
|
-
}
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
context "partial munges" do
|
82
|
-
subject { Langulator::Munger.new }
|
83
|
-
|
84
|
-
it "remaps a source dictionary" do
|
85
|
-
subject.transform(:english, english).should eq(english_remapped)
|
86
|
-
end
|
87
|
-
|
88
|
-
it "inserts an alternate language" do
|
89
|
-
subject.insert(:french, french, english_remapped).should eq(combined)
|
90
|
-
end
|
91
|
-
|
92
|
-
it "inserts an empty alternate language" do
|
93
|
-
subject.insert(:norwegian, {}, english_remapped).should eq(empty_target)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
it "munges" do
|
98
|
-
munger = Langulator::Munger.new(:language => :english, :translations => english, :alternates => {:french => french})
|
99
|
-
munger.munge.should eq(combined)
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
data/spec/untangler_spec.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'langulator/untangler'
|
3
|
-
|
4
|
-
describe Langulator::Untangler do
|
5
|
-
let(:aggregate) do
|
6
|
-
{
|
7
|
-
:game => {
|
8
|
-
:rock => {
|
9
|
-
:english => "rock",
|
10
|
-
:french => "pierre",
|
11
|
-
},
|
12
|
-
:paper => {
|
13
|
-
:english => "paper",
|
14
|
-
:french => "papier"
|
15
|
-
},
|
16
|
-
:scissors => {
|
17
|
-
:english => "scissors",
|
18
|
-
:french => "ciseau"
|
19
|
-
},
|
20
|
-
:other => {
|
21
|
-
:deeply => {
|
22
|
-
:english => "nested",
|
23
|
-
:french => "imbriqué"
|
24
|
-
}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
let(:english) do
|
31
|
-
{
|
32
|
-
:game => {
|
33
|
-
:rock => "rock",
|
34
|
-
:paper => "paper",
|
35
|
-
:scissors => "scissors",
|
36
|
-
:other => {:deeply => "nested"}
|
37
|
-
}
|
38
|
-
}
|
39
|
-
end
|
40
|
-
|
41
|
-
let(:french) do
|
42
|
-
{
|
43
|
-
:game => {
|
44
|
-
:rock => "pierre",
|
45
|
-
:paper => "papier",
|
46
|
-
:scissors => "ciseau",
|
47
|
-
:other => {:deeply => "imbriqué"}
|
48
|
-
}
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
context "with tangled data" do
|
53
|
-
subject { Langulator::Untangler.new(aggregate, :languages => [:english, :french]) }
|
54
|
-
|
55
|
-
it 'extracts English' do
|
56
|
-
input = {:rock => {:english => "rock"}, :paper => {:english => "paper"}}
|
57
|
-
expected_output = {:rock => "rock", :paper => "paper"}
|
58
|
-
subject.extract(:english, input).should eq(expected_output)
|
59
|
-
end
|
60
|
-
|
61
|
-
it "extracts complicated English" do
|
62
|
-
input = {:a => {:really => {:deeply => {:nested => {:game => {:rock => {:english => "rock"}, :paper => {:english => "paper"}}}}}}}
|
63
|
-
expected_output = {:a => {:really => {:deeply => {:nested => {:game => {:rock => "rock", :paper => "paper"}}}}}}
|
64
|
-
subject.extract(:english, input).should eq(expected_output)
|
65
|
-
end
|
66
|
-
|
67
|
-
it "filters out the english" do
|
68
|
-
subject.untangle[:english].should eq(english)
|
69
|
-
end
|
70
|
-
|
71
|
-
it "filters out the french" do
|
72
|
-
subject.untangle[:french].should eq(french)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|