gettext 2.3.3 → 2.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/doc/text/news.md +37 -0
  2. data/gettext.gemspec +1 -0
  3. data/lib/gettext/runtime/mo.rb +382 -0
  4. data/lib/gettext/runtime/mofile.rb +24 -366
  5. data/lib/gettext/runtime/textdomain.rb +17 -17
  6. data/lib/gettext/tools.rb +1 -1
  7. data/lib/gettext/tools/msgfmt.rb +2 -2
  8. data/lib/gettext/tools/msginit.rb +16 -15
  9. data/lib/gettext/tools/msgmerge.rb +258 -255
  10. data/lib/gettext/tools/parser/ruby.rb +24 -24
  11. data/lib/gettext/tools/po.rb +256 -0
  12. data/lib/gettext/tools/po_entry.rb +355 -0
  13. data/lib/gettext/tools/poparser.rb +118 -16
  14. data/lib/gettext/tools/xgettext.rb +56 -58
  15. data/lib/gettext/version.rb +1 -1
  16. data/samples/po/hello.pot +3 -3
  17. data/samples/po/hello2.pot +3 -3
  18. data/samples/po/hello_glade2.pot +3 -3
  19. data/samples/po/hello_gtk2.pot +3 -3
  20. data/samples/po/hello_noop.pot +3 -3
  21. data/samples/po/hello_plural.pot +3 -3
  22. data/samples/po/hello_tk.pot +3 -3
  23. data/src/poparser.ry +111 -9
  24. data/test/parser/test_ruby.rb +17 -13
  25. data/test/po/_.pot +3 -3
  26. data/test/po/backslash.pot +3 -3
  27. data/test/po/non_ascii.pot +3 -3
  28. data/test/po/np_.pot +5 -4
  29. data/test/po/ns_.pot +3 -3
  30. data/test/po/p_.pot +3 -3
  31. data/test/po/s_.pot +3 -3
  32. data/test/po/untranslated.pot +3 -3
  33. data/test/{test_mofile.rb → test_mo.rb} +3 -3
  34. data/test/test_parser.rb +13 -12
  35. data/test/test_po_entry.rb +329 -0
  36. data/test/test_po_parser.rb +209 -8
  37. data/test/tools/test_msginit.rb +0 -2
  38. data/test/tools/test_msgmerge.rb +427 -50
  39. data/test/tools/test_po.rb +487 -0
  40. data/test/tools/test_xgettext.rb +1 -1
  41. metadata +28 -45
  42. data/data/locale/de/LC_MESSAGES/gettext.mo +0 -0
  43. data/data/locale/de/LC_MESSAGES/rgettext.mo +0 -0
  44. data/data/locale/el/LC_MESSAGES/gettext.mo +0 -0
  45. data/data/locale/el/LC_MESSAGES/rgettext.mo +0 -0
  46. data/data/locale/sr/LC_MESSAGES/gettext.mo +0 -0
  47. data/data/locale/sr/LC_MESSAGES/rgettext.mo +0 -0
  48. data/data/locale/uk/LC_MESSAGES/gettext.mo +0 -0
  49. data/data/locale/uk/LC_MESSAGES/rgettext.mo +0 -0
  50. data/lib/gettext/tools/pomessage.rb +0 -232
  51. data/samples/locale/bg/LC_MESSAGES/hello_gtk.mo +0 -0
  52. data/samples/locale/bs/LC_MESSAGES/hello_gtk.mo +0 -0
  53. data/samples/locale/ca/LC_MESSAGES/hello_gtk.mo +0 -0
  54. data/samples/locale/cs/LC_MESSAGES/hello_gtk.mo +0 -0
  55. data/samples/locale/de/LC_MESSAGES/hello_gtk.mo +0 -0
  56. data/samples/locale/el/LC_MESSAGES/hello_gtk.mo +0 -0
  57. data/samples/locale/eo/LC_MESSAGES/hello_gtk.mo +0 -0
  58. data/samples/locale/es/LC_MESSAGES/hello_gtk.mo +0 -0
  59. data/samples/locale/fr/LC_MESSAGES/hello_gtk.mo +0 -0
  60. data/samples/locale/hr/LC_MESSAGES/hello_gtk.mo +0 -0
  61. data/samples/locale/hu/LC_MESSAGES/hello_gtk.mo +0 -0
  62. data/samples/locale/it/LC_MESSAGES/hello_gtk.mo +0 -0
  63. data/samples/locale/ja/LC_MESSAGES/hello_gtk.mo +0 -0
  64. data/samples/locale/ko/LC_MESSAGES/hello_gtk.mo +0 -0
  65. data/samples/locale/lv/LC_MESSAGES/hello_gtk.mo +0 -0
  66. data/samples/locale/nb/LC_MESSAGES/hello_gtk.mo +0 -0
  67. data/samples/locale/nl/LC_MESSAGES/hello_gtk.mo +0 -0
  68. data/samples/locale/pt_BR/LC_MESSAGES/hello_gtk.mo +0 -0
  69. data/samples/locale/ru/LC_MESSAGES/hello_gtk.mo +0 -0
  70. data/samples/locale/sr/LC_MESSAGES/hello_gtk.mo +0 -0
  71. data/samples/locale/sv/LC_MESSAGES/hello_gtk.mo +0 -0
  72. data/samples/locale/uk/LC_MESSAGES/hello_gtk.mo +0 -0
  73. data/samples/locale/vi/LC_MESSAGES/hello_gtk.mo +0 -0
  74. data/samples/locale/zh/LC_MESSAGES/hello_gtk.mo +0 -0
  75. data/samples/locale/zh_TW/LC_MESSAGES/hello_gtk.mo +0 -0
  76. data/test/po/ascii.pot +0 -23
  77. data/test/po/no_exist_msgid.pot +0 -20
  78. data/test/po/not_existed_msgid.pot +0 -20
  79. data/test/test_po_message.rb +0 -118
