langulator 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -4,7 +4,7 @@ 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.
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 (`<language>.yml`), then this will give you a single output file where each key is listed with the translations immediately below it.
8
8
 
9
9
  If you have partial translations in the target languages, these will be included.
10
10
 
@@ -20,7 +20,7 @@ If you need something other than US-ASCII, you may want to consider using ruby 1
20
20
 
21
21
  In 1.9.2 (patch 290):
22
22
 
23
- S\xC3\xB8knadstekst
23
+ S\xC3\xB8knadstekst
24
24
 
25
25
  In 1.9.3:
26
26
 
@@ -43,13 +43,17 @@ Or install it yourself as:
43
43
 
44
44
  ## Usage
45
45
 
46
- ### Compile
46
+ ### In Ruby
47
+
48
+ #### Compile
47
49
 
48
50
  * load individual translations
49
51
  * combine into aggregated translations
50
52
  * write to an aggregate file
51
53
 
52
- Langulator.compile(:source_language => :en, :target_languages => [:fr, :no], :base_path => '**/i18n/', :to => '/tmp/translations.yml')
54
+ ```
55
+ Langulator.compile(:source_language => :en, :target_languages => [:fr, :no], :base_path => '**/i18n/', :to => '/tmp/translations.yml')
56
+ ```
53
57
 
54
58
  Input:
55
59
 
@@ -93,13 +97,16 @@ Outputs:
93
97
  fr:
94
98
  no:
95
99
 
96
- ### Decompile
100
+ #### Decompile
97
101
 
98
102
  * load an aggregate file
99
103
  * separate into individual translations
100
104
  * write to individual translation files
101
105
 
102
- Langulator.decompile(:from => './tmp/translations.yml', :languages => [:en, :fr, :no])
106
+
107
+ ```
108
+ Langulator.decompile(:from => './tmp/translations.yml')
109
+ ```
103
110
 
104
111
  Input:
105
112
 
@@ -152,6 +159,28 @@ Output:
152
159
  main_course: Steak
153
160
  desert: Sjokolademousse
154
161
 
162
+ ### CLI
163
+
164
+ ```
165
+ $~: langulator help
166
+
167
+ $~: langulator help compile
168
+
169
+ $~: langulator help decompile
170
+ ```
171
+
172
+ #### Compile
173
+
174
+ ```
175
+ $~: langulator compile -s en -t fr nb-no -p 'config/locales/**/' -f ./tmp/translations.yml
176
+ ```
177
+
178
+ #### Decompile
179
+
180
+ ```
181
+ $~: langulator decompile -f ./tmp/translations.yml
182
+ ```
183
+
155
184
  ## TODO
156
185
 
157
186
  * handle yml files that are namespaced by the language, e.g.
