i18n-translators-tools 0.1.1 → 0.2

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.
@@ -20,9 +20,9 @@ module I18n
20
20
  def translate(locale, key, options = {})
21
21
  result = super(locale, key, options)
22
22
  return result unless result.kind_of?(Hash)
23
- return nil unless result[:t]
23
+ return nil unless result[:t] or result[:translation]
24
24
 
25
- tr = result[:t]
25
+ tr = result[:translation] || result[:t]
26
26
  values = options.except(*I18n::Backend::Base::RESERVED_KEYS)
27
27
 
28
28
  tr = resolve(locale, key, tr, options)
@@ -0,0 +1,19 @@
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
+ module I18n::Backend
7
+
8
+ # to use QT linguist TS files you should just include this backend
9
+ #
10
+ # I18n::Backend::Simple.send(:include, I18n::Backend::TS)
11
+ module TS
12
+ protected
13
+ def load_ts(fname)
14
+ locale = ::File.basename(fname, '.ts')
15
+ tr = I18n::Translate::Translate.new(locale, {:empty => true})
16
+ data = I18n::Translate::Processor::TS.new(fname, tr).read
17
+ end
18
+ end
19
+ end
@@ -37,6 +37,12 @@ module I18n::Translate
37
37
  nil
38
38
  end
39
39
 
40
+ def self.init(default='yml')
41
+ @processors.each do |processor|
42
+ processor.register(default)
43
+ end
44
+ end
45
+
40
46
 
41
47
  class Template
42
48
  FORMAT = []
@@ -50,6 +56,21 @@ module I18n::Translate
50
56
  def initialize(fname, tr)
51
57
  @filename = fname
52
58
  @translate = tr
59
+ fname =~ %r{/?([^/]+)\.[^\.]+$}i
60
+ @lang = $1.to_s.strip
61
+ end
62
+
63
+ def self.register(default='yml')
64
+ self::FORMAT.each do |format|
65
+ unless I18n::Translate::FORMATS.include?(format)
66
+ # default format will be first
67
+ if default == format
68
+ I18n::Translate::FORMATS.unshift(format)
69
+ else
70
+ I18n::Translate::FORMATS << format
71
+ end
72
+ end
73
+ end
53
74
  end
54
75
 
55
76
  def read
@@ -68,7 +89,7 @@ module I18n::Translate
68
89
  end
69
90
 
70
91
  def self.can_handle?(fname)
71
- fname =~ %r{\.([^\.]+)$}
92
+ fname =~ %r{\.([^\.]+)$}i
72
93
  self::FORMAT.include?($1)
73
94
  end
74
95
 
@@ -82,6 +103,35 @@ module I18n::Translate
82
103
  data
83
104
  end
84
105
 
106
+ def uninspect(str)
107
+ return nil unless str
108
+ 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|
109
+ repl = ""
110
+ if ['\\', '#', '"'].include?($1)
111
+ repl = $1
112
+ else
113
+ repl = eval("\"\\#{$1}\"")
114
+ end
115
+ repl
116
+ end
117
+ end
118
+
119
+ # convert old and shorthand fields
120
+ def migrate(data)
121
+ keys = I18n::Translate.hash_to_keys(data, @translate.options[:separator])
122
+ keys.each do |key|
123
+ k, prop = $1, $2 if key =~ /(.*)\.([^\.]+)$/
124
+ next unless prop or k
125
+ next unless ["old", "t"].include?(prop)
126
+ entry = I18n::Translate.find(k, data, @translate.options[:separator])
127
+ value = entry.delete(prop)
128
+ prop = (prop == "old") ? "old_default" : "translation"
129
+ entry[prop] = value
130
+ I18n::Translate.set(k, entry, data, @translate.options[:separator])
131
+ end
132
+ data
133
+ end
134
+
85
135
  def mode(m)
86
136
  mode = m.dup
87
137
  mode << ":" << @translate.options[:encoding] if defined?(Encoding)