@@ -14,7 +14,7 @@
14
14
  =end
15
15
 
16
16
  require 'gettext/core_ext/string'
17
- require 'gettext/runtime/mofile'
17
+ require 'gettext/runtime/mo'
18
18
  require 'gettext/runtime/locale_path'
19
19
 
20
20
  module GetText
@@ -72,26 +72,26 @@ module GetText
72
72
 
73
73
  lang_key = lang.to_s
74
74
 
75
- mofile = nil
75
+ mo = nil
76
76
  if self.class.cached?
77
- mofile = @mofiles[lang_key]
77
+ mo = @mofiles[lang_key]
78
78
  end
79
- unless mofile
80
- mofile = load_mo(lang)
79
+ unless mo
80
+ mo = load_mo(lang)
81
81
  end
82
82
 
83
- if (! mofile) or (mofile ==:empty)
83
+ if (! mo) or (mo ==:empty)
84
84
  return nil
85
85
  end
86
86
 
87
- return mofile[msgid] if mofile.has_key?(msgid)
87
+ return mo[msgid] if mo.has_key?(msgid)
88
88
 
89
89
  ret = nil
90
90
  if msgid.include?("\000")
91
91
  # Check "aaa\000bbb" and show warning but return the singular part.
92
92
  msgid_single = msgid.split("\000")[0]
93
93
  msgid_single_prefix_re = /^#{Regexp.quote(msgid_single)}\000/
94
- mofile.each do |key, val|
94
+ mo.each do |key, val|
95
95
  if msgid_single_prefix_re =~ key
96
96
  # Usually, this is not caused to make po-files from rgettext.
97
97
  separated_msgid = msgid.gsub(/\000/, '", "')
@@ -106,7 +106,7 @@ module GetText
106
106
  end
107
107
  else
108
108
  msgid_prefix_re = /^#{Regexp.quote(msgid)}\000/
109
- mofile.each do |key, val|
109
+ mo.each do |key, val|
110
110
  if msgid_prefix_re =~ key
111
111
  ret = val.split("\000")[0]
112
112
  break
@@ -132,8 +132,8 @@ module GetText
132
132
  ret = nil
133
133
  elsif msg.include?("\000")
134
134
  # [[msgstr[0], msgstr[1], msgstr[2],...], cond]
135
- mofile = @mofiles[lang.to_posix.to_s]
136
- cond = (mofile and mofile != :empty) ? mofile.plural_as_proc : DEFAULT_PLURAL_CALC
135
+ mo = @mofiles[lang.to_posix.to_s]
136
+ cond = (mo and mo != :empty) ? mo.plural_as_proc : DEFAULT_PLURAL_CALC
137
137
  ret = [msg.split("\000"), cond]
138
138
  else
139
139
  ret = [[msg], DEFAULT_SINGLE_CALC]
@@ -160,21 +160,21 @@ module GetText
160
160
  lang = lang.to_posix unless lang.kind_of? Locale::Tag::Posix
161
161
  lang_key = lang.to_s
162
162
 
163
- mofile = @mofiles[lang_key]
164
- if mofile
165
- if mofile == :empty
163
+ mo = @mofiles[lang_key]
164
+ if mo
165
+ if mo == :empty
166
166
  return :empty
167
167
  elsif ! self.class.cached?
168
- mofile.update!
168
+ mo.update!
169
169
  end
170
- return mofile
170
+ return mo
171
171
  end
172
172
 
173
173
  path = @locale_path.current_path(lang)
174
174
 
175
175
  if path
176
176
  charset = @output_charset || lang.charset || Locale.charset || "UTF-8"
177
- @mofiles[lang_key] = MoFile.open(path, charset)
177
+ @mofiles[lang_key] = MO.open(path, charset)
178
178
  else
179
179
  @mofiles[lang_key] = :empty
180
180
  end
