i18n-translators-tools 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,7 +2,10 @@ I18n translation and locales management utility
2
2
  ===============================================
3
3
 
4
4
  This package brings you useful utility and library which can help you to handle
5
- locale files and translations in your Ruby projects.
5
+ locale files and translations in your Ruby projects. It is build upon i18n
6
+ library and extends it's simple format so you can simply track field changes
7
+ or keep translator's notes. Conversion back to simple format is possible and as
8
+ simple as call 'i18n-translate strip'.
6
9
 
7
10
 
8
11
  Interesting features
@@ -11,6 +14,8 @@ Interesting features
11
14
  * no database required
12
15
  * merging and changes propagation (adding, removing and changed default text)
13
16
  keeping default file untouched
17
+ * hard/soft merging (hard deletes extra keys from target, soft set them
18
+ obsolete; default is soft)
14
19
  * creating new locale file based on default file
15
20
  * converting from one format to another (yml <=> rb <=> po <=> ts <=> properties)
16
21
  * statistics
@@ -275,7 +280,7 @@ New format looks like:
275
280
  file: "file parsed from reference"
276
281
  line: "line parsed from po reference"
277
282
  translation: "translation itself"
278
- flag: "one of (ok || incomplete || changed || untranslated)"
283
+ flag: "one of (ok || incomplete || changed || untranslated || obsolete)"
279
284
  fuzzy: true # exists only where flag != ok (nice to have when you want
280
285
  edit files manually)
281
286
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ require 'rake/testtask'
8
8
  require 'rake/gempackagetask'
9
9
  require 'rake/clean'
10
10
 
11
- CLEAN << "coverage" << "pkg" << "README.html" << "CHANGELOG.html"
11
+ CLEAN << "coverage" << "pkg" << "README.html" << "CHANGELOG.html" << '*.rbc'
12
12
 
13
13
  task :default => [:test, :doc, :gem]
14
14
  Rake::TestTask.new(:test) do |t|
@@ -23,6 +23,12 @@ task :rcov do |t|
23
23
  system "rcov --exclude .rvm,lib/ruby --sort coverage --text-summary --text-coverage-diff -o coverage test/all.rb"
24
24
  end
25
25
 
26
+ desc "Benchmark processors"
27
+ task :bench do |t|
28
+ system "ruby benchmark/gettext_scan.rb"
29
+ system "ruby benchmark/gettext_reg.rb"
30
+ end
31
+
26
32
  begin
27
33
  require 'bluecloth'
28
34
 
@@ -47,5 +53,5 @@ begin
47
53
  build_document("CHANGELOG.md")
48
54
  end
49
55
 
50
- rescue
56
+ rescue LoadError
51
57
  end
data/bin/i18n-translate CHANGED
@@ -51,6 +51,8 @@ options:
51
51
  --quiet, -q -- show less information
52
52
  --verbose, -v -- shows more information during
53
53
  locales processing
54
+ --hard, -h -- deletes from target files all missing keys in default
55
+ normaly those keys are kept but flag is set to obsolete
54
56
 
55
57
 
56
58
  CONFIG FILE
@@ -184,7 +186,8 @@ opts = GetoptLong.new(
184
186
  ["--encoding", "-e", GetoptLong::REQUIRED_ARGUMENT],
185
187
  ["--verbose", "-v", GetoptLong::NO_ARGUMENT],
186
188
  ["--quiet", "-q", GetoptLong::NO_ARGUMENT],
187
- ["--locale", "-l", GetoptLong::REQUIRED_ARGUMENT]
189
+ ["--locale", "-l", GetoptLong::REQUIRED_ARGUMENT],
190
+ ["--hard", "-h", GetoptLong::NO_ARGUMENT]
188
191
  )
189
192
 
190
193
 
@@ -214,6 +217,8 @@ opts.each do |opt, val|
214
217
  tmp[optkey] = val
215
218
  when :deep
216
219
  tmp[optkey] = val
220
+ when :hard
221
+ tmp[:merge] = "hard"
217
222
  when :verbose
218
223
  tmp[:verbose] = true
219
224
  tmp[:quiet] = false
@@ -13,9 +13,9 @@ spec = Gem::Specification.new do |s|
13
13
  s.email = "pejuko@gmail.com"
14
14
  s.authors = ["Petr Kovar"]
15
15
  s.name = 'i18n-translators-tools'
16
- s.version = '0.2.3'
16
+ s.version = '0.2.4'
17
17
  s.date = Time.now.strftime("%Y-%m-%d")