@@ -96,4 +146,9 @@ end
96
146
  require 'i18n/processor/yaml'
97
147
  require 'i18n/processor/ruby'
98
148
  require 'i18n/processor/gettext'
149
+ require 'i18n/processor/ts'
150
+ require 'i18n/processor/properties'
151
+
99
152
 
153
+ # initialize all registred processors
154
+ I18n::Translate::Processor.init
@@ -6,7 +6,7 @@
6
6
  module I18n::Translate::Processor
7
7
 
8
8
  class Gettext < Template
9
- FORMAT = ['po']
9
+ FORMAT = ['po', 'pot']
10
10
 
11
11
  protected
12
12
 
@@ -20,6 +20,7 @@ module I18n::Translate::Processor
20
20
  # empty line starts new entry
21
21
  if line =~ %r{^\s*$}
22
22
  if not entry.empty? and key
23
+ key = entry["default"].dup unless key
23
24
  I18n::Translate.set(key, entry, hash, @translate.options[:separator])
24
25
  end
25
26
  key = nil
@@ -34,9 +35,17 @@ module I18n::Translate::Processor
34
35
  when %r{^# (.*)$}
35
36
  entry["comment"] = $1.to_s.strip
36
37
 
37
- # translator's comment
38
+ # extracted comment
39
+ when %r{^#\. (.*)$}
40
+ entry["extracted_comment"] = $1.to_s.strip
41
+
42
+ # reference
38
43
  when %r{^#: (.*)$}
39
- entry["line"] = $1.to_s.strip
44
+ entry["reference"] = $1.to_s.strip
45
+ if entry["reference"] =~ %r{^(.*):(\d+)$}
46
+ entry["file"] = $1.to_s.strip
47
+ entry["line"] = $2.to_s.strip
48
+ end
40
49
 
41
50
  # flag
42
51
  when %r{^#, (.*)$}
@@ -47,34 +56,52 @@ module I18n::Translate::Processor
47
56
  else
48
57
  flags.delete_if{|x| not I18n::Translate::FLAGS.include?(x)}
49
58
  entry["flag"] = flags.first unless flags.empty?
59
+ entry["fuzzy"] = true
50
60
  end
51
61
 
52
62
  # old default
53
- when %r{^#\| msgid (.*)$}
54
- entry["old"] = $1.to_s.strip
63
+ when %r{^#\| msgid "(.*)"$}
64
+ entry["old_default"] = $1.to_s
65
+ # expect that this entry has no key
66
+ # if does, will be overwriten later
67
+ key = entry["old_default"].dup
55
68
 
56
- # key
69
+ # key (context)
57
70
  when %r{^msgctxt "(.*)"$}
58
71
  key = $1.to_s.strip
59
72
  last = "key"
60
73
 
61
74
  # default
62
75
  when %r{^msgid "(.*)"$}
63
- last = "default"
64
- entry[last] = $1.to_s.strip
76
+ if $1.to_s.strip.empty?
77
+ last = "po-header"
78
+ else
79
+ last = "default"
80
+ entry[last] = uninspect($1.to_s)
81
+ key = entry[last].dup unless key
82
+ end
65
83
 
66
84
  # translation
67
85
  when %r{^msgstr "(.*)"$}
68
- last = "t"
69
- entry[last] = $1.to_s.strip
86
+ last = "translation" unless last == "po-header"
87
+ entry[last] = uninspect($1.to_s)
70
88
 
71
89
  # string continuation
72
90
  when %r{^"(.*)"$}
73
91
  if last == "key"
74
- key = "#{key}#{$1.to_s.strip}"
92
+ key = "#{key}#{$1}"
93
+ elsif last == "po-header"
94
+ case $1
95
+ when %r{^Content-Type: text/plain; charset=(.*)$}
96
+ enc = uninspect($1.to_s).strip
97
+ @translate.options[:encoding] = enc unless enc.empty?
98
+ when %r{^X-Language: (.*)$}
99
+ # skip language is set from filename
100
+ end
75
101
  elsif last
76
- entry[last] = "#{entry[last]}#{$1.to_s.strip}"
102
+ entry[last] = "#{entry[last]}#{uninspect($1)}"
77
103
  end
104
+
78
105
  end
79
106
  end
80
107
 
@@ -87,31 +114,45 @@ module I18n::Translate::Processor
87
114
  end
88
115
 
89
116
 
90
- # this export ignores data
91
117
  def export(data)
118
+ target = data[@translate.lang]
92
119
  str = ""
93
120
  keys = I18n::Translate.hash_to_keys(@translate.default).sort
94
121
 
122
+ str << %~msgid ""\n~
123
+ str << %~msgstr ""\n~
124
+ str << %~"Content-Type: text/plain; charset=#{@translate.options[:encoding]}\\n"\n~
125
+ str << %~"X-Language: #{@translate.lang}\\n"\n~
95
126
  keys.each do |key|
96
127
  entry = [""]
97
- value = @translate.find(key, @translate.target)
128
+ value = @translate.find(key, target)
98
129
  next unless value
99
130
 
100
131
  if value.kind_of?(String)
101
- entry << %~msgctxt #{key.inspect}~
102
- entry << %~msgid #{@translate.find(key, @translate.default).to_s.inspect}~
132
+ # leave out msgctxt if using po strings as a key
133
+ default = @translate.find(key, @translate.default)
134
+ entry << %~msgctxt #{key.inspect}~ if key != default
135
+ entry << %~msgid #{default.to_s.inspect}~
103
136
  entry << %~msgstr #{value.to_s.inspect}~
104
137
  else
105
- entry << %~# #{value["comment"]}~ unless value["comment"].to_s.empty?
106
- entry << %~#: #{value["line"]}~ unless value["line"].to_s.empty?
138
+ entry << %~# #{value["comment"].to_s.strip}~ unless value["comment"].to_s.strip.empty?
139
+ entry << %~#. #{value["extracted_comment"].to_s.strip}~ unless value["extracted_comment"].to_s.strip.empty?
140
+ if not value["reference"].to_s.strip.empty?
141
+ entry << %~#: #{value["reference"].to_s.strip}~
142
+ elsif value["file"] or value["line"]
143
+ entry << %~#: #{value["file"].to_s.strip}:#{value["line"].to_s.strip}~
144
+ end
145
+ key_default = nil
146
+ key_default = value["default"] if value["default"] == key
147
+ key_default = value["old_default"] if value["old_default"] == key
107
148
  flags = []
108
- flags << "fuzzy" if value["fuzzy"]
149
+ flags << "fuzzy" if (not value["flag"].nil?) and (value["flag"] != "ok")
109
150
  flags << value["flag"] unless value["flag"].to_s.strip.empty?
110
151
  entry << %~#, #{flags.join(", ")}~ unless flags.empty?
111
- entry << %~#| msgid #{value["old"]}~ unless value["old"].to_s.empty?
112
- entry << %~msgctxt #{key.inspect}~
152
+ entry << %~#| msgid #{value["old_default"].to_s.inspect}~ unless value["old_default"].to_s.empty?
153
+ entry << %~msgctxt #{key.inspect}~ if key != key_default
113
154
  entry << %~msgid #{value["default"].to_s.inspect}~
114
- entry << %~msgstr #{value["t"].to_s.inspect}~
155
+ entry << %~msgstr #{value["translation"].to_s.inspect}~
115
156
  end
116
157
 
117
158
  entry << ""
@@ -0,0 +1,78 @@
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
+ module I18n::Translate::Processor
7
+
8
+ class Properties < Template
9
+ FORMAT = ['properties']
10
+
11
+ protected
12
+
13
+ def import(data)
14
+ hash = {}
15
+
16
+ key = nil
17
+ value = nil
18
+ line_number = 0
19
+ data.each_line do |line|
20
+ line_number += 1
21
+ # skip empty line and comments
22
+ next if (line =~ %r{^\s*$}) or (line =~ %r{^#.*})
23
+
24
+ case line
25
+ # multiline string
26
+ when %r{^([^=]+)\s*=\s*(.*)\\$}
27
+ key = $1.to_s.strip
28
+ value = $2.to_s.strip
29
+
30
+ # continuous multiline string
31
+ when %r{^\s*([^=]+)\\$}
32
+ value << $1.to_s.strip
33
+
34
+ # end of continuous string
35
+ when %r{^\s*([^=]+)$}
36
+ value << $1.to_s.strip
37
+ I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
38
+ value = nil
39
+
40
+ # simple key = value
41
+ when %r{^([^=]+)\s*=\s*(.*)$}
42
+ key, value = $1.to_s.strip, $2.to_s.strip
43
+ I18n::Translate.set(key, uninspect(value), hash, @translate.options[:separator])
44
+ end
45
+ end
46
+
47
+ {@lang => hash}
48
+ end
49
+
50
+
51
+ # this export ignores data
52
+ def export(data)
53
+ target = data[@translate.lang]
54
+ str = ""
55
+ keys = I18n::Translate.hash_to_keys(@translate.default).sort
56
+
57
+ keys.each do |key|
58
+ value = @translate.find(key, target)
59
+ next unless value
60
+ entry = ""
61
+
62
+
63
+ if value.kind_of?(String)
64
+ entry = value.strip
65
+ else
66
+ entry = value["translation"].to_s.strip
67
+ end
68
+
69
+ # create record in format: key = value
70
+ str << key << " = " << entry.gsub("\n", "\\n") << "\n"
71
+ end
72
+
73
+ str
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -11,7 +11,7 @@ module I18n::Translate::Processor
11
11
  protected
12
12
 
13
13
  def import(data)
14
- eval(data)
14
+ migrate(eval(data))
15
15
  end
16
16
 
17
17
  # serialize hash to string
@@ -0,0 +1,149 @@
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 'rexml/document'
7
+ require 'cgi'
8
+
9
+ module I18n::Translate::Processor
10
+
11
+ class TS < Template
12
+ FORMAT = ['ts']
13
+
14
+ protected
15
+
16
+ def get(xml, path, key=nil)
17
+ elements = []
18
+ xml.elements.each(path) {|e| elements << e}
19
+ element = elements.first
20
+ ret = ""
21
+ ret = element.get_text.to_s if element and not key
22
+ ret = element.attributes[key].to_s.strip if element and key
23
+ ret.gsub!("&apos;", "'")
24
+ ret = uninspect(::CGI::unescapeHTML(ret))
25
+ ret
26
+ end
27
+
28
+ def import(data)
29
+ hash = {}
30
+ xml = ::REXML::Document.new(data)
31
+ lang = @translate.lang
32
+ lang = get(xml, "TS", "language").to_s.strip if lang.empty?
33
+ xml.elements.each("//TS/context") do |context|
34
+ key = get(context, "name")
35
+ context.elements.each("message") do |message|
36
+ entry = {}
37
+ entry["file"] = get(message, "location", "filename").to_s.strip
38
+ entry["line"] = get(message, "location", "line").to_s.strip
39
+ unless entry["file"].empty? or entry["line"].empty?
40
+ entry["reference"] = "#{entry['file']}:#{entry['line']}"
41
+ end
42
+ if key.to_s.strip.empty?
43
+ # this happen if you use linguist on converting po to ts
44
+ # context is saved as a comment
45
+ key = get(message, "comment").to_s.strip
46
+ # if converted from po using linguist, there is one message
47
+ # without key
48
+ #raise "No key for message: #{message.to_s}" if key.empty?
49
+ end
50
+ entry["default"] = get(message, "source")
51
+ entry["old_default"] = get(message, "oldsource")
52
+ entry["extracted_comment"] = get(message, "extracomment").to_s.strip
53
+ entry["comment"] = get(message, "translatorcomment").to_s.strip
54
+ entry["translation"] = get(message, "translation")
55
+ fuzzy = get(message, "translation", "type").to_s.strip
56
+ entry["fuzzy"] = true unless fuzzy.empty?
57
+ flag = get(message, "extra-po-flags").to_s.strip
58
+ entry["flag"] = flag unless flag.empty?
59
+ entry.delete_if {|k,v| v.to_s.empty?}
60
+ if key
61
+ # not key means it can be header of po file converted to ts
62
+ I18n::Translate.set(key, entry, hash, @translate.options[:separator])
63
+ end
64
+ key = nil
65
+ end
66
+ end
67
+ {lang => hash}
68
+ end
69
+
70
+
71
+ # serialize hash to XML
72
+ def export(data, indent=0)
73
+ target = data[@translate.lang]
74
+ xml = <<EOF
75
+ <?xml version="1.0" encoding="#{@translate.options[:encoding]}"?>
76
+ <!DOCTYPE TS>
77
+ <TS version="2.0" language="#{@translate.lang}">
78
+ EOF
79
+
80
+ keys = I18n::Translate.hash_to_keys(@translate.default).sort
81
+ keys.each do |key|
82
+ value = @translate.find(key, target)
83
+ next unless value
84
+
85
+ if value.kind_of?(String)
86
+ fuzzy = (value.to_s.empty?) ? %~ type="unfinished"~ : ""
87
+ xml += <<EOF
88
+ <context>
89
+ <name>#{::CGI.escapeHTML(key)}</name>
90
+ <message>
91
+ <source>#{::CGI.escapeHTML(@translate.find(key, @translate.default).to_s)}</source>
92
+ <translation#{fuzzy}>#{::CGI.escapeHTML(value.to_s)}</translation>
93
+ </message>
94
+ </context>
95
+ EOF
96
+ else
97
+ fuzzy = ((value["flag"] == "ok") or value["flag"].to_s.strip.empty?) ? "" : %~ type="unfinished"~
98
+ xml += <<EOF
99
+ <context>
100
+ <name>#{::CGI.escapeHTML(key)}</name>
101
+ <message>
102
+ EOF
103
+ if value["file"] or value["line"]
104
+ xml += <<EOF
105
+ <location filename="#{::CGI.escapeHTML(value["file"].to_s)}" line="#{::CGI.escapeHTML(value["line"].to_s)}" />
106
+ EOF
107
+ end
108
+ xml += <<EOF
109
+ <source>#{::CGI.escapeHTML(value["default"].to_s)}</source>
110
+ EOF
111
+ unless value["old_default"].to_s.empty?
112
+ xml += <<EOF
113
+ <oldsource>#{::CGI.escapeHTML(value["old_default"].to_s)}</oldsource>
114
+ EOF
115
+ end
116
+ unless value["extracted_comment"].to_s.empty?
117
+ xml += <<EOF
118
+ <extracomment>#{::CGI.escapeHTML(value["extracted_comment"].to_s)}</extracomment>
119
+ EOF
120
+ end
121
+ unless value["comment"].to_s.empty?
122
+ xml += <<EOF
123
+ <translatorcomment>#{::CGI.escapeHTML(value["comment"].to_s)}</translatorcomment>
124
+ EOF
125
+ end
126
+ xml += <<EOF
127
+ <translation#{fuzzy}>#{::CGI.escapeHTML(value["translation"].to_s)}</translation>
128
+ EOF
129
+ unless value["flag"].to_s.strip.empty?
130
+ xml += <<EOF
131
+ <extra-po-flags>#{::CGI.escapeHTML(value["flag"].to_s.strip)}</extra-po-flags>
132
+ EOF
133
+ end
134
+ xml += <<EOF
135
+ </message>
136
+ </context>
137
+ EOF
138
+ end
139
+ end
140
+
141
+ xml += <<EOF
142
+ </TS>
143
+ EOF
144
+ xml
145
+ end
146
+
147
+ end
148
+
149
+ end