i18n-translators-tools 0.1

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