18
- s.add_dependency('i18n', '>= 0.4.1')
18
+ s.add_dependency('i18n', '>= 0.5.0')
19
19
  s.add_dependency('ya2yaml')
20
20
  s.require_path = 'lib'
21
21
  s.files = ["bin/i18n-translate", "README.md", "i18n-translators-tools.gemspec", "Rakefile"]
@@ -37,7 +37,7 @@ Supported formats:
37
37
 
38
38
  Backends:
39
39
  * Extended format. i18n-translators-tools brings extended format
40
- I18n::Backend::Simple.send(:include, I18n::Backend::Translator)
40
+ I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
41
41
  * Gettext po
42
42
  I18n::Backend::Simple.send(:include, I18n::Backend::PO)
43
43
  * QT Linguist TS
@@ -64,9 +64,19 @@ Changelog:
64
64
  v0.2.3
65
65
  * fix: hash_to_keys can work with enhanced format => default can be in
66
66
  enchanced format
67
+ * default file can be now in enhanced format. if translation field is missing
67
68
  * i18n-translate <source file> <target file>
68
69
  automaticlay perform convert action from one file to another
69
70
 
71
+ v0.2.4
72
+ * enhanced support for java properties
73
+ * hard/soft merges. hard deletes deleted keys in target and soft set them to
74
+ obsolete
75
+ * processors now generate keys list from provided data and not from @tr.default
76
+ * flag obsolete added
77
+ * delete function
78
+ * i18n-0.5.0 compatibility (for older i18n user v0.2.3)
79
+
70
80
  For more information read README.md and CHANGELOG.md
71
81
 
72
82
  -----------------------------------------------------------------------------
@@ -77,10 +87,13 @@ http://github.com/pejuko/i18n-translators-tools
77
87
  EOF
78
88
  s.description = <<EOF
79
89
  This package brings you useful utility and library which can help you to handle
80
- locale files and translations in your Ruby projects. Offers also built-in simple
81
- console editor. Supported formats are YAML, Ruby, Gettext po, QT Linguist TS and
82
- Java Properties. Read README.md file and run i18n-translate without parameters
83
- for more information.
90
+ locale files and translations in your Ruby projects.
91
+ It is build upon i18n library and extends it's simple format so you can simply
92
+ track field changes or keep translator's notes. Conversion back to simple format
93
+ is possible and as simple as call 'i18n-translate strip'. Offers also built-in
94
+ simple console editor. Supported formats are YAML, Ruby, Gettext po,
95
+ QT Linguist TS and Java Properties. Read README.md file and run i18n-translate
96
+ without parameters for more information.
84
97
  EOF
85
98
  end
86
99
 
@@ -8,7 +8,7 @@ module I18n
8
8
  module Backend
9
9
  # It is highly recommended to use Translator wit Fallback plugin
10
10
  #
11
- # I18n::Backend::Simple.send(:include, I18n::Backend::Translator)
11
+ # I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
12
12
  # I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
13
13
  #
14
14
  # notice that Translator have to be included BEFORE Fallback otherwise
@@ -28,7 +28,7 @@ module I18n
28
28
 
29
29
  return nil if tr.to_s.empty?
30
30
 
31
- values = options.except(*I18n::Backend::Base::RESERVED_KEYS)
31
+ values = options.except(*RESERVED_KEYS)
32
32
 
33
33
  tr = resolve(locale, key, tr, options)
34
34
  tr = interpolate(locale, tr, values) if values
@@ -121,7 +121,7 @@ module I18n::Translate
121
121
  # converts inspected string back into normal string
122
122
  def uninspect(str)
123
123
  return nil unless str
