translations_sync 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+