data/lib/gettext/tools.rb CHANGED
@@ -29,7 +29,7 @@ require 'gettext/tools/xgettext'
29
29
  require 'gettext/tools/msgfmt'
30
30
  require 'gettext/tools/msginit'
31
31
  require 'gettext/tools/msgmerge'
32
- require 'gettext/runtime/mofile'
32
+ require 'gettext/runtime/mo'
33
33
  require 'fileutils'
34
34
 
35
35
  module GetText
@@ -51,8 +51,8 @@ module GetText
51
51
  def run(*options) # :nodoc:
52
52
  initialize_arguments(*options)
53
53
 
54
- parser = PoParser.new
55
- data = MoFile.new
54
+ parser = POParser.new
55
+ data = MO.new
56
56
 
57
57
  parser.parse_file(@input_file, data)
58
58
  data.save_to_file(@output_file)
@@ -66,14 +66,14 @@ module GetText
66
66
  parse_arguments(*arguments)
67
67
  validate
68
68
 
69
- parser = PoParser.new
69
+ parser = POParser.new
70
70
  parser.ignore_fuzzy = false
71
71
  pot = parser.parse_file(@input_file,
72
- GetText::Tools::MsgMerge::PoData.new)
72
+ GetText::PO.new)
73
73
  po = replace_pot_header(pot)
74
74
 
75
75
  File.open(@output_file, "w") do |f|
76
- f.puts(po.generate_po)
76
+ f.puts(po.to_s)
77
77
  end
78
78
  end
79
79
 
@@ -169,15 +169,16 @@ module GetText
169
169
  end
170
170
 
171
171
  def replace_pot_header(pot) #:nodoc:
172
- @entry = pot[""]
173
- @comment = pot.comment("")
172
+ @entry = pot[""].msgstr
173
+ @comment = pot[""].translator_comment
174
174
  @translator = translator_info
175
175
 
176
176
  replace_entry
177
177
  replace_comment
178
178
 
179
- pot[""] = @entry.chomp
180
- pot.set_comment("", @comment)
179
+ pot[""] = @entry
180
+ pot[""].translator_comment = @comment
181
+ pot[""].flag = pot[""].flag.gsub(/\Afuzzy\z/, "")
181
182
  pot
182
183
  end
183
184
 
@@ -239,11 +240,11 @@ module GetText
239
240
  replace_description
240
241
  replace_first_author
241
242
  replace_copyright_year