124
- str.gsub(%r!\\([\\#"abefnrstvx]|u\d{4}|u\{[^\}]+\}|\d{1,3}|x\d{1,2}|cx|C-[a-zA-Z]|M-[a-zA-Z])!) do |m|
124
+ str.gsub(%r!\\([\\#"abefnrstvx]|u\d{4}|u\{[^\}]+\}|\d{1,3}|x\d{1,2}|cx|C-[a-zA-Z]|M-[a-zA-Z]| |=|:)!) do |m|
125
125
  repl = ""
126
126
  if ['\\', '#', '"'].include?($1)
127
127
  repl = $1
@@ -117,7 +117,7 @@ module I18n::Translate::Processor
117
117
  def export(data)
118
118
  target = data[@translate.lang]
119
119
  str = ""
120
- keys = I18n::Translate.hash_to_keys(@translate.default).sort
120
+ keys = I18n::Translate.hash_to_keys(target).sort
121
121
 
122
122
  str << %~msgid ""\n~
123
123
  str << %~msgstr ""\n~
@@ -0,0 +1,190 @@
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 'strscan'
7
+
8
+ module I18n::Translate::Processor
9
+
10
+ class GettextScanner < Template
11
+ FORMAT = ['po', 'pot']
12
+
13
+ protected
14
+
15
+ def import(data)
16
+ hash = {}
17
+
18
+ entry = {}
19
+ key = nil
20
+ last = nil
21
+
22
+
23
+ s = StringScanner.new(data)
24
+
25
+ until s.eos?
26
+
27
+ # empty line starts new entry
28
+ if s.scan /\n\s*\n/
29
+ if not entry.empty? and key
30
+ key = entry["default"].dup unless key
31
+ I18n::Translate.set(key, entry, hash, @translate.options[:separator])
32
+ end
33
+ key = last = nil
34
+ entry = {}
35
+ next
36
+ end
37
+
38
+ # skip end of line
39
+ if s.scan /\n/
40
+ next
41
+ end
42
+
43
+ # comments
44
+ if s.scan /#/
45
+ # translator's comment
46
+ if s.scan %r{\s+}
47
+ entry["comment"] = s.scan(/.*?$/).to_s.strip
48
+
49
+ # extracted comment
50
+ elsif s.scan %r{\.\s+}
51
+ entry["extracted_comment"] = s.scan(/.*?$/).to_s.strip
52
+
53
+ # reference
54
+ elsif s.scan %r{:\s+}
55
+ entry["reference"] = s.scan(/.*?$/).to_s.strip
56
+ if entry["reference"] =~ %r{(.*):(\d+)}
57
+ entry["file"] = $1.to_s.strip
58
+ entry["line"] = $2.to_s.strip
59
+ end
60
+
61
+ # flag
62
+ elsif s.scan %r{,\s+}
63
+ flags = s.scan(/.*?$/).split(",").compact.map{|x| x.strip}
64
+ fuzzy = flags.delete("fuzzy")
65
+ unless fuzzy
66
+ entry["flag"] = "ok"
67
+ else
68
+ flags.delete_if{|x| not I18n::Translate::FLAGS.include?(x)}
69
+ entry["flag"] = flags.first unless flags.empty?
70
+ entry["fuzzy"] = true
71
+ end
72
+
73
+ # old default
74
+ elsif s.scan %r{\| msgid\s+"}
75
+ match = s.scan(%r{.*"$}).to_s[0..-2]
76
+ entry["old_default"] = match
77
+ # expect that this entry has no key
78
+ # if does, will be overwriten later
79
+ key = entry["old_default"].dup
80
+ end
81
+ end # end of scan for comments
82
+
83
+ # key (context)
84
+ if s.scan %r{msgctxt\s+"}
85
+ key = get_string(s)
86
+ last = "key"
87
+
88
+ # default
89
+ elsif s.scan %r{msgid\s+"}
90
+ match = get_string(s)
91
+ if match.empty?
92
+ last = "po-header"
93
+ else
94
+ last = "default"
95
+ entry[last] = uninspect(match)
96
+ key = entry[last].dup unless key
97
+ end
98
+
99
+ # translation
100
+ elsif s.scan %r{msgstr\s+"}
101
+ match = get_string(s)
102
+ last = "translation" unless last == "po-header"
103
+ entry[last] = uninspect(match)
104
+
105
+ # string continuation
106
+ elsif s.scan %r{"}
107
+ match = get_string(s)
108
+ if last == "key"
109
+ key = "#{key}#{match}"
110
+ elsif last == "po-header"
111
+ case match
112
+ when %r{Content-Type: text/plain; charset=(.*)}
113
+ enc = uninspect($1.to_s).strip
114
+ @translate.options[:encoding] = enc unless enc.empty?
115
+ when %r{X-Language: (.*)}
116
+ # skip language is set from filename
117
+ end
118
+ elsif last
119
+ entry[last] = "#{entry[last]}#{uninspect(match)}"
120
+ end
121
+ end
122
+ end
123
+
124
+ # last line at end of file
125
+ if not entry.empty? and key
126
+ I18n::Translate.set(key, entry, hash, @translate.options[:separator])
127
+ end
128
+
129
+ {@translate.lang => hash}
130
+ end
131
+
132
+
133
+ def export(data)
134
+ target = data[@translate.lang]
135
+ str = ""
136
+ keys = I18n::Translate.hash_to_keys(@translate.default).sort
137
+
138
+ str << %~msgid ""\n~
139
+ str << %~msgstr ""\n~
140
+ str << %~"Content-Type: text/plain; charset=#{@translate.options[:encoding]}\\n"\n~
141
+ str << %~"X-Language: #{@translate.lang}\\n"\n~
142
+ keys.each do |key|
143
+ entry = [""]
144
+ value = @translate.find(key, target)
145
+ next unless value
146
+
147
+ if value.kind_of?(String)
148
+ # leave out msgctxt if using po strings as a key
149
+ default = @translate.find(key, @translate.default)
150
+ entry << %~msgctxt #{key.inspect}~ if key != default
151
+ entry << %~msgid #{default.to_s.inspect}~
152
+ entry << %~msgstr #{value.to_s.inspect}~
153
+ else
154
+ entry << %~# #{value["comment"].to_s.strip}~ unless value["comment"].to_s.strip.empty?
155
+ entry << %~#. #{value["extracted_comment"].to_s.strip}~ unless value["extracted_comment"].to_s.strip.empty?
156
+ if not value["reference"].to_s.strip.empty?
157
+ entry << %~#: #{value["reference"].to_s.strip}~
158
+ elsif value["file"] or value["line"]
159
+ entry << %~#: #{value["file"].to_s.strip}:#{value["line"].to_s.strip}~
160
+ end
161
+ key_default = nil
162
+ key_default = value["default"] if value["default"] == key
163
+ key_default = value["old_default"] if value["old_default"] == key
164
+ flags = []
165
+ flags << "fuzzy" if (not value["flag"].nil?) and (value["flag"] != "ok")
166
+ flags << value["flag"] unless value["flag"].to_s.strip.empty?
167
+ entry << %~#, #{flags.join(", ")}~ unless flags.empty?
168
+ entry << %~#| msgid #{value["old_default"].to_s.inspect}~ unless value["old_default"].to_s.empty?
169
+ entry << %~msgctxt #{key.inspect}~ if key != key_default
170
+ entry << %~msgid #{value["default"].to_s.inspect}~
171
+ entry << %~msgstr #{value["translation"].to_s.inspect}~
172
+ end
173
+
174
+ entry << ""
175
+
176
+ str << entry.join("\n")
177
+ end
178
+
179
+ str
180
+ end
181
+
182
+ private
183
+
184
+ def get_string scanner
185
+ scanner.scan(%r{.*?"$}).to_s[0..-2]
186
+ end
187
+
188
+ end
189
+
190
+ end
@@ -8,6 +8,13 @@ module I18n::Translate::Processor
8
8
  class Properties < Template
9
9
  FORMAT = ['properties']
10
10
 
11
+ WHITE_SPACE = / |\n|\t|\f|\r|\\u0020|\\u0009|\\u000C/
12
+ ASSIGN = /(?:#{WHITE_SPACE})*?(?:=|:)(?:#{WHITE_SPACE})*/
13
+ KEY = /^((?:[^\\:=]|\\:|\\=|\\ )+?)/m
14
+ VALUE_MULTILINE = /(.*?(?:\\\\)*)\\$/
15
+ VALUE = /(.*?(?:\\\\)*$)/
16
+ VALUE_END = /([^=]+?(?:\\\\)*$)/
17
+
11
18
  protected
12
19
 
13
20
  def import(data)
@@ -15,44 +22,53 @@ module I18n::Translate::Processor
15
22
 
16
23
  key = nil
17
24
  value = nil
25
+ status = :first
18
26
  line_number = 0
19
27
  data.each_line do |line|
20
28
  line_number += 1
21
- # skip empty line and comments
22
- next if (line =~ %r{^\s*$}) or (line =~ %r{^#.*})
29
+ # skip empty line and comments (first non white character is # or !)
30
+ next if (line =~ %r{^(#{WHITE_SPACE})*$}) or (line =~ %r{^(#{WHITE_SPACE})*(#|!).*})
23
31
 
24
- case line
25
32
  # multiline string
26
- when %r{^([^=]+)\s*=\s*(.*)\\$}
27
- key = $1.to_s.strip
28
- value = $2.to_s.strip
33
+ if line[%r{#{KEY}#{ASSIGN}#{VALUE_MULTILINE}}] and (status == :first)
34
+ status = :inside
35
+ key = $1.to_s
36
+ value = $2.to_s
29
37
 
30
38
  # continuous multiline string
31
- when %r{^\s*([^=]+)\\$}
32
- value << $1.to_s.strip
39
+ elsif line[%r{^(?:#{WHITE_SPACE})*#{VALUE_MULTILINE}}] and (status == :inside)
40
+ value << $1.to_s
33
41
 
34
42
  # end of continuous string
35
- when %r{^\s*([^=]+)$}
43
+ elsif line[%r{^(?:#{WHITE_SPACE})*#{VALUE_END}$}] and (status == :inside)
36
44
  value << $1.to_s.strip
37
- I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
45
+ I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
38
46
  value = nil
47
+ status = :first
39
48
 
40
49
  # simple key = value
41
- when %r{^([^=]+)\s*=\s*(.*)$}
50
+ elsif line[%r{#{KEY}#{ASSIGN}#{VALUE}}]
42
51
  key, value = $1.to_s.strip, $2.to_s.strip
43
- I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
52
+ I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
53
+
54
+ # empty key
55
+ elsif line[/#{KEY}$/]
56
+ key = $1.to_s.strip
57
+ value = ""
58
+ I18n::Translate.set(uninspect(key), uninspect(value), hash, @translate.options[:separator])
59
+ else
60
+ puts "*** not match: '#{line}'"
44
61
  end
45
62
  end
46
63
 
47
64
  {@lang => hash}
48
65
  end
49
66
 
50
-
51
67
  # this export ignores data
52
68
  def export(data)
53
69
  target = data[@translate.lang]
54
70
  str = ""
55
- keys = I18n::Translate.hash_to_keys(@translate.default).sort
71
+ keys = I18n::Translate.hash_to_keys(target).sort
56
72
 
57
73
  keys.each do |key|
58
74
  value = @translate.find(key, target)
@@ -61,13 +77,13 @@ module I18n::Translate::Processor
61
77
 
62
78
 
63
79
  if value.kind_of?(String)
64
- entry = value.strip
80
+ entry = value
65
81
  else
66
- entry = value["translation"].to_s.strip
82
+ entry = value["translation"].to_s
67
83
  end
68
84
 
69
85
  # create record in format: key = value
70
- str << key << " = " << entry.gsub("\n", "\\n") << "\n"
86
+ str << key.gsub(/( |:|=)/){|m| "\\#{m}"} << " = " << entry.gsub("\n", "\\n") << "\n"
71
87
  end
72
88
 
73
89
  str
@@ -53,6 +53,7 @@ module I18n::Translate::Processor
53
53
  entry["comment"] = get(message, "translatorcomment").to_s.strip
54
54
  entry["translation"] = get(message, "translation")
55
55
  fuzzy = get(message, "translation", "type").to_s.strip
56
+ entry["flag"] = fuzzy if fuzzy == "obsolete"
56
57
  entry["fuzzy"] = true unless fuzzy.empty?
57
58
  flag = get(message, "extra-po-flags").to_s.strip
58
59
  entry["flag"] = flag unless flag.empty?
@@ -77,7 +78,7 @@ module I18n::Translate::Processor
77
78
  <TS version="2.0" language="#{@translate.lang}">
78
79
  EOF
79
80
 
80
- keys = I18n::Translate.hash_to_keys(@translate.default).sort
81
+ keys = I18n::Translate.hash_to_keys(target).sort
81
82
  keys.each do |key|
82
83
  value = @translate.find(key, target)
83
84
  next unless value
@@ -94,7 +95,7 @@ EOF
94
95
  </context>
95
96
  EOF
96
97
  else
97
- fuzzy = ((value["flag"] == "ok") or value["flag"].to_s.strip.empty?) ? "" : %~ type="unfinished"~
98
+ fuzzy = ((value["flag"] == "ok") or value["flag"].to_s.strip.empty?) ? "" : (value["flag"] == "obsolete") ? %~ type="obsolete"~ : %~ type="unfinished"~
98
99
  xml += <<EOF
99
100
  <context>
100
101
  <name>#{::CGI.escapeHTML(key)}</name>
@@ -28,7 +28,7 @@ require 'find'
28
28
  # extracted_comment: po compatibility
29
29
  # file: file where it is # po compatibility
30
30
  # line: the lines, where is this key used # po compatibility
31
- # flag: ok || incomplete || changed || untranslated
31
+ # flag: ok || incomplete || changed || untranslated || obsolete
32
32
  # fuzzy: true # exists only where flag != ok (nice to have when you want
33
33
  # edit files manualy)
34
34
  #
@@ -48,7 +48,7 @@ require 'find'
48
48
  #
49
49
  module I18n::Translate
50
50
 
51
- FLAGS = %w(ok incomplete changed untranslated)
51
+ FLAGS = %w(ok incomplete changed untranslated obsolete)
52
52
  #FORMATS = %w(yml rb po pot ts properties) # the first one is preferred if :format => auto
53
53
  # function I18n::Translate::Processor.init will register known
54
54
  # formats
@@ -103,12 +103,26 @@ module I18n::Translate
103
103
  h = h[chunk]
104
104
  end
105
105
  unless value
106
- h[path[-1]] = nil
106
+ h.delete(path[-1])
107
107
  else
108
108
  h[path[-1]] = value
109
109
  end
110
110
  end
111
111
 
112
+ def self.delete(key, hash, separator=".")
113
+ path = key.split(separator)
114
+ set(key, nil, hash, separator)
115
+ i = path.size - 1
116
+ while i >= 0
117
+ k = path[0..i].join(separator)
118
+ trg = find(k, hash, separator)
119
+ if trg and trg.kind_of?(Hash) and trg.empty?
120
+ set(k, nil, hash, separator)
121
+ end
122
+ i -= 1
123
+ end
124
+ end
125
+
112
126
  # scans :locale_dir for files with valid formats and returns
113
127
  # list of files with locales. If block is given then
114
128
  # it creates Translate object for each entry and pass it to the block
@@ -175,7 +189,8 @@ module I18n::Translate
175
189
  :default => "default", # default name for file containing default app's key => string
176
190
  :force_encoding => true, # in ruby 1.9 forces string encoding
177
191
  :encoding => "utf-8", # encoding name to be forced to
178
- :format => "auto" # auto, rb, yml
192
+ :format => "auto", # auto, rb, yml
193
+ :merge => "soft", # hard or soft: hard strips old keys from target and soft set it to obsolete
179
194
  }
180
195
 
181
196
  attr_reader :default, :target, :merge, :options, :lang, :default_file, :lang_file
@@ -225,8 +240,9 @@ module I18n::Translate
225
240
  # 'old_translation' => '', # if flag == 'changed' then old_translation = t and t = ''
226
241
  # 'translation' => '', # value set in target file
227
242
  # 'comment' => '' # a comment added by a translator
228
- # 'flag' => ok || incomplete || changed || untranslated # set by merging tool except incomplete
229
- # which is set by translator
243
+ # 'flag' => ok || incomplete || changed || untranslated || obsolete
244
+ # # set by merging tool except incomplete
245
+ # which is set by translator
230
246
  # # other keys helded for compatibility with other formats
231
247
  # }
232
248
  def [](key)
@@ -279,6 +295,11 @@ module I18n::Translate
279
295
  I18n::Translate.find(key, hash, separator)
280
296
  end
281
297
 
298
+ def delete(key)
299
+ I18n::Translate.delete(key, @default, @options[:separator])
300
+ I18n::Translate.delete(key, @target, @options[:separator])
301
+ end
302
+
282
303
  # will create path in @target for 'key' and set the 'value'
283
304
  def []=(key, value)
284
305
  I18n::Translate.set(key, value, @target, @options[:separator])
@@ -346,6 +367,32 @@ module I18n::Translate
346
367
 
347
368
  self[key] = trg
348
369
  end
370
+ obsolete!
371
+ end
372
+
373
+ def obsolete!(merge = @options[:merge])
374
+ def_keys = I18n::Translate.hash_to_keys(@default, @options[:separator]).sort
375
+ trg_keys = I18n::Translate.hash_to_keys(@target, @options[:separator]).sort
376
+
377
+ obsolete_keys = trg_keys - def_keys
378
+ obsolete_keys.each do |key|
379
+ if merge == "hard"
380
+ I18n::Translate.delete(key, @target, @options[:separator])
381
+ next
382
+ end
383
+
384
+ trg = find(key)
385
+ next unless trg
386
+
387
+ if trg.kind_of?(String)
388
+ trg = {"translation" => trg, "flag" => "obsolete"}
389
+ else
390
+ trg["flag"] = "obsolete"
391
+ trg["fuzzy"] = true
392
+ end
393
+
394
+ I18n::Translate.set(key, trg, @target, @options[:separator])
395
+ end
349
396
  end
350
397
 
351
398
  # re-read @target data from the disk and create @merge
@@ -380,7 +427,7 @@ module I18n::Translate
380
427
  end
381
428
 
382
429
  # returns statistics hash
383
- # {:total => N, :ok => N, :changed => N, :incomplete => N, :untranslated => N, :fuzzy => N, :progress => N}
430
+ # {:total => N, :ok => N, :changed => N, :obsolete => N, :incomplete => N, :untranslated => N, :fuzzy => N, :progress => N}
384
431
  def stat
385
432
  stat = {
386
433
  :total => @merge.size,
@@ -388,6 +435,7 @@ module I18n::Translate
388
435
  :changed => @merge.select{|e| e["flag"] == "changed"}.size,
389
436
  :incomplete => @merge.select{|e| e["flag"] == "incomplete"}.size,
390
437
  :untranslated => @merge.select{|e| e["flag"] == "untranslated"}.size,
438
+ :obsolete => @merge.select{|e| e["flag"] == "obsolete"}.size,
391
439
  :fuzzy => @merge.select{|e| e["flag"] != "ok"}.size
392
440
  }
393
441
  stat[:progress] = (stat[:ok].to_f / stat[:total].to_f) * 100
data/test/all.rb CHANGED
@@ -8,6 +8,7 @@ $KCODE='UTF8'
8
8
  require 'test/unit'
9
9
  require 'rubygems'
10
10
  require 'yaml'
11
+ require 'pp'
11
12
 
12
13
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__), ".."))
13
14
  require 'lib/i18n-translate'
@@ -64,7 +65,13 @@ def diff(src, trg)
64
65
  src.keys.each { |key| puts "src key: #{key}" unless trg.keys.include?(key) }
65
66
  trg.keys.each { |key| puts "trg key: #{key}" unless src.keys.include?(key) }
66
67
  src.keys.each do |key, value|
67
- puts "#{key} #{src[key].inspect} != #{trg[key].inspect}" if src[key] != trg[key]
68
+ if src[key] != trg[key]
69
+ print "#{key}: "
70
+ pp src[key]
71
+ puts "!="
72
+ pp trg[key]
73
+ end
74
+ #puts "#{key} #{src[key].inspect} != #{trg[key].inspect}" if src[key] != trg[key]
68
75
  end
69
76
  elsif src.kind_of?(Array) and trg.kind_of?(Array)
70
77
  src.each {|k| puts "not in trg '#{k}'" unless trg.include?(k)}
data/test/processor.rb CHANGED
@@ -10,7 +10,7 @@ module I18n::Test
10
10
  def __prepare(processor, file)
11
11
  # prepare object with correct data from yaml
12
12
  @tr = I18n::Translate::Translate.new('cze', {:locale_dir => $src_dir, :default_format => 'yml', :format => 'yml'})
13
- @tr.assign(@tr.target)
13
+ @tr.assign(@tr.merge)
14
14
 
15
15
  # prepare reader and writer
16
16
  @src_file = File.join($src_dir, file)
@@ -13,4 +13,20 @@ class TestBackendProperties < Test::Unit::TestCase
13
13
 
14
14
  include I18n::Test::Backend
15
15
 
16
+ def test_1000_colon_as_assign_operator
17
+ assert_equal( "apple, banana, pear, cantaloupe, watermelon, kiwi, mango", I18n.t("fruits") )
18
+ end
19
+
20
+ def test_1010_space_at_key_begining
21
+ assert_equal( "Beauty", I18n.t("Truth") )
22
+ end
23
+
24
+ def test_1020_escaped_operators_in_key
25
+ assert_equal( "operators", I18n.t("esc=aped:operators") )
26
+ end
27
+
28
+ def test_1030_key_without_assign_and_value
29
+ assert_equal( "translation missing: cze.empty_key", I18n.t("empty_key") )
30
+ end
31
+
16
32
  end
@@ -12,7 +12,7 @@ class TestProcessorTS < Test::Unit::TestCase
12
12
 
13
13
  include I18n::Test::Processor
14
14
 
15
- def test_0030_read_po_to_ts
15
+ def test_1010_read_po_to_ts
16
16
  t = I18n::Translate::Translate.new('cze', {:locale_dir => $src_dir, :default_format => 'yml', :format => 'yml'})
17
17
  file = File.join($src_dir, 'po_to_ts.ts')
18
18
  reader = I18n::Translate::Processor::TS.new(file, t)
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-translators-tools
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
5
4
  prerelease: false
6
5
  segments:
7
6
  - 0
8
7
  - 2
9
- - 3
10
- version: 0.2.3
8
+ - 4
9
+ version: 0.2.4
11
10
  platform: ruby
12
11
  authors:
13
12
  - Petr Kovar
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-07-30 00:00:00 +02:00
17
+ date: 2010-11-29 00:00:00 +01:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -26,12 +25,11 @@ dependencies:
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- hash: 13
30
28
  segments:
31
29
  - 0
32
- - 4
33
- - 1
34
- version: 0.4.1
30
+ - 5
31
+ - 0
32
+ version: 0.5.0
35
33
  type: :runtime
36
34
  version_requirements: *id001
37
35
  - !ruby/object:Gem::Dependency
@@ -42,7 +40,6 @@ dependencies:
42
40
  requirements:
43
41
  - - ">="
44
42
  - !ruby/object:Gem::Version
45
- hash: 3
46
43
  segments:
47
44
  - 0
48
45
  version: "0"
@@ -50,10 +47,13 @@ dependencies:
50
47
  version_requirements: *id002
51
48
  description: |
52
49
  This package brings you useful utility and library which can help you to handle
53
- locale files and translations in your Ruby projects. Offers also built-in simple
54
- console editor. Supported formats are YAML, Ruby, Gettext po, QT Linguist TS and
55
- Java Properties. Read README.md file and run i18n-translate without parameters
56
- for more information.
50
+ locale files and translations in your Ruby projects.
51
+ It is build upon i18n library and extends it's simple format so you can simply
52
+ track field changes or keep translator's notes. Conversion back to simple format
53
+ is possible and as simple as call 'i18n-translate strip'. Offers also built-in
54
+ simple console editor. Supported formats are YAML, Ruby, Gettext po,
55
+ QT Linguist TS and Java Properties. Read README.md file and run i18n-translate
56
+ without parameters for more information.
57
57
 
58
58
  email: pejuko@gmail.com
59
59
  executables:
@@ -74,6 +74,7 @@ files:
74
74
  - lib/i18n/backend/translate.rb
75
75
  - lib/i18n/processor/properties.rb
76
76
  - lib/i18n/processor/ts.rb
77
+ - lib/i18n/processor/gettext_strscan.rb
77
78
  - lib/i18n/processor/ruby.rb
78
79
  - lib/i18n/processor/yaml.rb
79
80
  - lib/i18n/processor/gettext.rb
@@ -124,7 +125,7 @@ post_install_message: |
124
125
 
125
126
  Backends:
126
127
  * Extended format. i18n-translators-tools brings extended format
127
- I18n::Backend::Simple.send(:include, I18n::Backend::Translator)
128
+ I18n::Backend::Simple.send(:include, I18n::Backend::Translate)
128
129
  * Gettext po
129
130
  I18n::Backend::Simple.send(:include, I18n::Backend::PO)
130
131
  * QT Linguist TS
@@ -151,9 +152,19 @@ post_install_message: |
151
152
  v0.2.3
152
153
  * fix: hash_to_keys can work with enhanced format => default can be in
153
154
  enchanced format
155
+ * default file can be now in enhanced format. if translation field is missing
154
156
  * i18n-translate <source file> <target file>
155
157
  automaticlay perform convert action from one file to another
156
158
 
159
+ v0.2.4
160
+ * enhanced support for java properties
161
+ * hard/soft merges. hard deletes deleted keys in target and soft set them to
162
+ obsolete
163
+ * processors now generate keys list from provided data and not from @tr.default
164
+ * flag obsolete added
165
+ * delete function
166
+ * i18n-0.5.0 compatibility (for older i18n user v0.2.3)
167
+
157
168
  For more information read README.md and CHANGELOG.md
158
169
 
159
170
  -----------------------------------------------------------------------------
@@ -171,7 +182,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
171
182
  requirements:
172
183
  - - ">="
173
184
  - !ruby/object:Gem::Version
174
- hash: 3
175
185
  segments:
176
186
  - 0
177
187
  version: "0"
@@ -180,7 +190,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
190
  requirements:
181
191
  - - ">="
182
192
  - !ruby/object:Gem::Version
183
- hash: 3
184
193
  segments:
185
194
  - 0
186
195
  version: "0"