translations 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +1 -0
- data/bin/translations +6 -0
- data/lib/translations.rb +5 -0
- data/lib/translations/cli.rb +147 -0
- data/lib/translations/serializer.rb +22 -0
- data/lib/translations/translation.rb +94 -0
- data/lib/translations/translation_collection.rb +34 -0
- data/lib/translations/version.rb +3 -0
- data/spec/fixtures/missing_keys/de.yml +2 -0
- data/spec/fixtures/missing_keys/en.yml +6 -0
- data/spec/fixtures/working/de.yml +2 -0
- data/spec/fixtures/working/en.yml +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/translations/serializer_spec.rb +76 -0
- data/spec/translations/translation_collection_spec.rb +47 -0
- data/spec/translations/translation_spec.rb +119 -0
- data/translations.gemspec +24 -0
- metadata +126 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-1.9.3-p385@translations
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
translations (0.0.1)
|
|
5
|
+
thor (~> 0.17.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
diff-lcs (1.1.3)
|
|
11
|
+
predicated (0.2.6)
|
|
12
|
+
rspec (2.12.0)
|
|
13
|
+
rspec-core (~> 2.12.0)
|
|
14
|
+
rspec-expectations (~> 2.12.0)
|
|
15
|
+
rspec-mocks (~> 2.12.0)
|
|
16
|
+
rspec-core (2.12.2)
|
|
17
|
+
rspec-expectations (2.12.1)
|
|
18
|
+
diff-lcs (~> 1.1.3)
|
|
19
|
+
rspec-mocks (2.12.2)
|
|
20
|
+
ruby2ruby (2.0.3)
|
|
21
|
+
ruby_parser (~> 3.1)
|
|
22
|
+
sexp_processor (~> 4.0)
|
|
23
|
+
ruby_parser (3.1.1)
|
|
24
|
+
sexp_processor (~> 4.1)
|
|
25
|
+
sexp_processor (4.1.4)
|
|
26
|
+
thor (0.17.0)
|
|
27
|
+
wrong (0.7.0)
|
|
28
|
+
diff-lcs (~> 1.1.2)
|
|
29
|
+
predicated (~> 0.2.6)
|
|
30
|
+
ruby2ruby (>= 2.0.1)
|
|
31
|
+
ruby_parser (>= 3.0.1)
|
|
32
|
+
sexp_processor (>= 4.0)
|
|
33
|
+
|
|
34
|
+
PLATFORMS
|
|
35
|
+
ruby
|
|
36
|
+
|
|
37
|
+
DEPENDENCIES
|
|
38
|
+
rspec (~> 2.12.0)
|
|
39
|
+
translations!
|
|
40
|
+
wrong (~> 0.7.0)
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Marten Lienen
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Translations
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
Edit your translation files in a synchronized way.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Naming
|
|
8
|
+
------
|
|
9
|
+
|
|
10
|
+
1. One translation is your `master`. This file is always complete and serves as a reference for the `slaves`.
|
|
11
|
+
2. All translations that are not `master` are `slave`s. This means that they may not be up-to-date and can be synchronized
|
|
12
|
+
with the master using `translate`.
|
|
13
|
+
|
|
14
|
+
The default master is `en`, but you may alter it using the `-m <locale>` switch.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Usage
|
|
18
|
+
-----
|
|
19
|
+
|
|
20
|
+
Currently there are 6 commands to alter your translation files:
|
|
21
|
+
|
|
22
|
+
- `add`: Add a new key to all your files
|
|
23
|
+
- `update`: Update a key in a single file
|
|
24
|
+
- `change`: Change the meaning of a key in a way that requires a re-translation for all locales
|
|
25
|
+
- `move`: Move a key around
|
|
26
|
+
- `remove`: Remove a key from all files
|
|
27
|
+
- `translate`: Translate all keys that are missing from a given file
|
|
28
|
+
|
|
29
|
+
For more info just run `translations help <command>`.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Requirements
|
|
33
|
+
------------
|
|
34
|
+
|
|
35
|
+
- All your translations are stored in YAML format and one file per locale called `<locale>.yml`
|
|
36
|
+
- Optimally you work in a rails project, e.g. your translations are stored in `config/locales`, but you may alter this location with `-d <directory>`
|
|
37
|
+
- Optimally you have a master
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Missing/Coming Features
|
|
41
|
+
----------------
|
|
42
|
+
|
|
43
|
+
The following features (or whatever comes to your mind) will probably be implemented in the next time:
|
|
44
|
+
|
|
45
|
+
- Support for Pluralizations
|
|
46
|
+
- Support for [Cascading](http://svenfuchs.com/2011/2/11/organizing-translations-with-i18n-cascade-and-i18n-missingtranslations)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Installation
|
|
50
|
+
------------
|
|
51
|
+
|
|
52
|
+
Add this line to your application's Gemfile:
|
|
53
|
+
|
|
54
|
+
gem 'translations'
|
|
55
|
+
|
|
56
|
+
And then execute:
|
|
57
|
+
|
|
58
|
+
$ bundle
|
|
59
|
+
|
|
60
|
+
Or install it yourself as:
|
|
61
|
+
|
|
62
|
+
$ gem install translations
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Contributing
|
|
66
|
+
------------
|
|
67
|
+
|
|
68
|
+
1. Fork it
|
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
70
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
72
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/translations
ADDED
data/lib/translations.rb
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
require "translations/serializer"
|
|
4
|
+
|
|
5
|
+
module Translations
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
class_option :directory, aliases: ["-d"], default: "config/locales", type: :string, desc: "Directory containing the translations"
|
|
8
|
+
class_option :master, aliases: ["-m"], default: "en", type: :string, desc: "The master locale"
|
|
9
|
+
|
|
10
|
+
desc "translate LOCALE [KEYS]", "Translate the KEYS into the given LOCALE"
|
|
11
|
+
long_desc <<-DESC
|
|
12
|
+
Translate KEYS from the master to LOCALE. If you omit KEYS it will instead ask you to translate all keys that exist
|
|
13
|
+
in master but not in LOCALE.
|
|
14
|
+
DESC
|
|
15
|
+
def translate locale, *keys
|
|
16
|
+
translations = serializer.translations
|
|
17
|
+
|
|
18
|
+
translation = translations.for_locale(locale)
|
|
19
|
+
|
|
20
|
+
if keys.length == 0
|
|
21
|
+
keys = translation.missing_keys_from_translation(translations.master)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
master = translations.master
|
|
25
|
+
|
|
26
|
+
keys.each do |key|
|
|
27
|
+
say key
|
|
28
|
+
say "#{master.locale}: #{master[key]}"
|
|
29
|
+
|
|
30
|
+
translations.slaves.each do |translation|
|
|
31
|
+
answer = ask "#{translation.locale}?"
|
|
32
|
+
|
|
33
|
+
if answer.length > 0
|
|
34
|
+
translation[key] = answer
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
serializer.save translations
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "remove KEYS", "Remove KEYS from all locales"
|
|
45
|
+
def remove *keys
|
|
46
|
+
translations = serializer.translations
|
|
47
|
+
|
|
48
|
+
keys.each do |key|
|
|
49
|
+
translations.remove key
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
serializer.save translations
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "move FROM TO", "Move a translation from FROM to TO"
|
|
56
|
+
def move from, to
|
|
57
|
+
translations = serializer.translations
|
|
58
|
+
|
|
59
|
+
translations.move from, to
|
|
60
|
+
|
|
61
|
+
serializer.save translations
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
desc "add KEY", "Add a new KEY to all translations"
|
|
65
|
+
long_desc <<-DESC
|
|
66
|
+
Add a key and provide translations for as many locales as you can.
|
|
67
|
+
DESC
|
|
68
|
+
def add key
|
|
69
|
+
translations = serializer.translations
|
|
70
|
+
|
|
71
|
+
answer = ask "#{translations.master.locale}?"
|
|
72
|
+
|
|
73
|
+
if answer.length > 0
|
|
74
|
+
translations.master[key] = answer
|
|
75
|
+
else
|
|
76
|
+
say "You have to provide a translation for master"
|
|
77
|
+
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
translations.slaves.each do |translation|
|
|
82
|
+
answer = ask "#{translation.locale}?"
|
|
83
|
+
|
|
84
|
+
if answer.length > 0
|
|
85
|
+
translation[key] = answer
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
serializer.save translations
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
desc "change KEY", "Change the meaning of KEY"
|
|
93
|
+
long_desc <<-DESC
|
|
94
|
+
Changes the meaning of KEY in a way that requires re-translation to all locales. Therefore the key will be removed
|
|
95
|
+
from all locales for which you cannot provide a translation, so that it can be fixed using the translate command.
|
|
96
|
+
DESC
|
|
97
|
+
def change key
|
|
98
|
+
translations = serializer.translations
|
|
99
|
+
|
|
100
|
+
say "#{translations.master.locale}: #{translations.master[key]}"
|
|
101
|
+
answer = ask "#{translations.master.locale}?"
|
|
102
|
+
|
|
103
|
+
if answer.length > 0
|
|
104
|
+
translations.master[key] = answer
|
|
105
|
+
else
|
|
106
|
+
say "You have to provide a translation for master"
|
|
107
|
+
|
|
108
|
+
return
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
translations.slaves.each do |translation|
|
|
112
|
+
say "#{translation.locale}: #{translation[key]}"
|
|
113
|
+
answer = ask "#{translation.locale}?"
|
|
114
|
+
|
|
115
|
+
if answer.length > 0
|
|
116
|
+
translation[key] = answer
|
|
117
|
+
else
|
|
118
|
+
translation.remove key
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
serializer.save translations
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
desc "update LOCALE KEY", "Update KEY in a specific LOCALE without changing it's meaning"
|
|
126
|
+
long_desc <<-DESC
|
|
127
|
+
Just update KEY in LOCALE without altering any other locales.
|
|
128
|
+
DESC
|
|
129
|
+
def update locale, key
|
|
130
|
+
translations = serializer.translations
|
|
131
|
+
translation = translations.for_locale(locale)
|
|
132
|
+
|
|
133
|
+
say "#{locale}: #{translation[key]}"
|
|
134
|
+
answer = ask "#{locale}?"
|
|
135
|
+
|
|
136
|
+
translation[key] = answer
|
|
137
|
+
|
|
138
|
+
serializer.save translations
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
no_tasks do
|
|
142
|
+
def serializer
|
|
143
|
+
@serializer ||= Serializer.new options.directory, options.master
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "translations/translation_collection"
|
|
2
|
+
|
|
3
|
+
module Translations
|
|
4
|
+
class Serializer
|
|
5
|
+
def initialize directory, master
|
|
6
|
+
@directory = directory
|
|
7
|
+
@master = master
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def translations
|
|
11
|
+
TranslationCollection.new Dir["#{@directory}/*"].map { |file| Translation.new YAML.load_file(file) }, @master
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def save translations
|
|
15
|
+
translations.each do |translation|
|
|
16
|
+
File.open File.join(@directory, "#{translation.locale}.yml"), "w" do |file|
|
|
17
|
+
file.write translation.to_hash.to_yaml
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Translations
|
|
2
|
+
class Translation
|
|
3
|
+
class InvalidKeyException < ArgumentError
|
|
4
|
+
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
attr_reader :locale
|
|
8
|
+
|
|
9
|
+
def initialize translations
|
|
10
|
+
@locale = translations.keys.first
|
|
11
|
+
@translations = translations[@locale]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def keys
|
|
15
|
+
keys_of_nested_hash @translations
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def missing_keys_from_translation translation
|
|
19
|
+
translation.keys - keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def has_key? key
|
|
23
|
+
translation = key.split(".").inject(@translations) do |translation, key|
|
|
24
|
+
if translation.nil?
|
|
25
|
+
nil
|
|
26
|
+
else
|
|
27
|
+
translation[key]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
translation != nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def [] key
|
|
35
|
+
key.split(".").inject(@translations) do |translation, key|
|
|
36
|
+
if translation.nil?
|
|
37
|
+
raise InvalidKeyException
|
|
38
|
+
else
|
|
39
|
+
translation[key]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def []= key, value
|
|
45
|
+
parts = key.split(".")
|
|
46
|
+
|
|
47
|
+
hash = parts.slice(0, parts.length - 1).inject(@translations) do |translation, key|
|
|
48
|
+
if !translation.has_key? key
|
|
49
|
+
translation[key] = { }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if !translation[key].is_a? Hash
|
|
53
|
+
translation[key] = { }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
translation[key]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
hash[parts.last] = value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def remove key
|
|
63
|
+
parts = key.split(".")
|
|
64
|
+
last_part = parts.pop
|
|
65
|
+
|
|
66
|
+
parent = parts.inject(@translations) do |translation, key|
|
|
67
|
+
translation[key]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
parent.delete last_part
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def move from, to
|
|
74
|
+
self[to] = self[from]
|
|
75
|
+
|
|
76
|
+
remove from
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_hash
|
|
80
|
+
{ @locale => @translations }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
def keys_of_nested_hash hash
|
|
85
|
+
hash.map do |key, value|
|
|
86
|
+
if value.is_a? Hash
|
|
87
|
+
keys_of_nested_hash(value).map { |nested_key| "#{key}.#{nested_key}" }
|
|
88
|
+
else
|
|
89
|
+
key
|
|
90
|
+
end
|
|
91
|
+
end.flatten 1
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "translations/translation"
|
|
2
|
+
|
|
3
|
+
module Translations
|
|
4
|
+
class TranslationCollection
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
attr_reader :master
|
|
8
|
+
|
|
9
|
+
def initialize translations, master
|
|
10
|
+
@translations = translations
|
|
11
|
+
@master = @translations.select { |translation| translation.locale == master }.first
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def each
|
|
15
|
+
@translations.each { |translation| yield translation }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def for_locale locale
|
|
19
|
+
@translations.select { |translation| translation.locale == locale }.first
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def slaves
|
|
23
|
+
@translations.reject { |translation| translation == master }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove key
|
|
27
|
+
@translations.each { |translation| translation.remove key }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def move from, to
|
|
31
|
+
@translations.each { |translation| translation.move from, to }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "wrong/adapters/rspec"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "translations/serializer"
|
|
2
|
+
|
|
3
|
+
describe Translations::Serializer do
|
|
4
|
+
describe "#translations" do
|
|
5
|
+
let(:serializer) { Translations::Serializer.new("spec/fixtures/working", "en") }
|
|
6
|
+
|
|
7
|
+
it "should parse YAML files into a TranslationCollection" do
|
|
8
|
+
assert { serializer.translations.is_a? Translations::TranslationCollection }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should create a translation for every found locale" do
|
|
12
|
+
locales = serializer.translations.map(&:locale)
|
|
13
|
+
|
|
14
|
+
assert { locales.== ["en", "de"] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should set the master correctly" do
|
|
18
|
+
assert { serializer.translations.master.locale == "en" }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#save" do
|
|
23
|
+
before :each do
|
|
24
|
+
if File.exists? "/tmp/de.yml"
|
|
25
|
+
File.delete "/tmp/de.yml"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "should write the YAML serialization of the translations into the files" do
|
|
30
|
+
translation = Translations::Translation.new({
|
|
31
|
+
"de" => {
|
|
32
|
+
"save" => "Speichern"
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
translations = Translations::TranslationCollection.new [translation], "de"
|
|
37
|
+
|
|
38
|
+
serializer = Translations::Serializer.new "/tmp/", "de"
|
|
39
|
+
|
|
40
|
+
serializer.save translations
|
|
41
|
+
|
|
42
|
+
assert { File.exists?("/tmp/de.yml") }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "should output the keys in alphabetical order" do
|
|
46
|
+
translations_hash = {
|
|
47
|
+
"en" => {
|
|
48
|
+
"animals" => {
|
|
49
|
+
"mammals" => {
|
|
50
|
+
"elephant" => "Elephant",
|
|
51
|
+
"human" => "Human"
|
|
52
|
+
},
|
|
53
|
+
"fish" => {
|
|
54
|
+
"trout" => "Trout",
|
|
55
|
+
"bass" => "Bass",
|
|
56
|
+
"mackerel" => "Mackerel"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"actions" => {
|
|
60
|
+
"save" => "Save",
|
|
61
|
+
"add" => "Add",
|
|
62
|
+
"delete" => "Delete"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
translation = Translations::Translation.new(translations_hash)
|
|
68
|
+
translations = Translations::TranslationCollection.new [translation], "en"
|
|
69
|
+
serializer = Translations::Serializer.new "/tmp/", "en"
|
|
70
|
+
|
|
71
|
+
serializer.save translations
|
|
72
|
+
|
|
73
|
+
assert { YAML.load_file("/tmp/en.yml") == translations_hash }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "translations/translation_collection"
|
|
2
|
+
|
|
3
|
+
describe Translations::TranslationCollection do
|
|
4
|
+
let(:translation_de) { Translations::Translation.new({ "de" => { "save" => "Speichern" } }) }
|
|
5
|
+
let(:translation_en) { Translations::Translation.new({ "en" => { "save" => "Save" } }) }
|
|
6
|
+
let(:translations) { Translations::TranslationCollection.new [translation_de, translation_en], "en" }
|
|
7
|
+
|
|
8
|
+
it "should be Enumerable" do
|
|
9
|
+
enumerated_translations = []
|
|
10
|
+
|
|
11
|
+
translations.each { |translation| enumerated_translations << translation }
|
|
12
|
+
|
|
13
|
+
assert { enumerated_translations == [translation_de, translation_en] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should have a master locale" do
|
|
17
|
+
assert { translations.master == translation_en }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should expose all translations that are not master as slaves" do
|
|
21
|
+
assert { translations.slaves == [translation_de] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#for_locale" do
|
|
25
|
+
it "should return the Translation for a locale" do
|
|
26
|
+
assert { translations.for_locale("de") == translation_de }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#remove" do
|
|
31
|
+
it "should remove the given key from all translations" do
|
|
32
|
+
translations.remove "save"
|
|
33
|
+
|
|
34
|
+
assert { translation_de.has_key?("save") == false }
|
|
35
|
+
assert { translation_en.has_key?("save") == false }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#move" do
|
|
40
|
+
it "should move the key in all translations" do
|
|
41
|
+
translations.move "save", "x_save"
|
|
42
|
+
|
|
43
|
+
assert { translation_de["x_save"] == "Speichern" }
|
|
44
|
+
assert { translation_en["x_save"] == "Save" }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require "translations/translation"
|
|
2
|
+
|
|
3
|
+
describe Translations::Translation do
|
|
4
|
+
let(:translation) {
|
|
5
|
+
Translations::Translation.new({
|
|
6
|
+
"en" => {
|
|
7
|
+
"save" => "Save",
|
|
8
|
+
"buttons" => {
|
|
9
|
+
"delete" => "Delete",
|
|
10
|
+
"save" => "Save"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
it "should have a locale" do
|
|
17
|
+
assert { translation.locale == "en" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should be able to enumerate it's keys" do
|
|
21
|
+
assert { translation.keys == ["save", "buttons.delete", "buttons.save"] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should be able to compute the missing keys when compared to another translation" do
|
|
25
|
+
translation_de = Translations::Translation.new({
|
|
26
|
+
"de" => {
|
|
27
|
+
"buttons" => {
|
|
28
|
+
"save" => "Speichern"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
translation_de.missing_keys_from_translation(translation).should == ["save", "buttons.delete"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#has_key?" do
|
|
37
|
+
it "should return true if the key exists" do
|
|
38
|
+
assert { translation.has_key?("buttons.delete") == true }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should return false if the key does not exist" do
|
|
42
|
+
assert { translation.has_key?("key.does.not.exist") == false }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "should return false if the key does not exist" do
|
|
46
|
+
assert { translation.has_key?("buttons.xxx") == false }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "#[]" do
|
|
51
|
+
it "should expose translations through their keys" do
|
|
52
|
+
assert { translation["buttons.delete"] == "Delete" }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should raise an exception if the key is not found" do
|
|
56
|
+
assert { rescuing { translation["key.does.not.exist"] }.is_a? Translations::Translation::InvalidKeyException }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "#[]=" do
|
|
61
|
+
it "should let you mutate translations through their keys" do
|
|
62
|
+
translation["buttons.delete"] = "Remove"
|
|
63
|
+
|
|
64
|
+
assert { translation["buttons.delete"] == "Remove" }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should let you create new translations using keys" do
|
|
68
|
+
translation = Translations::Translation.new({
|
|
69
|
+
"en" => { }
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
translation["buttons.delete"] = "Delete"
|
|
73
|
+
|
|
74
|
+
assert { translation["buttons.delete"] == "Delete" }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "should expand a node when you set a key that goes 'through' another translation" do
|
|
78
|
+
translation["save.success"] = "Success"
|
|
79
|
+
|
|
80
|
+
assert { translation["save.success"] == "Success" }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#remove" do
|
|
85
|
+
it "should remove the key" do
|
|
86
|
+
translation.remove "buttons.delete"
|
|
87
|
+
|
|
88
|
+
assert { translation.has_key?("buttons.delete") == false }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "#move" do
|
|
93
|
+
it "should move the key" do
|
|
94
|
+
translation.move "buttons.delete", "actions.crud.delete"
|
|
95
|
+
|
|
96
|
+
assert { translation["actions.crud.delete"] == "Delete" }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should remove the old key" do
|
|
100
|
+
translation.move "buttons.delete", "actions.crud.delete"
|
|
101
|
+
|
|
102
|
+
assert { translation.has_key?("buttons.delete") == false }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "should have a Hash representation" do
|
|
107
|
+
expected = {
|
|
108
|
+
"en" => {
|
|
109
|
+
"save" => "Save",
|
|
110
|
+
"buttons" => {
|
|
111
|
+
"delete" => "Delete",
|
|
112
|
+
"save" => "Save"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
assert { translation.to_hash == expected }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'translations/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "translations"
|
|
8
|
+
gem.version = Translations::VERSION
|
|
9
|
+
gem.authors = ["Marten Lienen"]
|
|
10
|
+
gem.email = ["marten.lienen@gmail.com"]
|
|
11
|
+
gem.description = %q{Manage YAML translations}
|
|
12
|
+
gem.summary = %q{}
|
|
13
|
+
gem.homepage = "https://github.com/CQQL/translations"
|
|
14
|
+
|
|
15
|
+
gem.add_dependency "thor", "~> 0.17.0"
|
|
16
|
+
|
|
17
|
+
gem.add_development_dependency "rspec", "~> 2.12.0"
|
|
18
|
+
gem.add_development_dependency "wrong", "~> 0.7.0"
|
|
19
|
+
|
|
20
|
+
gem.files = `git ls-files`.split($/)
|
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
23
|
+
gem.require_paths = ["lib"]
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: translations
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Marten Lienen
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: thor
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.17.0
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 0.17.0
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rspec
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: 2.12.0
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 2.12.0
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: wrong
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ~>
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.7.0
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 0.7.0
|
|
62
|
+
description: Manage YAML translations
|
|
63
|
+
email:
|
|
64
|
+
- marten.lienen@gmail.com
|
|
65
|
+
executables:
|
|
66
|
+
- translations
|
|
67
|
+
extensions: []
|
|
68
|
+
extra_rdoc_files: []
|
|
69
|
+
files:
|
|
70
|
+
- .gitignore
|
|
71
|
+
- .rspec
|
|
72
|
+
- .ruby-version
|
|
73
|
+
- Gemfile
|
|
74
|
+
- Gemfile.lock
|
|
75
|
+
- LICENSE.txt
|
|
76
|
+
- README.md
|
|
77
|
+
- Rakefile
|
|
78
|
+
- bin/translations
|
|
79
|
+
- lib/translations.rb
|
|
80
|
+
- lib/translations/cli.rb
|
|
81
|
+
- lib/translations/serializer.rb
|
|
82
|
+
- lib/translations/translation.rb
|
|
83
|
+
- lib/translations/translation_collection.rb
|
|
84
|
+
- lib/translations/version.rb
|
|
85
|
+
- spec/fixtures/missing_keys/de.yml
|
|
86
|
+
- spec/fixtures/missing_keys/en.yml
|
|
87
|
+
- spec/fixtures/working/de.yml
|
|
88
|
+
- spec/fixtures/working/en.yml
|
|
89
|
+
- spec/spec_helper.rb
|
|
90
|
+
- spec/translations/serializer_spec.rb
|
|
91
|
+
- spec/translations/translation_collection_spec.rb
|
|
92
|
+
- spec/translations/translation_spec.rb
|
|
93
|
+
- translations.gemspec
|
|
94
|
+
homepage: https://github.com/CQQL/translations
|
|
95
|
+
licenses: []
|
|
96
|
+
post_install_message:
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
none: false
|
|
102
|
+
requirements:
|
|
103
|
+
- - ! '>='
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
none: false
|
|
108
|
+
requirements:
|
|
109
|
+
- - ! '>='
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
requirements: []
|
|
113
|
+
rubyforge_project:
|
|
114
|
+
rubygems_version: 1.8.25
|
|
115
|
+
signing_key:
|
|
116
|
+
specification_version: 3
|
|
117
|
+
summary: ''
|
|
118
|
+
test_files:
|
|
119
|
+
- spec/fixtures/missing_keys/de.yml
|
|
120
|
+
- spec/fixtures/missing_keys/en.yml
|
|
121
|
+
- spec/fixtures/working/de.yml
|
|
122
|
+
- spec/fixtures/working/en.yml
|
|
123
|
+
- spec/spec_helper.rb
|
|
124
|
+
- spec/translations/serializer_spec.rb
|
|
125
|
+
- spec/translations/translation_collection_spec.rb
|
|
126
|
+
- spec/translations/translation_spec.rb
|