242
- @comment = @comment.gsub(/#, fuzzy/, "")
243
+ @comment = @comment.gsub(/^fuzzy$/, "")
243
244
  end
244
245
 
245
246
  EMAIL = "EMAIL@ADDRESS"
246
- FIRST_AUTHOR_KEY = /^(\s*#\s*) FIRST AUTHOR <#{EMAIL}>, (\d+\.)$/
247
+ FIRST_AUTHOR_KEY = /^FIRST AUTHOR <#{EMAIL}>, (\d+\.)$/
247
248
 
248
249
  def replace_last_translator #:nodoc:
249
250
  unless @translator.nil?
@@ -273,9 +274,9 @@ module GetText
273
274
  def replace_plural_forms #:nodoc:
274
275
  plural_entry = plural_forms(@language)
275
276
  if PLURAL_FORMS =~ @entry
276
- @entry = @entry.gsub(PLURAL_FORMS, "\\1 #{plural_entry}")
277
+ @entry = @entry.gsub(PLURAL_FORMS, "\\1 #{plural_entry}\n")
277
278
  else
278
- @entry << "Plural-Forms: #{plural_entry}"
279
+ @entry << "Plural-Forms: #{plural_entry}\n"
279
280
  end
280
281
  end
281
282
 
@@ -329,7 +330,7 @@ module GetText
329
330
  "nplurals=#{nplural}; plural=#{plural_expression};"
330
331
  end
331
332
 
332
- DESCRIPTION_TITLE = /^(\s*#\s*) SOME DESCRIPTIVE TITLE\.$/
333
+ DESCRIPTION_TITLE = /^SOME DESCRIPTIVE TITLE\.$/
333
334
 
334
335
  def replace_description #:nodoc:
335
336
  language_name = Locale::Info.get_language(@language).name
@@ -342,18 +343,18 @@ module GetText
342
343
  @comment = @comment.gsub(DESCRIPTION_TITLE, "\\1 #{description}")
343
344
  end
344
345
 
345
- YEAR_KEY = /^(\s*#\s* FIRST AUTHOR <#{EMAIL}>,) YEAR\.$/
346
+ YEAR_KEY = /^(FIRST AUTHOR <#{EMAIL}>,) YEAR\.$/
346
347
  LAST_TRANSLATOR_KEY = /^(Last-Translator:) FULL NAME <#{EMAIL}>$/
347
348
 
348
349
  def replace_first_author #:nodoc:
349
350
  @comment = @comment.gsub(YEAR_KEY, "\\1 #{year}.")
350
351
  unless @translator.nil?
351
352
  @comment = @comment.gsub(FIRST_AUTHOR_KEY,
352
- "\\1 #{@translator}, \\2")
353
+ "#{@translator}, \\1")
353
354
  end
354
355
  end
355
356
 
356
- COPYRIGHT_KEY = /(\s*#\s* Copyright \(C\)) YEAR (THE PACKAGE'S COPYRIGHT HOLDER)$/
357
+ COPYRIGHT_KEY = /^(Copyright \(C\)) YEAR (THE PACKAGE'S COPYRIGHT HOLDER)$/
357
358
  def replace_copyright_year #:nodoc:
358
359
  @comment = @comment.gsub(COPYRIGHT_KEY, "\\1 #{year} \\2")
359
360
  end
@@ -20,64 +20,110 @@
20
20
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
21
 
22
22
  require "optparse"
23
+ require "levenshtein"
23
24
  require "gettext"
24
25
  require "gettext/tools/poparser"
25
- require "gettext/tools/pomessage"
26
-
27
- # TODO: MsgMerge should use PoMessage to generate PO content.
26
+ require "gettext/tools/po"
28
27
 
29
28
  module GetText
30
29
  module Tools
31
30
  class MsgMerge
32
31
  class PoData #:nodoc:
33
32
 
34
- attr_reader :msgids
33
+ attr_reader :po
35
34
 
36
35
  def initialize
37
- @msgid2msgstr = {}
38
- @msgid2comment = {}
39
- @msgids = []
36
+ @po = PO.new
40
37
  end
41
38
 
42
- def set_comment(msgid_or_sym, comment)
43
- @msgid2comment[msgid_or_sym] = comment
39
+ def set_comment(msgid, comments, msgctxt=nil)
40
+ entry = generate_entry(msgid)
41
+
42
+ if msgid == :last
43
+ entry.comment = comments
44
+ return
45
+ end
46
+
47
+ comments.each_line do |_line|
48
+ line = _line.chomp
49
+ entry = parse_comment(line, entry)
50
+ end
44
51
  end
45
52
 
46
53
  def msgstr(msgid)
47
- @msgid2msgstr[msgid]
54
+ self[msgid]
48
55
  end
49
56
 
50
57
  def comment(msgid)
51
- @msgid2comment[msgid]
58
+ msgctxt, msgid, _ = split_msgid(msgid)
59
+ id = [msgctxt, msgid]
60
+ entry = @po[*id]
61
+ return nil if entry.nil?
62
+
63
+ formatted_comments = entry.format_translator_comment
64
+ formatted_comments << entry.format_extracted_comment
65
+ formatted_comments << entry.format_reference_comment
66
+ formatted_comments << entry.format_flag_comment
67
+ formatted_comments << entry.format_previous_comment
68
+
69
+ unless entry.comment.nil?
70
+ formatted_comments = entry.format_comment("#", entry.comment)
71
+ end
72
+
73
+ formatted_comments.chomp
52
74
  end
53
75
 
54
76
  def [](msgid)
55
- @msgid2msgstr[msgid]
77
+ msgctxt, msgid, _ = split_msgid(msgid)
78
+ @po[msgctxt, msgid].msgstr
56
79
  end
57
80
 
58
- def []=(msgid, msgstr)
59
- # Retain the order
60
- unless @msgid2msgstr.has_key?(msgid)
61
- @msgids << msgid
81
+ def []=(msgid, value)
82
+ msgctxt, msgid, msgid_plural = split_msgid(msgid)
83
+ id = [msgctxt, msgid]
84
+
85
+ if value.instance_of?(POEntry)
86
+ @po[*id] = value
87
+ return value
62
88
  end
63
89
 
64
- @msgid2msgstr[msgid] = msgstr
90
+ msgstr = value
91
+ if @po.has_key?(*id)
92
+ @po[*id] = msgstr
93
+ @po[*id].msgctxt = msgctxt
94
+ @po[*id].msgid_plural = msgid_plural
95
+ else
96
+ type = detect_entry_type(msgctxt, msgid_plural)
97
+ entry = POEntry.new(type)
98
+ entry.msgctxt = msgctxt
99
+ entry.msgid = msgid
100
+ entry.msgid_plural = msgid_plural
101
+ entry.msgstr = msgstr
102
+ @po[*id] = entry
103
+ entry
104
+ end
65
105
  end
66
106
 
67
107
  def each_msgid
68
- msgids = @msgids.delete_if do |msgid|
69
- msgid.kind_of?(Symbol) or msgid.empty?
108
+ msgids.each do |id|
109
+ next if id.kind_of?(Symbol) or id.empty?
110
+ yield(id)
70
111
  end
112
+ end
71
113
 
72
- msgids.each do |msgid|
73
- yield(msgid)
114
+ def msgids
115
+ @po.collect do |entry|
116
+ msgctxt = entry.msgctxt
117
+ msgid = entry.msgid
118
+ generate_original_string(msgctxt, msgid)
74
119
  end
75
120
  end
76
121
 
77
122
  def msgid?(msgid)
78
123
  return false if msgid.kind_of?(Symbol)
79
124
  return true if msgid.empty?
80
- @msgid2msgstr.has_key?(msgid)
125
+ msgctxt, msgid, _ = split_msgid(msgid)
126
+ @po.has_key?(msgctxt, msgid)
81
127
  end
82
128
 
83
129
  # Is it necessary to implement this method?
@@ -86,9 +132,9 @@ module GetText
86
132
  end
87
133
 
88
134
  def nplurals
89
- return 0 if @msgid2msgstr[""].nil?
135
+ return 0 if @po[""].msgstr.nil?
90
136
 
91
- if /\s*nplurals\s*=\s*(\d+)/ =~ @msgid2msgstr[""]
137
+ if /\s*nplurals\s*=\s*(\d+)/ =~ @po[""].msgstr
92
138
  return $1.to_i
93
139
  else
94
140
  return 0
@@ -96,81 +142,12 @@ module GetText
96
142
  end
97
143
 
98
144
  def generate_po
99
- str = ""
100
- str << generate_po_header
101
- str << "\n"
102
-
103
- po_entries = []
104
- self.each_msgid do |id|
105
- po_entries << self.generate_po_entry(id)
106
- end
107
-
108
- unless @msgid2comment[:last].nil?
109
- po_entries << "#{@msgid2comment[:last]}\n"
110
- end
111
-
112
- str << po_entries.join("\n")
113
- str
114
- end
115
-
116
- def generate_po_header
117
- str = ""
118
-
119
- str << @msgid2comment[""].strip << "\n"
120
- str << 'msgid ""' << "\n"
121
- str << 'msgstr ""' << "\n"
122
- msgstr = @msgid2msgstr[""].gsub(/"/, '\"').gsub(/\r/, "")
123
- msgstr = msgstr.gsub(/^(.*)$/, '"\1\n"')
124
- str << msgstr.chomp
125
- str << "\n"
126
-
127
- str
145
+ @po.to_s
128
146
  end
129
147
 
130
148
  def generate_po_entry(msgid)
131
- str = ""
132
- str << @msgid2comment[msgid]
133
- if str[-1] != "\n"[0]
134
- str << "\n"
135
- end
136
-
137
- id = msgid.gsub(/\r/, "")
138
- msgstr = @msgid2msgstr[msgid]
139
- if msgstr.nil?
140
- msgstr = ""
141
- else
142
- msgstr = msgstr.gsub(/\r/, "")
143
- end
144
-
145
- if id.include?("\004")
146
- ids = id.split(/\004/)
147
- context = ids[0]
148
- id = ids[1]
149
- str << "msgctxt " << __conv(context) << "\n"
150
- end
151
-
152
- if id.include?("\000")
153
- ids = id.split(/\000/)
154
- str << "msgid " << __conv(ids[0]) << "\n"
155
- ids[1..-1].each do |single_id|
156
- str << "msgid_plural " << __conv(single_id) << "\n"
157
- end
158
-
159
- if msgstr.empty?
160
- nplurals.times do |id_count|
161
- str << "msgstr[#{id_count}] " << '""' << "\n"
162
- end
163
- else
164
- msgstr.split("\000", -1).each_with_index do |m, n|
165
- str << "msgstr[#{n}] " << __conv(m) << "\n"
166
- end
167
- end
168
- else
169
- str << "msgid " << __conv(id) << "\n"
170
- str << "msgstr " << __conv(msgstr) << "\n"
171
- end
172
-
173
- str
149
+ msgctxt, msgid, _ = split_msgid(msgid)
150
+ @po[msgctxt, msgid].to_s
174
151
  end
175
152
 
176
153
  def __conv(str)
@@ -189,209 +166,235 @@ module GetText
189
166
  end
190
167
 
191
168
  def escape(string)
192
- PoMessage.escape(string)
169
+ POEntry.escape(string)
170
+ end
171
+
172
+ private
173
+ def split_msgid(msgid)
174
+ return [nil, msgid, nil] if msgid == :last
175
+ return [nil, "", nil] if msgid.empty?
176
+ msgctxt, msgid = msgid.split("\004", 2)
177
+ if msgid.nil?
178
+ msgid = msgctxt
179
+ msgctxt = nil
180
+ end
181
+ msgid, msgid_plural = msgid.split("\000", 2)
182
+ [msgctxt, msgid, msgid_plural]
183
+ end
184
+
185
+ def generate_original_string(msgctxt, msgid)
186
+ return msgid if msgid == :last
187
+ original_string = ""
188
+ msgid_plural = @po[msgctxt, msgid].msgid_plural
189
+ original_string << "#{msgctxt}\004" unless msgctxt.nil?
190
+ original_string << msgid
191
+ original_string << "\000#{msgid_plural}" unless msgid_plural.nil?
192
+ original_string
193
+ end
194
+
195
+ def detect_entry_type(msgctxt, msgid_plural)
196
+ if msgctxt.nil?
197
+ if msgid_plural.nil?
198
+ :normal
199
+ else
200
+ :plural
201
+ end
202
+ else
203
+ if msgid_plural.nil?
204
+ :msgctxt
205
+ else
206
+ :msgctxt_plural
207
+ end
208
+ end
209
+ end
210
+
211
+ def generate_entry(msgid)
212
+ msgctxt, msgid, _ = split_msgid(msgid)
213
+ id = [msgctxt, msgid]
214
+ @po[*id] = nil unless @po.has_key?(*id)
215
+ entry = @po[*id]
216
+
217
+ entry.translator_comment = ""
218
+ entry.extracted_comment = ""
219
+ entry.references = []
220
+ entry.flag = ""
221
+ entry.previous = ""
222
+ entry
223
+ end
224
+
225
+ def parse_comment(line, entry)
226
+ if line == "#"
227
+ entry.translator_comment << ""
228
+ elsif /\A(#.)\s*(.*)\z/ =~ line
229
+ mark = $1
230
+ content = $2
231
+ case mark
232
+ when POParser::TRANSLATOR_COMMENT_MARK
233
+ entry.translator_comment << "#{content}\n"
234
+ when POParser::EXTRACTED_COMMENT_MARK
235
+ entry.extracted_comment << "#{content}\n"
236
+ when POParser::REFERENCE_COMMENT_MARK
237
+ entry.references << content
238
+ when POParser::FLAG_MARK
239
+ entry.flag << "#{content}\n"
240
+ when POParser::PREVIOUS_MSGID_COMMENT_MARK
241
+ entry.previous << "#{content}\n"
242
+ else
243
+ entry.comment << line
244
+ end
245
+ end
246
+ entry
193
247
  end
194
248
  end
195
249
 
196
250
  class Merger #:nodoc:
197
- # From GNU gettext source.
198
- #
199
251
  # Merge the reference with the definition: take the #. and
200
252
  # #: comments from the reference, take the # comments from
201
253
  # the definition, take the msgstr from the definition. Add
202
254
  # this merged entry to the output message list.
203
255
 
204
- DOT_COMMENT_RE = /\A#\./
205
- SEMICOLON_COMMENT_RE = /\A#\:/
206
- FUZZY_RE = /\A#\,/
207
- NOT_SPECIAL_COMMENT_RE = /\A#([^:.,]|\z)/
208
-
209
- CRLF_RE = /\r?\n/
210
256
  POT_DATE_EXTRACT_RE = /POT-Creation-Date:\s*(.*)?\s*$/
211
257
  POT_DATE_RE = /POT-Creation-Date:.*?$/
212
258
 
213
259
  def merge(definition, reference)
214
- definition.each_msgid do |msgid|
215
- msgstr = definition[msgid] || ""
216
- definition[msgid] = msgstr
217
- end
260
+ result = GetText::PO.new
218
261
 
219
- reference.each_msgid do |msgid|
220
- msgstr = reference[msgid] || ""
221
- reference[msgid] = msgstr
222
- end
262
+ reference.each do |entry|
263
+ msgid = entry.msgid
264
+ msgctxt = entry.msgctxt
265
+ id = [msgctxt, msgid]
223
266
 
224
- # deep copy
225
- result = Marshal.load( Marshal.dump(reference) )
226
-
227
- used = []
228
- merge_header(result, definition)
229
-
230
- result.each_msgid do |msgid|
231
- if definition.msgid?(msgid)
232
- used << msgid
233
- merge_message(msgid, result, msgid, definition)
234
- elsif other_msgid = definition.search_msgid_fuzzy(msgid, used)
235
- used << other_msgid
236
- merge_fuzzy_message(msgid, result, other_msgid, definition)
237
- elsif msgid.index("\000") and (reference.msgstr(msgid).gsub("\000", "").empty?)
238
- # plural
239
- result[msgid] = ([""] * definition.nplurals).join("\000")
240
- else
241
- change_reference_comment(msgid, result)
267
+ if definition.has_key?(*id)
268
+ result[*id] = merge_entry(definition[*id], entry)
269
+ next
242
270
  end
243
- end
244
271
 
245
- ###################################################################
246
- # msgids which are not used in reference are handled as obsolete. #
247
- ###################################################################
248
- last_comment = result.comment(:last) || ""
249
- definition.each_msgid do |msgid|
250
- unless used.include?(msgid)
251
- last_comment << "\n"
252
- last_comment << definition.generate_po_entry(msgid).strip.gsub(/^/, "#. ")
253
- last_comment << "\n"
272
+ if msgctxt.nil?
273
+ same_msgid_entry = find_by_msgid(definition, msgid)
274
+ if not same_msgid_entry.nil? and not same_msgid_entry.msgctxt.nil?
275
+ result[nil, msgid] = merge_fuzzy_entry(same_msgid_entry, entry)
276
+ next
277
+ end
254
278
  end
279
+
280
+ fuzzy_entry = find_fuzzy_entry(definition, msgid, msgctxt)
281
+ unless fuzzy_entry.nil?
282
+ result[*id] = merge_fuzzy_entry(fuzzy_entry, entry)
283
+ next
284
+ end
285
+
286
+ result[*id] = entry
255
287
  end
256
- result.set_comment(:last, last_comment) unless last_comment.empty?
257
288
 
289
+ add_obsolete_entry(result, definition)
258
290
  result
259
291
  end
260
292
 
261
- def merge_message(msgid, target, def_msgid, definition)
262
- merge_comment(msgid, target, def_msgid, definition)
293
+ def merge_entry(definition_entry, reference_entry)
294
+ if definition_entry.msgid.empty? and definition_entry.msgctxt.nil?
295
+ new_header = merge_header(definition_entry, reference_entry)
296
+ return new_header
297
+ end
263
298
 
264
- ############################################
265
- # check mismatch of msgid and msgid_plural #
266
- ############################################
267
- def_msgstr = definition[def_msgid]
268
- if msgid.index("\000")
269
- if def_msgstr.index("\000")
270
- # OK
271
- target[msgid] = def_msgstr
272
- else
273
- # NG
274
- strings = []
275
- definition.nplurals.times do
276
- strings << def_msgstr
277
- end
278
- target[msgid] = strings.join("\000")
279
- end
280
- else
281
- if def_msgstr.index("\000")
282
- # NG
283
- target[msgid] = def_msgstr.split("\000")[0]
284
- else
285
- # OK
286
- target[msgid] = def_msgstr
287
- end
299
+ if definition_entry.flag == "fuzzy"
300
+ entry = definition_entry
301
+ entry.flag = "fuzzy"
302
+ return entry
288
303
  end
289
- end
290
304
 
291
- # for the future
292
- def merge_fuzzy_message(msgid, target, def_msgid, definition)
293
- merge_message(msgid, target, def_msgid, definition)
294
- end
305
+ entry = reference_entry
306
+ entry.translator_comment = definition_entry.translator_comment
307
+ entry.previous = nil
295
308
 
296
- def merge_comment(msgid, target, def_msgid, definition)
297
- ref_comment = target.comment(msgid)
298
- def_comment = definition.comment(def_msgid)
309
+ unless definition_entry.msgid_plural == reference_entry.msgid_plural
310
+ entry.flag = "fuzzy"
311
+ end
299
312
 
300
- normal_comment = []
301
- dot_comment = []
302
- semi_comment = []
303
- is_fuzzy = false
313
+ entry.msgstr = definition_entry.msgstr
314
+ entry
315
+ end
304
316
 
305
- def_comment.split(CRLF_RE).each do |l|
306
- if NOT_SPECIAL_COMMENT_RE =~ l
307
- normal_comment << l
308
- end
317
+ def merge_header(old_header, new_header)
318
+ header = old_header
319
+ if POT_DATE_EXTRACT_RE =~ new_header.msgstr
320
+ create_date = $1
321
+ pot_creation_date = "POT-Creation-Date: #{create_date}"
322
+ header.msgstr = header.msgstr.gsub(POT_DATE_RE, pot_creation_date)
309
323
  end
324
+ header.flag = nil
325
+ header
326
+ end
310
327
 
311
- ref_comment.split(CRLF_RE).each do |l|
312
- if DOT_COMMENT_RE =~ l
313
- dot_comment << l
314
- elsif SEMICOLON_COMMENT_RE =~ l
315
- semi_comment << l
316
- elsif FUZZY_RE =~ l
317
- is_fuzzy = true if msgid != ""
318
- end
328
+ def find_by_msgid(entries, msgid)
329
+ same_msgid_entries = entries.find_all do |entry|
330
+ entry.msgid == msgid
319
331
  end
332
+ same_msgid_entries = same_msgid_entries.sort_by do |entry|
333
+ entry.msgctxt
334
+ end
335
+ same_msgid_entries.first
336
+ end
320
337
 
321
- str = format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
322
- target.set_comment(msgid, str)
338
+ def merge_fuzzy_entry(fuzzy_entry, entry)
339
+ merged_entry = merge_entry(fuzzy_entry, entry)
340
+ merged_entry.flag = "fuzzy"
341
+ merged_entry
323
342
  end
324
343
 
325
- def change_reference_comment(msgid, podata)
326
- normal_comment = []
327
- dot_comment = []
328
- semi_comment = []
329
- is_fuzzy = false
344
+ MAX_FUZZY_DISTANCE = 0.5 # XXX: make sure that its value is proper.
330
345
 
331
- podata.comment(msgid).split(CRLF_RE).each do |l|
332
- if DOT_COMMENT_RE =~ l
333
- dot_comment << l
334
- elsif SEMICOLON_COMMENT_RE =~ l
335
- semi_comment << l
336
- elsif FUZZY_RE =~ l
337
- is_fuzzy = true
338
- else
339
- normal_comment << l
346
+ def find_fuzzy_entry(definition, msgid, msgctxt)
347
+ min_distance_entry = nil
348
+ min_distance = MAX_FUZZY_DISTANCE
349
+
350
+ same_msgctxt_entries = definition.find_all do |entry|
351
+ entry.msgctxt == msgctxt
352
+ end
353
+ same_msgctxt_entries.each do |entry|
354
+ distance = Levenshtein.normalized_distance(entry.msgid, msgid)
355
+ if min_distance > distance
356
+ min_distance = distance
357
+ min_distance_entry = entry
340
358
  end
341
359
  end
342
360
 
343
- str = format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
344
- podata.set_comment(msgid, str)
361
+ min_distance_entry
345
362
  end
346
363
 
347
- def format_comment(normal_comment, dot_comment, semi_comment, is_fuzzy)
348
- str = ""
349
-
350
- str << normal_comment.join("\n").gsub(/^#(\s*)/) do |sss|
351
- if $1 == ""
352
- "# "
353
- else
354
- sss
355
- end
356
- end
357
- if normal_comment.size > 0
358
- str << "\n"
364
+ def add_obsolete_entry(result, definition)
365
+ obsolete_entry = generate_obsolete_entry(result, definition)
366
+ unless obsolete_entry.nil?
367
+ result[:last] = obsolete_entry
359
368
  end
369
+ result
370
+ end
360
371
 
361
- str << dot_comment.join("\n").gsub(/^#.(\s*)/) do |sss|
362
- if $1 == ""
363
- "#. "
364
- else
365
- sss
366
- end
367
- end
368
- if dot_comment.size > 0
369
- str << "\n"
370
- end
372
+ def generate_obsolete_entry(result, definition)
373
+ obsolete_entry = nil
371
374
 
372
- str << semi_comment.join("\n").gsub(/^#:\s*/, "#: ")
373
- if semi_comment.size > 0
374
- str << "\n"
375
- end
375
+ obsolete_entries = extract_obsolete_entries(result, definition)
376
+ unless obsolete_entries.empty?
377
+ obsolete_comment = ""
376
378
 
377
- if is_fuzzy
378
- str << "#, fuzzy\n"
379
+ obsolete_entries.each do |entry|
380
+ obsolete_comment << entry.to_s
381
+ end
382
+ obsolete_entry = POEntry.new(:normal)
383
+ obsolete_entry.msgid = :last
384
+ obsolete_entry.comment = obsolete_comment
379
385
  end
380
-
381
- str
386
+ obsolete_entry
382
387
  end
383
388
 
384
- def merge_header(target, definition)
385
- merge_comment("", target, "", definition)
386
-
387
- msg = target.msgstr("")
388
- def_msg = definition.msgstr("")
389
- if POT_DATE_EXTRACT_RE =~ msg
390
- time = $1
391
- def_msg = def_msg.sub(POT_DATE_RE, "POT-Creation-Date: #{time}")
389
+ def extract_obsolete_entries(result, definition)
390
+ obsolete_entries = []
391
+ definition.each do |entry|
392
+ id = [entry.msgctxt, entry.msgid]
393
+ unless result.has_key?(*id)
394
+ obsolete_entries << entry
395
+ end
392
396
  end
393
-
394
- target[""] = def_msg
397
+ obsolete_entries
395
398
  end
396
399
  end
397
400
 
@@ -500,10 +503,10 @@ module GetText
500
503
  def run(*options) #:nodoc:
501
504
  config = check_command_line_options(*options)
502
505
 
503
- parser = PoParser.new
506
+ parser = POParser.new
504
507
  parser.ignore_fuzzy = false
505
- defpo = parser.parse_file(config.defpo, PoData.new)
506
- refpot = parser.parse_file(config.refpot, PoData.new)
508
+ defpo = parser.parse_file(config.defpo, PO.new)
509
+ refpot = parser.parse_file(config.refpot, PO.new)
507
510
 
508
511
  merger = Merger.new
509
512
  result = merger.merge(defpo, refpot)
@@ -512,10 +515,10 @@ module GetText
512
515
 
513
516
  if config.output.is_a?(String)
514
517
  File.open(File.expand_path(config.output), "w+") do |file|
515
- file.write(result.generate_po)
518
+ file.write(result.to_s)
516
519
  end
517
520
  else
518
- puts(result.generate_po)
521
+ puts(result.to_s)
519
522
  end
520
523
  end
521
524
  end