@@ -1,3 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'langulator'
4
+ require 'langulator/cli'
5
+
6
+ Langulator::CLI.start
@@ -0,0 +1,13 @@
1
+ Feature: Aggregate individual translation files
2
+
3
+ Combines any number of source translation files into a single file,
4
+ where all the translations for a single key are grouped for easy translation.
5
+
6
+ @compile
7
+ Scenario: Compile aggregate
8
+ Given a website in English, French, and Norwegian
9
+ And there is a translation file for English
10
+ And there is an outdated translation file for French
11
+ And there is no translation file for Norwegian
12
+ When the aggregate file is compiled
13
+ Then the output file collects the translation keys for easy translation
@@ -0,0 +1,13 @@
1
+ Feature: Separate an aggregate into individual i18n files
2
+
3
+ Takes an aggregate translation file and writes the translated keys into
4
+ their respective i18n files at their original locations.
5
+
6
+ @decompile
7
+ Scenario: Decompile aggregate
8
+ Given a website in English, French, and Norwegian
9
+ And there is an aggregate translation file
10
+ When the aggregate file is decompiled
11
+ Then the English translations should be complete
12
+ And the French translations should be complete
13
+ And the Norwegian translations should be complete
@@ -0,0 +1,8 @@
1
+ ---
2
+ food:
3
+ breakfast: yoghurt
4
+ lunch: sandwich
5
+ dinner:
6
+ main_course: steak
7
+ side: baked potato
8
+ desert: chocolate mousse
@@ -0,0 +1,10 @@
1
+ ---
2
+ food:
3
+ breakfast: yaourt
4
+ lunch: sandwich
5
+ dinner:
6
+ main_course: biffteak
7
+ side:
8
+ desert: mousse au chocolat
9
+ thanks: merci
10
+
@@ -0,0 +1,38 @@
1
+ ---
2
+ features/fixtures/:
3
+ food:
4
+ breakfast:
5
+ :english: yoghurt
6
+ :norsk: joggurt
7
+ :francais: yaourt
8
+ lunch:
9
+ :english: sandwich
10
+ :norsk: smørbrød
11
+ :francais: sandwich
12
+ dinner:
13
+ main_course:
14
+ :english: steak
15
+ :norsk: steak
16
+ :francais: biffteak
17
+ side:
18
+ :english: baked potato
19
+ :norsk: bakt potet
20
+ :francais: pomme de terre au four
21
+ desert:
22
+ :english: chocolate mousse
23
+ :norsk: sjokolademousse
24
+ :francais: mousse au chocolat
25
+ features/fixtures/lang/:
26
+ volume:
27
+ sound:
28
+ :english: loud
29
+ :norsk: høyt
30
+ :francais: haut
31
+ liquid:
32
+ :english: full
33
+ :norsk: fullt
34
+ :francais: plein
35
+ hair:
36
+ :english: because I'm worth it
37
+ :norsk: for det er jeg verdt
38
+ :francais: parce que je le vaux bien
@@ -0,0 +1,5 @@
1
+ ---
2
+ volume:
3
+ sound: loud
4
+ liquid: full
5
+ hair: because I'm worth it
@@ -0,0 +1,5 @@
1
+ ---
2
+ volume:
3
+ sound: haut
4
+ liquid: plein
5
+ hair: parce que je le vaux bien
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ Given /a website in English, French, and Norwegian$/ do
4
+ end
5
+
6
+ Given /^there is a translation file for English$/ do
7
+ end
8
+
9
+ Given /^there is an outdated translation file for French$/ do
10
+ end
11
+
12
+ Given /^there is no translation file for Norwegian$/ do
13
+ end
14
+
15
+ Given /^there is an aggregate translation file$/ do
16
+ end
17
+
18
+ When /the aggregate file is compiled/ do
19
+ Langulator.compile(:source_language => :en, :target_languages => [:fr, :no], :base_path => 'features/fixtures/**/', :to => AGGREGATE_FILE)
20
+ end
21
+
22
+ When /^the aggregate file is decompiled$/ do
23
+ # Using full language name rather than the iso-code to avoid
24
+ # overwriting the individual files used for input in the compile feature.
25
+ # Ideally I should just use different languages, but I don't speak any others.
26
+ Langulator.decompile(:languages => [:english, :francais, :norsk], :from => INFILE)
27
+ end
28
+
29
+ Then /^the output file collects the translation keys for easy translation$/ do
30
+ expected = {
31
+ "features/fixtures/" => {
32
+ "food" => {
33
+ "breakfast" => {:en => "yoghurt", :no => nil, :fr => "yaourt"},
34
+ "lunch" => {:en => "sandwich", :no => nil, :fr => "sandwich"},
35
+ "dinner" => {
36
+ "main_course" => {:en => "steak", :no => nil, :fr => "biffteak"},
37
+ "side" => {:en => "baked potato", :no => nil, :fr => nil},
38
+ "desert" => {:en => "chocolate mousse", :no => nil, :fr => "mousse au chocolat"}
39
+ }
40
+ }
41
+ },
42
+ "features/fixtures/lang/" => {
43
+ "volume" => {
44
+ "sound" => {:en => "loud", :no => nil, :fr => "haut"},
45
+ "liquid" => {:en => "full", :no => nil, :fr => "plein"},
46
+ "hair" => {:en => "because I'm worth it", :no => nil, :fr => "parce que je le vaux bien"}
47
+ }
48
+ }
49
+ }
50
+ YAML.load(File.read(AGGREGATE_FILE)).should eq(expected)
51
+ end
52
+
53
+ Then /^the English translations should be complete$/ do
54
+ expected = {"food"=>{"breakfast"=>"yoghurt", "lunch"=>"sandwich", "dinner"=>{"main_course"=>"steak", "side"=>"baked potato", "desert"=>"chocolate mousse"}}}
55
+ english = YAML.load(File.read('features/fixtures/english.yml')).should eq(expected)
56
+
57
+ expected = {"volume"=>{"sound"=>"loud", "liquid"=>"full", "hair"=>"because I'm worth it"}}
58
+ nested_english = YAML.load(File.read('features/fixtures/lang/english.yml')).should eq(expected)
59
+ end
60
+
61
+ Then /^the French translations should be complete$/ do
62
+ expected = {"food"=>{"breakfast"=>"yaourt", "lunch"=>"sandwich", "dinner"=>{"main_course"=>"biffteak", "side"=>"pomme de terre au four", "desert"=>"mousse au chocolat"}}}
63
+ french = YAML.load(File.read('features/fixtures/francais.yml')).should eq(expected)
64
+
65
+ expected = {"volume"=>{"sound"=>"haut", "liquid"=>"plein", "hair"=>"parce que je le vaux bien"}}
66
+ nested_french = YAML.load(File.read('features/fixtures/lang/francais.yml')).should eq(expected)
67
+ end
68
+
69
+ Then /^the Norwegian translations should be complete$/ do
70
+ expected = {"food"=>{"breakfast"=>"joggurt", "lunch"=>"smørbrød", "dinner"=>{"main_course"=>"steak", "side"=>"bakt potet", "desert"=>"sjokolademousse"}}}
71
+ norwegian = YAML.load(File.read('features/fixtures/norsk.yml')).should eq(expected)
72
+
73
+ expected = {"volume"=>{"sound"=>"høyt", "liquid"=>"fullt", "hair"=>"for det er jeg verdt"}}
74
+ nested_norwegian = YAML.load(File.read('features/fixtures/lang/norsk.yml')).should eq(expected)
75
+ end
76
+
@@ -0,0 +1,13 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/../../lib/"
2
+
3
+ require 'langulator'
4
+
5
+ path = 'features/fixtures/'
6
+
7
+ AGGREGATE_FILE = "#{path}out.yml"
8
+
9
+ INFILE = "#{path}in.yml"
10
+
11
+ individual = ['english', 'lang/english', 'francais', 'lang/francais', 'norsk', 'lang/norsk']
12
+ INDIVIDUAL_FILES = individual.map {|f| "#{path}#{f}.yml"}
13
+
@@ -0,0 +1,20 @@
1
+ def cleanup(file)
2
+ FileUtils.rm(file) if File.exists? file
3
+ end
4
+
5
+ Before('@compile') do
6
+ cleanup AGGREGATE_FILE
7
+ end
8
+
9
+ After('@compile') do
10
+ cleanup AGGREGATE_FILE
11
+ end
12
+
13
+ Before('@decompile') do
14
+ INDIVIDUAL_FILES.each {|f| cleanup f}
15
+
16
+ end
17
+
18
+ After('@decompile') do
19
+ INDIVIDUAL_FILES.each {|f| cleanup f}
20
+ end
@@ -12,7 +12,9 @@ Gem::Specification.new do |gem|
12
12
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
13
  gem.name = "langulator"
