i18n-translators-tools 0.1

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/README.md ADDED
@@ -0,0 +1,229 @@
1
+ I18n translation and locales management utility
2
+ ===============================================
3
+
4
+ This package brings you useful utility which can help you to handle locale files
5
+ and translations in your Ruby projects.
6
+
7
+
8
+ Interesting features
9
+ --------------------
10
+
11
+ * no database required
12
+ * merging and changes propagation (adding, removing and changed default text)
13
+ keeping default file untouched
14
+ * creating new locale file based on another (usually default) file
15
+ * converting from one format to another (yml <=> rb <=> po)
16
+ * statistics
17
+ * built-in simple console translator
18
+ * support for locales split into sub-directories like:
19
+ * locale
20
+ * controller
21
+ * main
22
+ * default.yml
23
+ * en_US.yml
24
+ * en_GB.yml
25
+ * cs_CZ.yml
26
+ * auth
27
+ * default.yml
28
+ * en_US.yml
29
+ * en_GB.yml
30
+ * cs_CZ.yml
31
+ * model
32
+ * user
33
+ * default.yml
34
+ * en_US.yml
35
+ * en_GB.yml
36
+ * cs_CZ.yml
37
+ * rules
38
+ * en_US.rb
39
+ * en_GB.yml
40
+ * cs_CZ.rb
41
+ * adds extra translation metadata right to the locale files (translators whose
42
+ translate with text editors don't have to tackle with diffs)
43
+ * can strip all extra metadata and revert back to the
44
+ I18n::Backend::Simple format
45
+ * in configuration files
46
+ (~/.config/ruby/i18n-translate; locale/.i18n-translate) you can put your
47
+ common or project related configurations (e.g: exclude => ['rules'])
48
+ i18n-translate utility reads firstly config in your home
49
+ (~/.config/ruby/i18n-translate) then merge it with the one in your project
50
+ locale directory (e.g: locale/.i18n-translate) and then change it
51
+ using command line arguments
52
+
53
+
54
+ WARNING
55
+ -------
56
+
57
+ * **i18n-translate can NOT handle lambdas and procedures.** The solution is to
58
+ put all your rules to separate file and use exclude (repeatable) argument
59
+ or create configuration file for your project in locales directory including
60
+ exclude array.
61
+ * **po files are supported only partialy.** If you convert from yaml or ruby to
62
+ po and back you don't have to care even if you are using pluralization.
63
+ If you are converting from po origin files then you can lose header of the
64
+ file, pluralization, extracted comments, some flags (fuzzy will stay) and
65
+ previous-context. Strings over multiple lines are supported, however.
66
+ * **po files are not compatible with I18n::Gettext.** The main purpose of
67
+ enabling conversions to po files is effort to allow usage of many po editors
68
+ for ruby projects. It is supposed that you will keep your files in yml (rb)
69
+ and conversions will be used only for translators and then you convert it
70
+ back. Then you lose nothing.
71
+
72
+
73
+ How to add support into your application
74
+ ----------------------------------------
75
+
76
+ i18n-translate brings additional metadata to locales. Therefore you have to
77
+ include new backend into I18n. This new backend works as a transparent proxy
78
+ and can work with both simple and enhanced format. If you are already using
79
+ I18n then adding this backend will be probably the only change you have to do.
80
+ I highly recommend to use this extension together with Fallback backend
81
+ and all default values (usually in english) put into special file like
82
+ 'default.yml'.
83
+
84
+ This will enable to have locales like en_US and en_GB where
85
+ can native speakers put their polished english. I think it is good thing
86
+ to put all default strings into separate file(s). If you need to change
87
+ some text you don't have to touch source code.
88
+
89
+ Default files have to be in the I18n simple format. The enhanced format
90
+ is only for locales.
91
+
92
+ So in your application you should do something like this:
93
+
94
+ require 'i18n-translate'
95
+
96
+ I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
97
+ I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
98
+ I18n.default_locale = 'default'
99
+ I18n.load_path << Dir[ File.expand_path("../locale/*.yml", __FILE__) ]
100
+
101
+ and then you can use
102
+
103
+ I18n.t('some.key', :count => 3, :var => 'interpolated string')
104
+
105
+ as usual.
106
+
107
+
108
+ Examples
109
+ --------
110
+
111
+ Suppose we have our locales in 'locale/' directory without sub-directories and
112
+ default values are inside 'locale/default.yml' file. And we have two files
113
+ with locales 'locale/de_DE.yml', 'locale/cs_CZ.yml' and 'locale/extra/cs_CZ.yml'
114
+
115
+ **Merging new changes (additions, removes, field changes) to all files:**
116
+
117
+ $> i18n-translate merge
118
+ locale/cs_CZ.yml...merged
119
+ locale/de_DE.yml...merged
120
+
121
+ **Converting all cs_CZ files to rb format:**
122
+
123
+ $> i18n-translate convert -f yml -t rb -l cs_CZ -r
124
+ locale/cs_CZ.yml...converted
125
+ locale/extra/cs_CZ.yml...converted
126
+
127
+ **Show some statistics:**
128
+
129
+ $> i18n-translate stat
130
+ locale/cs_CZ.yml...65% (650/1000)
131
+ locale/de_DE.yml...90% (900/1000)
132
+
133
+ **Translate more entries (built-in translator invocation):**
134
+
135
+ $> i18n-translate translate -l cs_CZ
136
+
137
+ For more help run i18n-translate without parameters.
138
+
139
+ There also exists example web application. It is simple web standalone
140
+ translator. You can download it at [i18n-web-translator][1]
141
+
142
+
143
+ Supported formats
144
+ -----------------
145
+
146
+ * **po**; supported format looks like
147
+
148
+ # there is some comment
149
+ #, fuzzy, changed
150
+ #| msgid Old default
151
+ msgctxt "there.is.some.key"
152
+ msgid "Default text"
153
+ msgstr "Prelozeny defaultni text"
154
+
155
+ Such po file is pretty usable with po editors.
156
+
157
+ * **yml**; standard yaml files in I18n simple format
158
+ * **rb**; typical ruby files in I18n simple format
159
+
160
+
161
+ New locale files format
162
+ ------------------------
163
+
164
+ Old format using in Simple backend is:
165
+
166
+ key: "String"
167
+
168
+ or for pluralization (depends on rules) it can be similar to this:
169
+
170
+ key:
171
+ one: "String for one"
172
+ other: "plural string"
173
+
174
+ New format looks like:
175
+
176
+ key:
177
+ old: "old default string"
178
+ default: "new default string"
179
+ comment: "translator's comments"
180
+ t: "translation itself"
181
+ flag: "one of (ok || incomplete || changed || untranslated)"
182
+ fuzzy: true # exists only where flag != ok (nice to have when you want
183
+ edit files manually)
184
+
185
+ Pluralized variant should look like:
186
+
187
+ key:
188
+ one:
189
+ old:
190
+ default:
191
+ t:
192
+ ...
193
+ other:
194
+ old:
195
+ default:
196
+ t:
197
+ ...
198
+
199
+ As you can see the old format is string and the new format is hash.
200
+ If you use lambdas and procs objects, you should save them in separate
201
+ file(s) because i18n-translate utility can't handle them but Translate backend
202
+ can.
203
+
204
+
205
+ Configure file format
206
+ -------------------
207
+
208
+ Configuration files are normal ruby files which should return hash.
209
+ It is not necessary to use all switches.
210
+
211
+ *Example config file:*
212
+
213
+ {
214
+ :exclude => ['rules'],
215
+ :format => 'yml',
216
+ :default => 'en_US'
217
+ :default_format => 'rb',
218
+ :verbose => true,
219
+ :locale_dir => 'locales',
220
+ :separator => '.',
221
+ :target => 'yml',
222
+ :deep => true,
223
+ :force_encoding => true,
224
+ :encoding => 'utf-8',
225
+ :quiet => false
226
+ }
227
+
228
+ [1]: http://github.com/pejuko/i18n-web-translator
229
+
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+ # vi: fenc=utf-8:expandtab:ts=2:sw=2:sts=2
3
+ #
4
+ # @author: Petr Kovar <pejuko@gmail.com>
5
+
6
+ require 'rake/testtask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/clean'
9
+
10
+ CLEAN << "coverage" << "pkg"
11
+
12
+ task :default => [:test]
13
+ Rake::TestTask.new(:test) do |t|
14
+ t.pattern = File.join(File.dirname(__FILE__), 'test/tc_*.rb')
15
+ t.verbose = true
16
+ end
17
+
18
+ Rake::GemPackageTask.new(eval(File.read("i18n-translators-tools.gemspec"))) {|pkg|}
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # vi: fenc=utf-8:expandtab:ts=2:sw=2:sts=2
4
+ #
5
+ # @author: Petr Kovar <pejuko@gmail.com>
6
+
7
+ $KCODE="UTF8"
8
+
9
+ def print_help
10
+ print $0
11
+ puts " <command> [options] [locale directory]"
12
+ puts ""
13
+ puts "commands:
14
+ list -- list available locales
15
+ stat -- shows statistics
16
+ create -- adds new locale into locale directory
17
+ merge -- scans locale directory and merge locales with default file
18
+ convert -- convert from -f format to -c format
19
+ strip -- throw away translation metadata from locales and makes them
20
+ compatible again with I18n::Backend::Simple
21
+ translate -- simple console translator; --language must be set
22
+
23
+
24
+ options:
25
+ --exclude[=pattern], -x [pattern] -- exludes directory/file from scan
26
+ if pattern is missing, exclude
27
+ is reset to empty array. useful if
28
+ you have exclude configured in your
29
+ config file and want to reset it
30
+ e.g: merge -x -x rules
31
+ the first -x resets exclude
32
+ --locale_dir=<dir>, -p <dir> -- where to search for locales
33
+ (default: ./locale)
34
+ --default=<name>, -d <name> -- name of file with default strings
35
+ without suffix (default: default)
36
+ --format=<format>, -f <format> -- format of locale files. one from
37
+ auto, yml, rb, (po - not implemented)
38
+ (default: auto)
39
+ --default_format=<format> -- format of default locale
40
+ default: same as --format
41
+ --separator=<string>, -s <string> -- separator in keys (default: .)
42
+ (key e.g.: main.message.error)
43
+ --target=<format>, -t <format> -- convert from one format to second
44
+ e.g: -f rb -t yml
45
+ --locale=<locale>, -l <locale> -- work with specific locale only
46
+ e.g: create -p locale -l cs_CZ
47
+ --deep, -r -- scan locale_dir recursively
48
+ --quiet, -q -- show less information
49
+ --verbose, -v -- shows more information during
50
+ locales processing
51
+
52
+
53
+ CONFIG FILE
54
+
55
+ ~/.config/ruby/i18n-translate
56
+ locale_dir/.i18n-translate
57
+
58
+ i18n-translate reads its configuration firstly from file and then
59
+ overwrites them with command line arguments. It is normal ruby file
60
+ where is defined variable options as hash.
61
+
62
+ Example:
63
+
64
+ options = {
65
+ :exclude => ['rules'],
66
+ :verbose => true,
67
+ :format => 'yml',
68
+ :default => 'en_US'
69
+ }
70
+
71
+
72
+ !!! WARNING !!!
73
+
74
+ It can NOT handle locales with proc or lambda.
75
+ Store your procedures into diferent file/directory and include
76
+ this file/directory in --exclude. Optionaly you can set
77
+ excludes in your config file.
78
+ "
79
+ end
80
+
81
+
82
+ COMMANDS = %w(list stat create merge convert strip translate)
83
+ command = ARGV.shift
84
+
85
+ unless COMMANDS.include?(command)
86
+ print_help()
87
+ exit
88
+ end
89
+
90
+
91
+
92
+ begin
93
+ require 'rubygems'
94
+ rescue
95
+ end
96
+
97
+ require 'i18n'
98
+ require 'i18n-translate'
99
+
100
+
101
+ def process_locale(tr, command, opts)
102
+ options = opts.dup
103
+ options.delete(:quiet) if command == 'list'
104
+
105
+ print "#{tr.lang_file}..." unless options[:quiet]
106
+
107
+ msg = ""
108
+ save_locale = false
109
+ case command
110
+ when 'strip'
111
+ tr.strip!
112
+ msg="striped"
113
+ save_locale = true
114
+ when 'merge'
115
+ tr.assign(tr.merge)
116
+ msg="merged"
117
+ save_locale = true
118
+ when 'convert'
119
+ tr.options[:format] = options[:target]
120
+ msg="converted"
121
+ save_locale = true
122
+ when 'list'
123
+ msg="exists"
124
+ when 'stat'
125
+ stat = tr.stat
126
+ if stat[:total] > 0
127
+ msg = "%3d%% (#{stat[:ok]}/#{stat[:total]})" % stat[:progress].to_i
128
+ else
129
+ msg = "doesn't exist #{tr.default_file}: Try --default_format" unless File.exists?(tr.default_file)
130
+ end
131
+ end
132
+
133
+ if save_locale
134
+ tr.export!
135
+ end
136
+
137
+ puts msg unless options[:quiet]
138
+ end
139
+
140
+
141
+ # setup command line arguments
142
+ require 'getoptlong'
143
+ opts = GetoptLong.new(
144
+ ["--exclude", "-x", GetoptLong::OPTIONAL_ARGUMENT],
145
+ ["--locale_dir", "-p", GetoptLong::REQUIRED_ARGUMENT],
146
+ ["--default", "-d", GetoptLong::REQUIRED_ARGUMENT],
147
+ ["--format", "-f", GetoptLong::REQUIRED_ARGUMENT],
148
+ ["--default_format", "-g", GetoptLong::REQUIRED_ARGUMENT],
149
+ ["--separator", "-s", GetoptLong::REQUIRED_ARGUMENT],
150
+ ["--target", "-t", GetoptLong::REQUIRED_ARGUMENT],
151
+ ["--deep", "-r", GetoptLong::NO_ARGUMENT],
152
+ ["--verbose", "-v", GetoptLong::NO_ARGUMENT],
153
+ ["--quiet", "-q", GetoptLong::NO_ARGUMENT],
154
+ ["--locale", "-l", GetoptLong::REQUIRED_ARGUMENT]
155
+ )
156
+
157
+
158
+ # setting up default options
159
+ user_config_file = File.join(ENV["HOME"], ".config/ruby/i18n-translate")
160
+ options = I18n::Translate::Translate::DEFAULT_OPTIONS.dup
161
+ options.merge!(eval( File.read(user_config_file) )) if File.exists?(user_config_file)
162
+ options[:exclude] ||= []
163
+
164
+
165
+ tmp = {}
166
+ # process command line arguments
167
+ opts.each do |opt, val|
168
+ optkey = opt[2..-1].to_sym
169
+ case optkey
170
+ when :exclude
171
+ unless val.to_s.strip.empty?
172
+ tmp[:exclude] << val
173
+ else
174
+ tmp[:exclude] = []
175
+ end
176
+ when :format, :default_format
177
+ if (not I18n::Translate::FORMATS.include?(val)) and (val != 'auto')
178
+ puts "!!! Unknown file format"
179
+ exit 5
180
+ end
181
+ tmp[optkey] = val
182
+ when :deep
183
+ tmp[optkey] = val
184
+ when :verbose
185
+ tmp[:verbose] = true
186
+ tmp[:quiet] = false
187
+ when :quiet
188
+ tmp[:quiet] = true
189
+ tmp[:verbose] = false
190
+ else
191
+ tmp[optkey] = val
192
+ end
193
+ end
194
+
195
+ locale_dir = tmp[:locale_dir] ? tmp[:locale_dir] : options[:locale_dir]
196
+ # reading project locale config options
197
+ locale_config_file = File.join(locale_dir, ".i18n-translate")
198
+ options.merge!(eval( File.read(locale_config_file) )) if File.exists?(locale_config_file)
199
+
200
+ # merge command line arguments
201
+ options.merge!(tmp)
202
+
203
+
204
+ # process command
205
+ case command
206
+
207
+ when 'create'
208
+
209
+ unless options[:locale]
210
+ puts "!!! With command create you must set --locale parametr"
211
+ exit 1
212
+ end
213
+
214
+ format = (options[:format] == "auto") ? I18n::Translate::FORMATS.first : options[:format]
215
+ fname = File.join(options[:locale_dir], "#{options[:locale]}.#{format}")
216
+
217
+ if File.exists?(fname)
218
+ puts "!!! File '#{fname}' already exists"
219
+ exit 2
220
+ end
221
+
222
+ tr = I18n::Translate::Translate.new(options[:locale], options.merge({:format => format}))
223
+ tr.assign(tr.merge)
224
+ tr.export!
225
+
226
+ puts "#{tr.options[:locale_dir]}/#{tr.lang}...created" unless options[:quiet]
227
+
228
+ when 'translate'
229
+
230
+ unless options[:locale]
231
+ puts "!!! With command translate you must set --locale parametr"
232
+ exit 1
233
+ end
234
+
235
+ tr = I18n::Translate::Translate.new(options[:locale], options)
236
+ stat = tr.stat
237
+ tr.merge.select{|x| x["flag"] != "ok"}.each_with_index do |entry, i|
238
+ next_entry = false
239
+ while not next_entry
240
+ puts ""
241
+ puts ""
242
+ puts "(#{i+1}/#{stat[:fuzzy]}) #{entry["key"]} (#{entry["flag"]})"
243
+ puts "comment: #{entry["comment"]}" unless entry["comment"].empty?
244
+ puts "old default: #{entry["old_default"]}" unless entry["old_default"].empty?
245
+ puts "old translation: #{entry["old_t"]}" unless entry["old_t"].empty?
246
+ puts "default: #{entry["default"]}"
247
+ puts "translation: #{entry["t"]}"
248
+ puts ""
249
+ puts "Actions:"
250
+ puts "n (next) t (translate) f (change flag) c (comment) s (save) q (save & quit) x(exit no saving)"
251
+ action = STDIN.readline.strip
252
+ puts ""
253
+ case action
254
+ when 'n'
255
+ next_entry = true
256
+ when 't'
257
+ puts "Enter translation:"
258
+ entry["t"] = STDIN.readline.strip
259
+ entry["flag"] = "ok" unless entry["t"].empty?
260
+ tr.assign( [entry] )
261
+ puts "Flag sets to #{entry["flag"]}"
262
+ when 'f'
263
+ puts "Change flag to:"
264
+ puts "o (ok), i (incomplete), c (changed), u (untranslated)"
265
+ f = STDIN.readline.strip
266
+ I18n::Translate::FLAGS.each do |fname|
267
+ if fname[0,1] == f
268
+ entry["flag"] = fname
269
+ break
270
+ end
271
+ end
272
+ tr.assign( [entry] )
273
+ puts "Flag sets to #{entry["flag"]}"
274
+ when 'c'
275
+ puts "Enter comment:"
276
+ entry["comment"] = STDIN.readline.strip
277
+ tr.assign( [entry] )
278
+ puts "Comment has changed."
279
+ when 's'
280
+ tr.export!
281
+ puts "Translation saved"
282
+ when 'q'
283
+ tr.export!
284
+ puts "Translation saved"
285
+ exit
286
+ when 'x'
287
+ exit
288
+ end # case
289
+ end # while
290
+ end # each_with_index
291
+
292
+ tr.export!
293
+ tr.reload!
294
+ else
295
+
296
+ I18n::Translate.scan(options) do |tr|
297
+ # skip if not desired locale
298
+ next if options[:locale] and (options[:locale] != tr.lang)
299
+
300
+ process_locale(tr, command, options)
301
+ end
302
+
303
+ end
304
+