translations_sync 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,66 @@
1
+ TranslationsSync
2
+ ================
3
+
4
+ This plugin is intended to help the developer to synchronize the different locales for
5
+ the project that uses yaml based locales, in particular, I18n.
6
+
7
+
8
+ Requirements
9
+ ============
10
+
11
+ gem 'ya2yaml'
12
+
13
+
14
+ Installation
15
+ ============
16
+
17
+ 1. As plugin
18
+ rails plugin install http://github.com/dima4p/translations_sync/
19
+
20
+ 2. As gem
21
+ sudo gem install translations_sync
22
+ translations_sync
23
+
24
+
25
+ Description
26
+ ===========
27
+
28
+ There are two rake tasks implemented in the plugin.
29
+
30
+ 1. rake translations:sync
31
+
32
+ This task generates the files with missing translations in the directory
33
+ config/locales. One file per a locale is created with the name missing_#{locale}.yml
34
+ Each file contains the text of one of the existing locales, namely the first one
35
+ existing. The order to check is defined by the LIST parameter. This parameter
36
+ can also be used to add a new locale.
37
+ The parameter EXCLUDE may be used to extract one or more locales from the participation
38
+ in the process. If any locale is mentioned in both lists, it will not participate in the
39
+ calculation of the missing translations but it's value may be used in the sample
40
+ translation.
41
+ The parameter NAME allows you to write the missing translations to the specified
42
+ files instead of default 'missing'.
43
+ The parameter SOURCE allows you to restrict the synchronization to be done within
44
+ only one file name. Only the files for all the locales that meet the given name
45
+ will be loaded for the synchronization.
46
+ The parameter IN sets both NAME and SOURCE to its value.
47
+
48
+ 2. rake translations:singles
49
+
50
+ This task generates a single file config/locales/singles.yml that contains the
51
+ translations occuring only in one locale.
52
+
53
+ 3. rake translations:move KEY=key.to.move TO=where.to.move
54
+
55
+ This task moves the given KEY under the TO. Please note, that the TO
56
+ must not have the last part of the KEY at the end.
57
+ You can use all the parameters available for the translations:sync task with the default
58
+ value for the NAME equal to 'moved'.
59
+ In order to overwrite the existing file both NAME and SOURCE should be given
60
+ with the same value. For that there is the parameter IN that sets both values.
61
+
62
+
63
+ So far it works only for rails applications. Suggestions for others are welcome.
64
+
65
+
66
+ Copyright (c) 2010-2011 Dmitri Koulikoff, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rake/rdoctask'
2
+ require 'rubygems'
3
+ require 'rake/gempackagetask'
4
+ # require 'spec/rake/spectask'
5
+ require 'rubygems/specification'
6
+ require 'date'
7
+
8
+ GEM = "translations_sync"
9
+ GEM_VERSION = "0.2.0"
10
+ AUTHOR = "Dmitri Koulikoff"
11
+ EMAIL = "koulikoff@gmail.com"
12
+ HOMEPAGE = "http://github.com/dima4p/translations_sync/"
13
+ SUMMARY = "Synchronizes the different locales represeinted in yaml, in particular, for I18n"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "MIT-LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+
27
+ s.add_dependency "ya2yaml"
28
+
29
+ s.executables = 'translations_sync'
30
+ s.default_executable = 'translations_sync'
31
+ s.require_path = 'lib'
32
+ s.files = %w(MIT-LICENSE README Rakefile init.rb) +
33
+ Dir.glob("{bin,lib,spec}/**/*") -
34
+ Dir.glob("{bin,lib,spec}/**/*~")
35
+ end
36
+
37
+ desc 'Generate documentation for the translations_sync plugin.'
38
+ Rake::RDocTask.new(:rdoc) do |rdoc|
39
+ rdoc.rdoc_dir = 'rdoc'
40
+ rdoc.title = 'TranslationsSync'
41
+ rdoc.options << '--line-numbers' << '--inline-source'
42
+ rdoc.rdoc_files.include('README')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+
46
+ Rake::GemPackageTask.new(spec) do |pkg|
47
+ pkg.gem_spec = spec
48
+ end
49
+
50
+ # task :default => :spec
51
+ # desc "Default: Run specs"
52
+ # Spec::Rake::SpecTask.new do |t|
53
+ # t.spec_files = FileList['spec/**/*_spec.rb']
54
+ # t.spec_opts = %w(-fs --color)
55
+ # end
56
+
57
+ desc "install the gem locally"
58
+ task :install => [:package] do
59
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
60
+ end
61
+
62
+ desc "create a gemspec file"
63
+ task :make_spec do
64
+ File.open("#{GEM}.gemspec", "w") do |file|
65
+ file.puts spec.to_ruby
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+
5
+ from = File.join(File.split(Gem.bin_path('translations_sync')).first,
6
+ '..', 'lib', 'tasks', '*.rake')
7
+ to = File.expand_path('.', File.join('lib', 'tasks'))
8
+ environment = File.expand_path('.', File.join('config', 'environment.rb'))
9
+
10
+ if File.directory? to and File.exists? environment
11
+ FileUtils.cp Dir.glob(from), to
12
+ puts "Rake file are copied to #{to}"
13
+ else
14
+ puts 'You must run it from within the rails directory'
15
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require 'translations_sync'
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ namespace :translations do
3
+
4
+ PARAMS = '. LIST=locales,to,use EXCLUDE=locales,to,ignore'
5
+
6
+ desc "Synchronizes the existing translations" + PARAMS + ' NAME=file_name_prefix'
7
+ task :sync => :environment do
8
+ source = ENV['SOURCE'] || ENV['IN']
9
+ ts = TranslationsSync.new ENV['LIST'], ENV['EXCLUDE'], source
10
+ name = ENV['NAME'] || ENV['IN'] || 'missing'
11
+ ts.missing.keys.sort.each do |lang|
12
+ filename = File.join Rails.root, 'config', 'locales', "#{name}_#{lang}.yml"
13
+ print filename + ' ... '
14
+ if File.exist? filename
15
+ hash = YAML::load(File.open(filename))
16
+ File.open(filename, "w") do |file|
17
+ file.write(TranslationsSync.to_yaml(hash.deep_merge!(ts.missing.slice(lang))))
18
+ end
19
+ puts 'updated'
20
+ else
21
+ File.open(filename, "w") do |file|
22
+ file.write(TranslationsSync.to_yaml(ts.missing.slice lang))
23
+ end
24
+ puts 'created'
25
+ end
26
+ end
27
+ puts "All is synchronized" if ts.missing.size == 0
28
+ end
29
+
30
+ desc "Detects the translations existing only in one locale" + PARAMS
31
+ task :singles => :environment do
32
+ ts = TranslationsSync.new ENV['LIST'], ENV['EXCLUDE']
33
+ filename = File.join Rails.root, 'config', 'locales', "singles.yml"
34
+ if ts.singles.size > 0
35
+ File.open(filename, "w") do |file|
36
+ file.write(TranslationsSync.to_yaml ts.singles)
37
+ end
38
+ puts filename + ' <= ' + ts.singles.keys.join(', ')
39
+ else
40
+ puts 'No singles were found'
41
+ end
42
+ end
43
+
44
+ desc "Moves the key in the translations" + PARAMS + " KEY=key.to.move TO=where.to.move IN=filespec"
45
+ task :move => :environment do
46
+ source = ENV['SOURCE'] || ENV['IN']
47
+ name = ENV['NAME'] || ENV['IN'] || 'moved'
48
+ key = ENV['KEY'] or raise "Parameter KEY must be given"
49
+ ts = TranslationsSync.new ENV['LIST'], ENV['EXCLUDE'], source
50
+ if ts.move key, ENV['TO']
51
+ ts.moved.keys.sort.each do |lang|
52
+ filename = File.join Rails.root, 'config', 'locales', "#{name}_#{lang}.yml"
53
+ print filename + ' ... '
54
+ if File.exist? filename
55
+ status = 'updated'
56
+ else
57
+ status = 'created'
58
+ end
59
+ File.open(filename, "w") do |file|
60
+ file.write(TranslationsSync.to_yaml(ts.moved.slice lang))
61
+ end
62
+ puts status
63
+ end
64
+ else
65
+ puts "The key \"#{key}\" was not found"
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,245 @@
1
+ # encoding: utf-8
2
+ require 'ya2yaml'
3
+ require 'i18n'
4
+
5
+ class Hash
6
+ def stringify_keys!
7
+ keys.each do |key|
8
+ self[key.to_s] = delete(key)
9
+ end
10
+ self
11
+ end
12
+ end
13
+
14
+ class TranslationsSync
15
+
16
+ attr_accessor :translations, :list
17
+ DEFAULT_LIST = 'ru,en'
18
+ EXCLUDE_LIST = ''
19
+ TAIL = '=TODO'
20
+ PKORDER = {'zero' => 0, 'one' => 1, 'two' => 2, 'few' => 3, 'many' => 5, 'other' => 9}
21
+ PKLIST = PKORDER.keys
22
+ RE = Regexp.new ' \d(' + PKORDER.keys.join('|') + '):'
23
+
24
+ class << self
25
+ def to_yaml(hash)
26
+ translate_keys(hash).ya2yaml.gsub(RE, ' \1:')
27
+ end
28
+
29
+ private
30
+
31
+ def translate_keys(hash)
32
+ hash.each_pair do |key, v|
33
+ if v.is_a? Hash
34
+ if v.keys.sort == (v.keys & PKLIST).sort
35
+ new = {}
36
+ v.keys.each do |k|
37
+ new["#{PKORDER[k]}#{k.to_s}"] = v[k]
38
+ end
39
+ hash[key] = new
40
+ else
41
+ translate_keys v
42
+ end
43
+ end
44
+ end
45
+ hash
46
+ end
47
+ end
48
+
49
+ def initialize(list = nil, exclude = nil, source = nil)
50
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
51
+ @translations = I18n.backend.send :translations
52
+ if source
53
+ re = Regexp.new "#{Regexp.escape source}_[^.]+\\.(yml|rb)\\Z"
54
+ I18n.load_path.reject! do |path|
55
+ path !~ re
56
+ end
57
+ @translations.each do |key, hash|
58
+ hash.keys.each do |k|
59
+ hash.delete(k) unless k == :pluralize
60
+ end
61
+ end
62
+ I18n.backend.send(:init_translations)
63
+ end
64
+ @full_list = ((list || DEFAULT_LIST).split(',').map(&:to_sym) + translations.keys).uniq
65
+ exclude = (exclude || EXCLUDE_LIST).split(',').map(&:to_sym)
66
+ @list = @full_list - exclude
67
+ @flat = {}
68
+ @list.each do |lang|
69
+ flatten_keys(lang, translations[lang] || {}, @flat)
70
+ end
71
+ @flat.delete([:pluralize]) # we do not need this proc if it exists
72
+ @pluralize_keys = @list.inject({}) do |acc, lang|
73
+ acc[lang] = if locale_pluralize = I18n.backend.send(:lookup, lang, :pluralize) and
74
+ locale_pluralize.respond_to?(:call)
75
+ ((0..100).map do |n|
76
+ locale_pluralize.call n
77
+ end << :other).uniq
78
+ else
79
+ [:one, :other]
80
+ end
81
+ acc
82
+ end
83
+ end
84
+
85
+ def locales_with_missing
86
+ unless @locales_with_missing
87
+ size = list.size
88
+ @locales_with_missing = []
89
+ @flat.each_pair do |key, val|
90
+ if val.size < size
91
+ (@locales_with_missing += list - val.keys).uniq!
92
+ break if @locales_with_missing.size == size
93
+ end
94
+ end
95
+ end
96
+ @locales_with_missing
97
+ end
98
+
99
+ def missing
100
+ unless @missing
101
+ @missing = {}
102
+ locales_with_missing.each do |lang|
103
+ @missing[lang] = {}
104
+ end
105
+ @flat.each_pair do |key, val|
106
+ (locales_with_missing - val.keys).each do |lang|
107
+ push_to_hash @missing[lang], lang, key, val, :missing
108
+ end
109
+ end
110
+ @missing.stringify_keys!
111
+ end
112
+ @missing
113
+ end
114
+
115
+ def locales_with_singles
116
+ unless @locales_with_singles
117
+ size = list.size
118
+ @locales_with_singles = []
119
+ @flat.each_pair do |key, val|
120
+ if val.size < size
121
+ (@locales_with_singles += val.keys).uniq! if val.size == 1
122
+ break if @locales_with_singles.size == size
123
+ end
124
+ end
125
+ end
126
+ @locales_with_singles
127
+ end
128
+
129
+ def singles
130
+ unless @singles
131
+ @singles = {}
132
+ locales_with_singles.each do |lang|
133
+ @singles[lang] = {}
134
+ end
135
+ @flat.each_pair do |key, val|
136
+ lang = val.keys.first
137
+ push_to_hash @singles[lang], lang, key, val, :singles if val.size == 1
138
+ end
139
+ @singles.stringify_keys!
140
+ end
141
+ @singles
142
+ end
143
+
144
+ def move(key, destination)
145
+ key = key.split('.').map(&:to_sym)
146
+ key_length = key.length - 1
147
+ return false if key_length < 0
148
+ destination ||= ''
149
+ destination = destination.split('.').map(&:to_sym)
150
+ result = false
151
+ @flat.each_pair do |array, val|
152
+ if array[0, key_length + 1] == key
153
+ array[0, key_length] = destination
154
+ result = true
155
+ end
156
+ end
157
+ @moved = nil
158
+ result
159
+ end
160
+
161
+ def moved
162
+ unless @moved
163
+ @moved = {}
164
+ @list.each do |lang|
165
+ @moved[lang] = {}
166
+ end
167
+ @flat.each_pair do |key, val|
168
+ val.keys.each do |lang|
169
+ push_to_hash @moved[lang], lang, key, val, :moved
170
+ end
171
+ end
172
+ @moved.keys.each do |key|
173
+ @moved.delete(key) if @moved[key].size == 0
174
+ end
175
+ @moved.stringify_keys!
176
+ end
177
+ @moved
178
+ end
179
+
180
+ private
181
+
182
+ def flatten_keys(lang, src, dest = {}, prefix = [])
183
+ src.each_pair do |key, value|
184
+ new = prefix.dup << key
185
+ if value.is_a? Hash
186
+ if value.keys.include? :other
187
+ insert_translation(dest, new, lang, value)
188
+ else
189
+ flatten_keys lang, value, dest, new
190
+ end
191
+ else
192
+ insert_translation(dest, new, lang, value)
193
+ end
194
+ end
195
+ dest
196
+ end
197
+
198
+ def insert_translation(dest, key, lang, value)
199
+ dest[key] ||= {}
200
+ dest[key][lang] = value
201
+ end
202
+
203
+ def push_to_hash(hash, target, keys, val, mode)
204
+ key = keys.pop
205
+ h = hash
206
+ keys.each do |k|
207
+ h[k.to_s] = {} unless h[k.to_s]
208
+ h = h[k.to_s]
209
+ end
210
+ case mode
211
+ when :missing
212
+ lang = @full_list.detect do |lang|
213
+ not val[lang].nil?
214
+ end
215
+ if val[lang].is_a? Hash
216
+ h[key.to_s] = {}
217
+ @pluralize_keys[target].each do |pkey|
218
+ h[key.to_s][pkey.to_s] =
219
+ (val[lang][pkey] || val[lang][:many] || val[lang][:other]) + TAIL
220
+ end
221
+ elsif val[lang].is_a? Array
222
+ h[key.to_s] = val[lang].map do |text|
223
+ text.is_a?(String) ? (text + TAIL) : text
224
+ end
225
+ else
226
+ begin
227
+ h[key.to_s] = val[lang].to_s + TAIL
228
+ rescue
229
+ puts %Q(Can not assign to the key "#{key}")
230
+ raise
231
+ end
232
+ end
233
+ when :singles
234
+ value = val.values.first
235
+ value.stringify_keys! if value.is_a? Hash
236
+ h[key.to_s] = value
237
+ when :moved
238
+ value = val[target] or raise 'The translations are not synchronized'
239
+ value.stringify_keys! if value.is_a? Hash
240
+ h[key.to_s] = value
241
+ end
242
+ keys.push key
243
+ end
244
+
245
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $TESTING=true
4
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
5
+
6
+ require 'translation_sync'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: translations_sync
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Dmitri Koulikoff
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-16 00:00:00 +03:00
19
+ default_executable: translations_sync
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ya2yaml
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Synchronizes the different locales represeinted in yaml, in particular, for I18n
36
+ email: koulikoff@gmail.com
37
+ executables:
38
+ - translations_sync
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ - MIT-LICENSE
44
+ files:
45
+ - MIT-LICENSE
46
+ - README
47
+ - Rakefile
48
+ - init.rb
49
+ - bin/translations_sync
50
+ - lib/tasks/translations_sync.rake
51
+ - lib/translations_sync.rb
52
+ - spec/spec_helper.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/dima4p/translations_sync/
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ hash: 3
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.6.2
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Synchronizes the different locales represeinted in yaml, in particular, for I18n
87
+ test_files: []
88
+