14
14
  gem.require_paths = ["lib"]
15
- gem.version = "0.0.4"
15
+ gem.version = "0.0.5"
16
16
 
17
17
  gem.add_development_dependency "rspec"
18
+ gem.add_development_dependency "cucumber"
19
+ gem.add_runtime_dependency "thor"
18
20
  end
@@ -1,16 +1,19 @@
1
1
  # encoding: utf-8
2
2
  require 'yaml'
3
- require 'langulator/aggregate'
3
+ require 'langulator/translation'
4
+ require 'langulator/aggregate_translation'
5
+ require 'langulator/individual_translation'
6
+ require 'langulator/individual_translations'
4
7
 
5
8
  module Langulator
6
9
 
7
10
  class << self
8
11
  def compile(options)
9
- Aggregate.from_files(options).compile
12
+ IndividualTranslations.new(options).compile
10
13
  end
11
14
 
12
15
  def decompile(options)
13
- Aggregate.from_file(options).decompile
16
+ AggregateTranslation.new(:location => options[:from]).decompile
14
17
  end
15
18
  end
16
19
 
@@ -0,0 +1,67 @@
1
+ module Langulator
2
+ class AggregateTranslation < Translation
3
+
4
+ attr_writer :individual_translations
5
+
6
+ def languages
7
+ @languages ||= detect_languages
8
+ end
9
+
10
+ def decompile
11
+ individual_translations.each(&:write)
12
+ end
13
+
14
+ def individual_translations
15
+ @individual_translations ||= extract_individual_translations
16
+ end
17
+
18
+ private
19
+
20
+ def extract_individual_translations
21
+ i18ns = IndividualTranslations.new
22
+ languages.each do |language|
23
+ i18ns << extract_translations(language)
24
+ end
25
+ i18ns
26
+ end
27
+
28
+ def extract_translations(language)
29
+ extract(language, translations).map do |path, values|
30
+ IndividualTranslation.new(:path => path, :base_filename => language, :translations => values)
31
+ end
32
+ end
33
+
34
+ def extract(language, aggregate)
35
+ separated = {}
36
+ aggregate.keys.each do |key|
37
+ values = aggregate[key]
38
+ if translations?(values)
39
+ separated[key] = values[language]
40
+ else
41
+ separated[key] = extract(language, values)
42
+ end
43
+ end
44
+ separated
45
+ end
46
+
47
+ def translations?(values)
48
+ !values.keys.select {|key| languages.include?(key) }.empty?
49
+ end
50
+
51
+ def detect_languages(dictionary = translations, detected = [])
52
+ dictionary.each do |key, values|
53
+ if values.is_a?(Hash)
54
+ detect_languages(values, detected)
55
+ else
56
+ language = key.to_sym
57
+ # The languages are always listed together.
58
+ # If we're seeing a duplicate language, then we're done
59
+ return detected if detected.include?(language)
60
+
61
+ detected << language
62
+ end
63
+ end
64
+ return detected
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,30 @@
1
+ require 'thor'
2
+
3
+ module Langulator
4
+
5
+ class CLI < Thor
6
+
7
+ desc "compile", "Combine individual i18n files into a single aggregate"
8
+ method_option :source_language, :type => :string, :aliases => "-s", :required => true, :desc => "Which i18n files have the required keys? Expects i18n files to be in format <language>.yml"
9
+ method_option :target_languages, :type => :array, :aliases => "-t", :required => true, :desc => "The target languages"
10
+ method_option :base_path, :type => :string, :aliases => "-p", :default => '**/i18n/', :desc => "The path used to glob for .yml files matching the desired languages"
11
+ method_option :file, :type => :string, :aliases => "-f", :default => './tmp/translations.yml', :desc => "The target path of the aggregate file"
12
+ def compile
13
+ compile_options = {
14
+ :source_language => options[:source_language].to_sym,
15
+ :target_languages => options[:target_languages].map(&:to_sym),
16
+ :base_path => options[:base_path],
17
+ :to => options[:file]
18
+ }
19
+ Langulator.compile(compile_options)
20
+ end
21
+
22
+ method_option :file, :type => :string, :aliases => "-f", :default => './tmp/translations.yml', :desc => "The path of the aggregate file"
23
+ desc "decompile", "Split an aggregate into its respective i18n files"
24
+ def decompile
25
+ Langulator.decompile(:from => options[:file])
26
+ end
27
+
28
+ end
29
+ end
30
+
@@ -0,0 +1,8 @@
1
+ module Langulator
2
+ class IndividualTranslation < Translation
3
+
4
+ def language
5
+ @language ||= filename.gsub(".yml", '').to_sym
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,115 @@
1
+ module Langulator
2
+ class IndividualTranslations
3
+ include Enumerable
4
+
5
+ attr_reader :translations, :aggregate_location, :base_path, :source_language, :target_languages
6
+ def initialize(options = {})
7
+ @aggregate_location = options[:to]
8
+ @base_path = options[:base_path]
9
+ @source_language = options[:source_language]
10
+ @target_languages = options[:target_languages]
11
+ end
12
+
13
+ def each
14
+ translations.each { |translation| yield translation }
15
+ end
16
+
17
+ def compile
18
+ aggregate.write
19
+ end
20
+
21
+ def <<(*objects)
22
+ @translations ||= []
23
+ objects.flatten.each do |obj|
24
+ translations << obj
25
+ end
26
+ end
27
+
28
+ def in(*languages)
29
+ select {|translation| languages.include?(translation.language)}
30
+ end
31
+
32
+ def source_translations
33
+ return [] unless source_language
34
+
35
+ self.in(source_language)
36
+ end
37
+
38
+ def target_translations
39
+ return [] unless target_languages
40
+
41
+ self.in(*target_languages)
42
+ end
43
+
44
+ def translations
45
+ @translations ||= load_translations
46
+ end
47
+
48
+ def aggregate
49
+ @aggregate ||= combine
50
+ end
51
+
52
+ private
53
+
54
+ def load_translations
55
+ i18ns = []
56
+ [source_language, *target_languages].each do |language|
57
+ paths.each do |path|
58
+ i18ns << IndividualTranslation.new(:path => path, :base_filename => language)
59
+ end
60
+ end
61
+ i18ns
62
+ end
63
+
64
+ def paths
65
+ @paths ||= locations.map {|location| location.gsub("#{source_language}.yml", '')}
66
+ end
67
+
68
+ def locations
69
+ @locations ||= Dir.glob("#{base_path}#{source_language}.yml")
70
+ end
71
+
72
+ def combine
73
+ dictionary = initialize_aggregate
74
+
75
+ target_translations.each do |i18n|
76
+ dictionary[i18n.path] = insert(i18n.language, i18n.translations, dictionary[i18n.path])
77
+ end
78
+
79
+ AggregateTranslation.new(:location => aggregate_location, :translations => dictionary)
80
+ end
81
+
82
+ def initialize_aggregate
83
+ dict = {}
84
+ source_translations.each do |i18n|
85
+ dict[i18n.path] = remap(i18n.language, i18n.translations)
86
+ end
87
+ dict
88
+ end
89
+
90
+ def remap(language, source)
91
+ target = {}
92
+ source.each do |key, value|
93
+ target[key] ||= {}
94
+ if value.is_a?(Hash)
95
+ target[key] = remap(language, value)
96
+ else
97
+ target[key][language] = value
98
+ end
99
+ end
100
+ target
101
+ end
102
+
103
+ def insert(language, source, target)
104
+ target.dup.each do |key, value|
105
+ if value.is_a?(Hash)
106
+ insert(language, (source || {})[key], value)
107
+ else
108
+ target[language] = source
109
+ end
110
+ end
111
+ target
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,44 @@
1
+ module Langulator
2
+ class Translation
3
+
4
+ attr_reader :location
5
+ def initialize(options = {})
6
+ @path = options[:path]
7
+ if options[:base_filename]
8
+ @filename = "#{options[:base_filename]}.yml"
9
+ end
10
+ @location = options[:location] || "#{@path}#{@filename}"
11
+ @translations = options[:translations]
12
+ end
13
+
14
+ def path
15
+ @path ||= location[/^(.*\/)/, 0] || "./"
16
+ end
17
+
18
+ def filename
19
+ @filename ||= location.gsub(path, '')
20
+ end
21
+
22
+ def translations
23
+ unless @translations
24
+ begin
25
+ @translations = read
26
+ rescue Errno::ENOENT => e
27
+ @translations = {}
28
+ end
29
+ end
30
+ @translations
31
+ end
32
+
33
+ def read
34
+ YAML.load(File.read(location))
35
+ end
36
+
37
+ def write
38
+ File.open(location, 'w:utf-8') do |file|
39
+ file.write translations.to_yaml
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ require 'langulator/translation'
2
+ require 'langulator/aggregate_translation'
3
+ require 'langulator/individual_translation'
4
+ require 'langulator/individual_translations'
5
+ require 'translation_interface'
6
+
7
+ describe Langulator::AggregateTranslation do
8
+ subject { Langulator::AggregateTranslation.new(:location => 'spec/fixtures/translations.yml') }
9
+
10
+ it_behaves_like "a translation"
11
+
12
+ its(:languages) { should eq([:klingon, :lolcode]) }
13
+
14
+ let(:combined) do
15
+ {
16
+ "spec/fixtures/" => {
17
+ "words" => {
18
+ "affirmative" => {
19
+ :klingon => "HISlaH",
20
+ :lolcode => "YA RLY"
21
+ },
22
+ "negative" => {
23
+ :klingon => "ghobe'",
24
+ :lolcode => "NO WAI"
25
+ },
26
+ "hello" => {
27
+ :klingon => "nuqneH",
28
+ :lolcode => "O HAI"
29
+ }
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ let(:klingon) do
36
+ dictionary = {"words" => {"affirmative" => "HISlaH", "negative" => "ghobe'", "hello" => "nuqneH"}}
37
+ Langulator::IndividualTranslation.new(:location => "spec/fixtures/klingon.yml", :translations => dictionary)
38
+ end
39
+
40
+ let(:lolcode) do
41
+ dictionary = {"words" => {"affirmative" => "YA RLY", "negative" => "NO WAI", "hello" => "O HAI"}}
42
+ Langulator::IndividualTranslation.new(:location => "spec/fixtures/lolcode.yml", :translations => dictionary)
43
+ end
44
+
45
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ some:
3
+ stuff: yadda yadda
@@ -0,0 +1,13 @@
1
+ require 'langulator/translation'
2
+ require 'langulator/individual_translation'
3
+ require 'translation_interface'
4
+
5
+ describe Langulator::IndividualTranslation do
6
+
7
+ subject { Langulator::IndividualTranslation.new(:location => 'spec/fixtures/english.yml') }
8
+
9
+ it_behaves_like "a translation"
10
+
11
+ its(:language) { should eq(:english) }
12
+
13
+ end
@@ -0,0 +1,58 @@
1
+ require 'langulator/translation'
2
+ require 'langulator/individual_translation'
3
+ require 'langulator/individual_translations'
4
+
5
+ describe Langulator::IndividualTranslations do
6
+
7
+ describe "collection" do
8
+ subject { Langulator::IndividualTranslations.new(:source_language => :en, :target_languages => [:fr]) }
9
+
10
+ let(:english) { stub(:english, :language => :en, :translations => {"stuff" => "whatever"}) }
11
+ let(:english2) { stub(:english2, :language => :en, :translations => {"more" => "stuff"}) }
12
+ let(:french) { stub(:french, :language => :fr, :translations => {"truc" => "machin"}) }
13
+ let(:french2) { stub(:french2, :language => :fr, :translations => {"autre" => "bidule"}) }
14
+
15
+ before(:each) do
16
+ subject << english
17
+ subject << english2
18
+ subject << french
19
+ subject << french2
20
+ end
21
+
22
+ it "can accumulate many at a time" do
23
+ subject << [stub(:language => :nl), stub(:language => :cz)]
24
+ subject.map(&:language).uniq.should eq([:en, :fr, :nl, :cz])
25
+ end
26
+
27
+ it "can iterate through them" do
28
+ subject.map(&:language).should eq([:en, :en, :fr, :fr])
29
+ end
30
+
31
+ it "selects a language" do
32
+ subject.in(:en).should eq([english, english2])
33
+ end
34
+
35
+ it "provides the source_translations" do
36
+ subject.source_translations.should eq([english, english2])
37
+ end
38
+
39
+ it "provides the target translations" do
40
+ subject.target_translations.should eq([french, french2])
41
+ end
42
+ end
43
+
44
+ describe "loading from a path" do
45
+ let(:options) { {:source_language => :english, :target_languages => [:norsk], :base_path => 'spec/**/', :to => 'spec/fixtures/out.yml'} }
46
+ subject { Langulator::IndividualTranslations.new(options) }
47
+
48
+ its(:source_language) { should eq(:english) }
49
+ its(:target_languages) { should eq([:norsk]) }
50
+ its(:base_path) { should eq('spec/**/') }
51
+ its(:aggregate_location) { should eq('spec/fixtures/out.yml') }
52
+
53
+ it "loads the actual individual translations" do
54
+ subject.map(&:language).uniq.should eq([:english, :norsk])
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ shared_examples_for "a translation" do
2
+ it { subject.should respond_to(:filename) }
3
+ it { subject.should respond_to(:location) }
4
+ it { subject.should respond_to(:path) }
5
+ it { subject.should respond_to(:translations) }
6
+ it { subject.should respond_to(:write) }
7
+ end
@@ -0,0 +1,59 @@
1
+ require 'yaml'
2
+ require 'langulator/translation'
3
+ require 'translation_interface'
4
+
5
+ describe Langulator::Translation do
6
+ subject { Langulator::Translation.new(:location => 'spec/fixtures/whatever.yml') }
7
+
8
+ it_behaves_like 'a translation'
9
+
10
+ its(:path) { should eq('spec/fixtures/') }
11
+ its(:location) { should eq('spec/fixtures/whatever.yml') }
12
+ its(:filename) { should eq('whatever.yml') }
13
+
14
+ describe "with a hyphen" do
15
+ subject { Langulator::Translation.new(:location => 'spec/fixtures/nb-no.yml') }
16
+ its(:filename) { should eq('nb-no.yml') }
17
+ end
18
+
19
+ describe "in the current directory" do
20
+ subject { Langulator::Translation.new(:location => 'whatever.yml') }
21
+ its(:path) { should eq('./') }
22
+ its(:filename) { should eq('whatever.yml') }
23
+ end
24
+
25
+ describe 'with a missing file' do
26
+ subject { Langulator::Translation.new(:location => 'spec/fixtures/does_not_exist.yml') }
27
+ its(:path) { should eq('spec/fixtures/') }
28
+ its(:filename) { should eq('does_not_exist.yml') }
29
+ its(:translations) { should eq({}) }
30
+ end
31
+
32
+ describe "with a path and base filename" do
33
+ subject { Langulator::Translation.new(:path => 'spec/fixtures/', :base_filename => 'whatever') }
34
+ its(:path) { should eq('spec/fixtures/') }
35
+ its(:location) { should eq('spec/fixtures/whatever.yml') }
36
+ its(:filename) { should eq('whatever.yml') }
37
+ end
38
+
39
+ its(:translations) { should eq({"some" => {"stuff" => "yadda yadda"}}) }
40
+
41
+ describe "with provided translations" do
42
+ subject { Langulator::Translation.new(:location => './nonexistant.yml', :translations => {"do" => "something"}) }
43
+ its(:translations) { should eq({"do" => "something"}) }
44
+ end
45
+
46
+ describe "#write" do
47
+ let(:location) { 'spec/fixtures/xy.yml' }
48
+ subject { Langulator::Translation.new(:location => location, :translations => {"some" => "stuff"}) }
49
+
50
+ before(:each) { FileUtils.rm(location) if File.exists?(location) }
51
+ after(:each) { FileUtils.rm(location) if File.exists?(location) }
52
+
53
+ it "outputs the content to the given location" do
54
+ subject.write
55
+
56
+ Langulator::Translation.new(:location => location).translations.should eq({"some" => "stuff"})
57
+ end
58
+ end
59
+ 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
4
+ version: 0.0.5
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-05 00:00:00.000000000 Z
12
+ date: 2012-05-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70204764441280 !ruby/object:Gem::Requirement
16
+ requirement: &70268228073700 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,29 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70204764441280
24
+ version_requirements: *70268228073700
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ requirement: &70268228072740 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70268228072740
36
+ - !ruby/object:Gem::Dependency
37
+ name: thor
38
+ requirement: &70268228071780 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70268228071780
25
47
  description: Manage, maintain, and munge your i18n files.
26
48
  email:
27
49
  - katrina.owen@gmail.com
@@ -36,16 +58,33 @@ files:
36
58
  - README.md
37
59
  - Rakefile
38
60
  - bin/langulator
61
+ - features/compile_aggregate.feature
62
+ - features/decompile_aggregate.feature
63
+ - features/fixtures/en.yml
64
+ - features/fixtures/fr.yml
65
+ - features/fixtures/in.yml
66
+ - features/fixtures/lang/en.yml
67
+ - features/fixtures/lang/fr.yml
68
+ - features/step_definitions/aggregate_steps.rb
69
+ - features/support/env.rb
70
+ - features/support/hooks.rb
39
71
  - langulator.gemspec
40
72
  - lib/langulator.rb
41
- - lib/langulator/aggregate.rb
42
- - lib/langulator/loader.rb
43
- - spec/aggregate_spec.rb
73
+ - lib/langulator/aggregate_translation.rb
74
+ - lib/langulator/cli.rb
75
+ - lib/langulator/individual_translation.rb
76
+ - lib/langulator/individual_translations.rb
77
+ - lib/langulator/translation.rb
78
+ - spec/aggregate_translation_spec.rb
44
79
  - spec/fixtures/english.yml
45
80
  - spec/fixtures/lang/english.yml
46
81
  - spec/fixtures/norsk.yml
47
82
  - spec/fixtures/translations.yml
48
- - spec/loader_spec.rb
83
+ - spec/fixtures/whatever.yml
84
+ - spec/individual_translation_spec.rb
85
+ - spec/individual_translations_spec.rb
86
+ - spec/translation_interface.rb
87
+ - spec/translation_spec.rb
49
88
  homepage: http://github.com/kytrinyx/langulator
50
89
  licenses: []
51
90
  post_install_message:
@@ -71,9 +110,23 @@ signing_key:
71
110
  specification_version: 3
72
111
  summary: Tasks to keep your i18n files managable.
73
112
  test_files:
74
- - spec/aggregate_spec.rb
113
+ - features/compile_aggregate.feature
114
+ - features/decompile_aggregate.feature
115
+ - features/fixtures/en.yml
116
+ - features/fixtures/fr.yml
117
+ - features/fixtures/in.yml
118
+ - features/fixtures/lang/en.yml
119
+ - features/fixtures/lang/fr.yml
120
+ - features/step_definitions/aggregate_steps.rb
121
+ - features/support/env.rb
122
+ - features/support/hooks.rb
123
+ - spec/aggregate_translation_spec.rb
75
124
  - spec/fixtures/english.yml
76
125
  - spec/fixtures/lang/english.yml
77
126
  - spec/fixtures/norsk.yml
78
127
  - spec/fixtures/translations.yml
79
- - spec/loader_spec.rb
128
+ - spec/fixtures/whatever.yml
129
+ - spec/individual_translation_spec.rb
130
+ - spec/individual_translations_spec.rb
131
+ - spec/translation_interface.rb
132
+ - spec/translation_spec.rb
@@ -1,124 +0,0 @@
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
@@ -1,44 +0,0 @@
1
- module Langulator
2
- class Loader
3
-
4
- attr_reader :base_path, :languages
5
- def initialize(options = {})
6
- @base_path = options[:base_path]
7
- @languages = [options[:source_language]] + options[:target_languages]
8
- end
9
-
10
- def paths
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
16
- end
17
-
18
- def translations
19
- translations = {}
20
- languages.each do |language|
21
- translations[language] = load_translations(language)
22
- end
23
- translations
24
- end
25
-
26
- def load_translations(language)
27
- translations = {}
28
- paths.each do |path|
29
- translations[path] = read_translations(path, language)
30
- end
31
- translations
32
- end
33
-
34
- def read_translations(path, language)
35
- file = "#{path}#{language}.yml"
36
- if File.exists?(file)
37
- YAML.load(File.read(file))
38
- else
39
- {}
40
- end
41
- end
42
-
43
- end
44
- end
@@ -1,141 +0,0 @@
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
@@ -1,46 +0,0 @@
1
- # encoding: utf-8
2
- require 'yaml'
3
- require 'langulator/loader'
4
-
5
- describe Langulator::Loader do
6
-
7
- let(:english) do
8
- {
9
- "spec/fixtures/" => {
10
- "food" => {
11
- "breakfast" => "yoghurt",
12
- "lunch" => "sandwich",
13
- "dinner" => {"main_course" => "steak", "side" => "baked potato", "desert" => "chocolate mousse"}
14
- }
15
- },
16
- "spec/fixtures/lang/" => {
17
- "volume" => {
18
- "sound" => "loud",
19
- "liquid" => "sloshing",
20
- "hair" => "because I'm worth it"
21
- }
22
- }
23
- }
24
- end
25
-
26
- let(:norwegian) do
27
- {
28
- "spec/fixtures/" => {
29
- "food" => {
30
- "lunch" => "smørbrød"
31
- }
32
- },
33
- "spec/fixtures/lang/" => {}
34
- }
35
- end
36
-
37
- let(:french) do
38
- {
39
- "spec/fixtures/" => {},
40
- "spec/fixtures/lang/" => {}
41
- }
42
- end
43
-
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}) }
46
- end