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
@@ -13,7 +13,7 @@
13
13
 
14
14
  require 'irb/ruby-lex.rb'
15
15
  require 'stringio'
16
- require 'gettext/tools/pomessage'
16
+ require 'gettext/tools/po_entry'
17
17
 
18
18
  module GetText
19
19
  class RubyLexX < RubyLex # :nodoc: all
@@ -79,9 +79,9 @@ module GetText
79
79
 
80
80
  end
81
81
 
82
- # Extends PoMessage for RubyParser.
82
+ # Extends POEntry for RubyParser.
83
83
  # Implements a sort of state machine to assist the parser.
84
- module PoMessageForRubyParser
84
+ module POEntryForRubyParser
85
85
  # Supports parsing by setting attributes by and by.
86
86
  def set_current_attribute(str)
87
87
  param = @param_type[@param_number]
@@ -98,8 +98,8 @@ module GetText
98
98
  @param_number += 1
99
99
  end
100
100
  end
101
- class PoMessage
102
- include PoMessageForRubyParser
101
+ class POEntry
102
+ include POEntryForRubyParser
103
103
  alias :initialize_old :initialize
104
104
  def initialize(type)
105
105
  initialize_old(type)
@@ -117,7 +117,7 @@ module GetText
117
117
 
118
118
  # (Since 2.1.0) the 2nd parameter is deprecated
119
119
  # (and ignored here).
120
- # And You don't need to keep the pomessages as unique.
120
+ # And You don't need to keep the poentries as unique.
121
121
 
122
122
  def parse(path) # :nodoc:
123
123
  source = IO.read(path)
@@ -142,14 +142,14 @@ module GetText
142
142
  end
143
143
 
144
144
  def parse_lines(path, lines) # :nodoc:
145
- pomessages = []
145
+ po = []
146
146
  file = StringIO.new(lines.join + "\n")
147
147
  rl = RubyLexX.new
148
148
  rl.set_input(file)
149
149
  rl.skip_space = true
150
150
  #rl.readed_auto_clean_up = true
151
151
 
152
- pomessage = nil
152
+ po_entry = nil
153
153
  line_no = nil
154
154
  last_comment = ''
155
155
  reset_comment = false
@@ -160,32 +160,32 @@ module GetText
160
160
  ignore_next_comma = false
161
161
  case tk
162
162
  when RubyToken::TkIDENTIFIER, RubyToken::TkCONSTANT
163
- store_pomessage(pomessages, pomessage, path, line_no, last_comment)
163
+ store_po_entry(po, po_entry, path, line_no, last_comment)
164
164
  if ID.include?(tk.name)
165
- pomessage = PoMessage.new(:normal)
165
+ po_entry = POEntry.new(:normal)
166
166
  elsif PLURAL_ID.include?(tk.name)
167
- pomessage = PoMessage.new(:plural)
167
+ po_entry = POEntry.new(:plural)
168
168
  elsif MSGCTXT_ID.include?(tk.name)
169
- pomessage = PoMessage.new(:msgctxt)
169
+ po_entry = POEntry.new(:msgctxt)
170
170
  elsif MSGCTXT_PLURAL_ID.include?(tk.name)
171
- pomessage = PoMessage.new(:msgctxt_plural)
171
+ po_entry = POEntry.new(:msgctxt_plural)
172
172
  else
173
- pomessage = nil
173
+ po_entry = nil
174
174
  end
175
175
  line_no = tk.line_no.to_s
176
176
  when RubyToken::TkSTRING, RubyToken::TkDSTRING
177
- pomessage.set_current_attribute tk.value if pomessage
177
+ po_entry.set_current_attribute tk.value if po_entry
178
178
  when RubyToken::TkPLUS, RubyToken::TkNL
179
179
  #do nothing
180
180
  when RubyToken::TkINTEGER
181
181
  ignore_next_comma = true
182
182
  when RubyToken::TkCOMMA
183
183
  unless ignore_current_comma
184
- pomessage.advance_to_next_attribute if pomessage
184
+ po_entry.advance_to_next_attribute if po_entry
185
185
  end
186
186
  else
187
- if store_pomessage(pomessages, pomessage, path, line_no, last_comment)
188
- pomessage = nil
187
+ if store_po_entry(po, po_entry, path, line_no, last_comment)
188
+ po_entry = nil
189
189
  last_comment = ""
190
190
  end
191
191
  end
@@ -217,7 +217,7 @@ module GetText
217
217
  reset_comment = true
218
218
  end
219
219
  end
220
- pomessages
220
+ po
221
221
  end
222
222
 
223
223
  def target?(file) # :nodoc:
@@ -225,11 +225,11 @@ module GetText
225
225
  end
226
226
 
227
227
  private
228
- def store_pomessage(pomessages, pomessage, file_name, line_no, last_comment) #:nodoc:
229
- if pomessage && pomessage.msgid
230
- pomessage.sources << file_name + ":" + line_no
231
- pomessage.add_comment(last_comment) unless last_comment.empty?
232
- pomessages << pomessage
228
+ def store_po_entry(po, po_entry, file_name, line_no, last_comment) #:nodoc:
229
+ if po_entry && po_entry.msgid
230
+ po_entry.references << file_name + ":" + line_no
231
+ po_entry.add_comment(last_comment) unless last_comment.empty?
232
+ po << po_entry
233
233
  true
234
234
  else
235
235
  false
@@ -0,0 +1,256 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
4
+ #
5
+ # License: Ruby's or LGPL
6
+ #
7
+ # This library is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This library is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "gettext/tools/po_entry"
21
+
22
+ module GetText
23
+
24
+ # PO stores PO entries like Hash. Each key of {POEntry} is msgctxt
25
+ # and msgid.
26
+ # PO[msgctxt, msgid] returns the {POEntry} containing msgctxt and
27
+ # msgid.
28
+ # If you specify msgid only, msgctxt is treated as nonexistent.
29
+ #
30
+ # @since 2.3.4
31
+ class PO
32
+ include Enumerable
33
+
34
+ class NonExistentEntryError < StandardError
35
+ end
36
+
37
+ # @!attribute [rw] order
38
+ # The order is used to sort PO entries(objects of {POEntry}) in
39
+ # {#to_s}.
40
+ # @param [Symbol] order the name as order by sort.
41
+ # Now :reference is allowed only.
42
+ # @return [Symbol] the name as order by sort.
43
+ attr_accessor :order
44
+
45
+ def initialize(order=nil)
46
+ @order = order || :references
47
+ @entries = {}
48
+ end
49
+
50
+ # Returns {POEntry} containing msgctxt and msgid.
51
+ # If you specify one argument, it is treated as msgid.
52
+ # @overload [](msgid)
53
+ # @!macro [new] po.[].argument
54
+ # @param [String] msgid msgid contained returning {POEntry}.
55
+ # @return [POEntry]
56
+ # @!macro po.[].argument
57
+ # @overload [](msgctxt, msgid)
58
+ # @!macro po.[].argument
59
+ # @param [String] msgid msgid contained returning {POEntry}.
60
+ def [](msgctxt, msgid=nil)
61
+ if msgid.nil?
62
+ msgid = msgctxt
63
+ msgctxt = nil
64
+ end
65
+
66
+ @entries[[msgctxt, msgid]]
67
+ end
68
+
69
+ # Stores {POEntry} or msgstr binding msgctxt and msgid. If you
70
+ # specify msgstr, this method creates {POEntry} containing it.
71
+ # If you specify the two argument, the first argument is treated
72
+ # as msgid.
73
+ #
74
+ # @overload []=(msgid, po_entry)
75
+ # @!macro [new] po.store.entry.arguments
76
+ # @param [String] msgid msgid binded {POEntry}.
77
+ # @param [POEntry] po_entry stored {POEntry}.
78
+ # @!macro po.store.entry.arguments
79
+ # @overload []=(msgctxt, msgid, po_entry)
80
+ # @param [String] msgctxt msgctxt binded {POEntry}.
81
+ # @!macro po.store.entry.arguments
82
+ # @overload []=(msgid, msgstr)
83
+ # @!macro [new] po.store.msgstr.arguments
84
+ # @param [String] msgid msgid binded {POEntry}.
85
+ # @param [String] msgstr msgstr contained {POEntry} stored PO.
86
+ # This {POEntry} is generated in this method.
87
+ # @!macro po.store.msgstr.arguments
88
+ # @overload []=(msgctxt, msgid, msgstr)
89
+ # @param [String] msgctxt msgctxt binded {POEntry}.
90
+ # @!macro po.store.msgstr.arguments
91
+ def []=(*arguments)
92
+ case arguments.size
93
+ when 2
94
+ msgctxt = nil
95
+ msgid = arguments[0]
96
+ value = arguments[1]
97
+ when 3
98
+ msgctxt = arguments[0]
99
+ msgid = arguments[1]
100
+ value = arguments[2]
101
+ else
102
+ raise(ArgumentError,
103
+ "[]=: wrong number of arguments(#{arguments.size} for 2..3)")
104
+ end
105
+
106
+ id = [msgctxt, msgid]
107
+ if value.instance_of?(POEntry)
108
+ @entries[id] = value
109
+ return(value)
110
+ end
111
+
112
+ msgstr = value
113
+ if @entries.has_key?(id)
114
+ entry = @entries[id]
115
+ else
116
+ if msgctxt.nil?
117
+ entry = POEntry.new(:normal)
118
+ else
119
+ entry = POEntry.new(:msgctxt)
120
+ end
121
+ @entries[id] = entry
122
+ end
123
+ entry.msgctxt = msgctxt
124
+ entry.msgid = msgid
125
+ entry.msgstr = msgstr
126
+ entry
127
+ end
128
+
129
+ # Returns if PO stores {POEntry} containing msgctxt and msgid.
130
+ # If you specify one argument, it is treated as msgid and msgctxt
131
+ # is nil.
132
+ #
133
+ # @overload has_key?(msgid)
134
+ # @!macro [new] po.has_key?.arguments
135
+ # @param [String] msgid msgid contained {POEntry} checked if it be
136
+ # stored PO.
137
+ # @!macro po.has_key?.arguments
138
+ # @overload has_key?(msgctxt, msgid)
139
+ # @param [String] msgctxt msgctxt contained {POEntry} checked if
140
+ # it be stored PO.
141
+ # @!macro po.has_key?.arguments
142
+ def has_key?(*arguments)
143
+ case arguments.size
144
+ when 1
145
+ msgctxt = nil
146
+ msgid = arguments[0]
147
+ when 2
148
+ msgctxt = arguments[0]
149
+ msgid = arguments[1]
150
+ else
151
+ message = "has_key?: wrong number of arguments " +
152
+ "(#{arguments.size} for 1..2)"
153
+ raise(ArgumentError, message)
154
+ end
155
+ id = [msgctxt, msgid]
156
+ @entries.has_key?(id)
157
+ end
158
+
159
+ # Calls block once for each {POEntry} as a block parameter.
160
+ # @overload each(&block)
161
+ # @yield [entry]
162
+ # @yieldparam [POEntry] entry {POEntry} in PO.
163
+ # @overload each
164
+ # @return [Enumerator] Returns Enumerator for {POEntry}.
165
+ def each
166
+ if block_given?
167
+ @entries.each do |_, entry|
168
+ yield(entry)
169
+ end
170
+ else
171
+ @entries.each_value
172
+ end
173
+ end
174
+
175
+ # For {PoParer}.
176
+ def set_comment(msgid, comment, msgctxt=nil)
177
+ id = [msgctxt, msgid]
178
+ self[*id] = nil unless @entries.has_key?(id)
179
+ self[*id].comment = comment
180
+ end
181
+
182
+ # Formats each {POEntry} to the format of PO files and returns joined
183
+ # them.
184
+ # @see http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
185
+ # The description for Format of PO in GNU gettext manual
186
+ # @return [String] Formatted and joined PO entries. It is used for
187
+ # creating .po files.
188
+ def to_s
189
+ po_string = ""
190
+
191
+ header_entry = @entries[[nil, ""]]
192
+ po_string << header_entry.to_s unless header_entry.nil?
193
+
194
+ content_entries = @entries.reject do |(msgctxt, msgid), _|
195
+ msgid == :last or msgid.empty?
196
+ end
197
+
198
+ sort_by_order(content_entries).each do |msgid, entry|
199
+ po_string << "\n" << entry.to_s
200
+ end
201
+
202
+ if @entries.has_key?([nil, :last])
203
+ po_string << "\n" << @entries[[nil, :last]].to_s
204
+ end
205
+
206
+ po_string
207
+ end
208
+
209
+ private
210
+ def sort_by_order(entries)
211
+ case @order
212
+ when :references
213
+ sorted_entries = sort_by_references(entries)
214
+ when :msgid
215
+ # TODO: sort by msgid alphabetically.
216
+ else
217
+ sorted_entries = entries.to_a
218
+ end
219
+ end
220
+
221
+ def sort_by_references(entries)
222
+ entries.each do |_, entry|
223
+ entry.references = entry.references.sort do |reference, other|
224
+ compare_references(reference, other)
225
+ end
226
+ end
227
+
228
+ entries.sort do |msgid_entry, other_msgid_entry|
229
+ # msgid_entry = [[msgctxt, msgid], POEntry]
230
+ entry_first_reference = msgid_entry[1].references.first
231
+ other_first_reference = other_msgid_entry[1].references.first
232
+ compare_references(entry_first_reference, other_first_reference)
233
+ end
234
+ end
235
+
236
+ def compare_references(reference, other)
237
+ entry_source, entry_line_number = split_reference(reference)
238
+ other_source, other_line_number = split_reference(other)
239
+
240
+ if entry_source != other_source
241
+ entry_source <=> other_source
242
+ else
243
+ entry_line_number <=> other_line_number
244
+ end
245
+ end
246
+
247
+ def split_reference(reference)
248
+ return ["", -1] if reference.nil?
249
+ if /\A(.+):(\d+?)\z/ =~ reference
250
+ [$1, $2.to_i]
251
+ else
252
+ [reference, -1]
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,355 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ # Copyright (C) 2010 masone (Christian Felder) <ema@rh-productions.ch>
5
+ # Copyright (C) 2009 Masao Mutoh
6
+ #
7
+ # License: Ruby's or LGPL
8
+ #
9
+ # This library is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU Lesser General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This library is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU Lesser General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU Lesser General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ module GetText
23
+ class ParseError < StandardError
24
+ end
25
+
26
+ # Contains data related to the expression or sentence that
27
+ # is to be translated.
28
+ class POEntry
29
+ class InvalidTypeError < StandardError
30
+ end
31
+
32
+ class NoMsgidError < StandardError
33
+ end
34
+
35
+ class NoMsgctxtError < StandardError
36
+ end
37
+
38
+ class NoMsgidPluralError < StandardError
39
+ end
40
+
41
+ PARAMS = {
42
+ :normal => [:msgid, :separator, :msgstr],
43
+ :plural => [:msgid, :msgid_plural, :separator, :msgstr],
44
+ :msgctxt => [:msgctxt, :msgid, :msgstr],
45
+ :msgctxt_plural => [:msgctxt, :msgid, :msgid_plural, :msgstr]
46
+ }
47
+
48
+ class << self
49
+ def escape(string)
50
+ string.gsub(/([\\"\n])/) do
51
+ special_character = $1
52
+ if special_character == "\n"
53
+ "\\n"
54
+ else
55
+ "\\#{special_character}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ @@max_line_length = 70
62
+
63
+ # Sets the max line length.
64
+ def self.max_line_length=(len)
65
+ @@max_line_length = len
66
+ end
67
+
68
+ # Gets the max line length.
69
+ def self.max_line_length
70
+ @@max_line_length
71
+ end
72
+
73
+ # Required
74
+ attr_reader :type # :normal, :plural, :msgctxt, :msgctxt_plural
75
+ attr_accessor :msgid
76
+ attr_accessor :msgstr
77
+ # Options
78
+ attr_accessor :msgid_plural
79
+ attr_accessor :separator
80
+ attr_accessor :msgctxt
81
+ attr_accessor :references # ["file1:line1", "file2:line2", ...]
82
+ attr_accessor :translator_comment
83
+ attr_accessor :extracted_comment
84
+ attr_accessor :flag
85
+ attr_accessor :previous
86
+ attr_accessor :comment
87
+
88
+ # Create the object. +type+ should be :normal, :plural, :msgctxt or :msgctxt_plural.
89
+ def initialize(type)
90
+ self.type = type
91
+ @translator_comment = nil
92
+ @extracted_comment = nil
93
+ @references = []
94
+ @flag = nil
95
+ @previous = nil
96
+ @msgctxt = nil
97
+ @msgid = nil
98
+ @msgid_plural = nil
99
+ @msgstr = nil
100
+ end
101
+
102
+ # Support for extracted comments. Explanation s.
103
+ # http://www.gnu.org/software/gettext/manual/gettext.html#Names
104
+ def add_comment(new_comment)
105
+ if (new_comment and ! new_comment.empty?)
106
+ @extracted_comment ||= ""
107
+ @extracted_comment += new_comment
108
+ end
109
+ to_s
110
+ end
111
+
112
+ # Returns a parameter representation suitable for po-files
113
+ # and other purposes.
114
+ def escaped(param_name)
115
+ escape(send(param_name))
116
+ end
117
+
118
+ # Checks if the self has same attributes as other.
119
+ def ==(other)
120
+ not other.nil? and
121
+ type == other.type and
122
+ msgid == other.msgid and
123
+ msgstr == other.msgstr and
124
+ msgid_plural == other.msgid_plural and
125
+ separator == other.separator and
126
+ msgctxt == other.msgctxt and
127
+ translator_comment == other.translator_comment and
128
+ extracted_comment == other.extracted_comment and
129
+ references == other.references and
130
+ flag == other.flag and
131
+ previous == other.previous and
132
+ comment == other.comment
133
+ end
134
+
135
+ def type=(type)
136
+ unless PARAMS.has_key?(type)
137
+ raise(InvalidTypeError, "\"%s\" is invalid type." % type)
138
+ end
139
+ @type = type
140
+ @param_type = PARAMS[@type]
141
+ end
142
+
143
+ # Checks if the other translation target is mergeable with
144
+ # the current one. Relevant are msgid and translation context (msgctxt).
145
+ def mergeable?(other)
146
+ other && other.msgid == self.msgid && other.msgctxt == self.msgctxt
147
+ end
148
+
149
+ # Merges two translation targets with the same msgid and returns the merged
150
+ # result. If one is declared as plural and the other not, then the one
151
+ # with the plural wins.
152
+ def merge(other)
153
+ return self unless other
154
+ raise ParseError, "Translation targets do not match: \n" \
155
+ " self: #{self.inspect}\n other: '#{other.inspect}'" unless self.mergeable?(other)
156
+ if other.msgid_plural && !self.msgid_plural
157
+ res = other
158
+ unless (res.references.include? self.references[0])
159
+ res.references += self.references
160
+ res.add_comment(self.extracted_comment)
161
+ end
162
+ else
163
+ res = self
164
+ unless (res.references.include? other.references[0])
165
+ res.references += other.references
166
+ res.add_comment(other.extracted_comment)
167
+ end
168
+ end
169
+ res
170
+ end
171
+
172
+ # Output the po entry for the po-file.
173
+ def to_s
174
+ raise(NoMsgidError, "msgid is nil.") unless @msgid
175
+
176
+ str = ""
177
+ # extracted comments
178
+ if @msgid == :last
179
+ return format_comment("#~", comment)
180
+ end
181
+
182
+ str << format_translator_comment
183
+ str << format_extracted_comment
184
+ str << format_reference_comment
185
+ str << format_flag_comment
186
+ str << format_previous_comment
187
+
188
+ # msgctxt, msgid, msgstr
189
+ if msgctxt?
190
+ if @msgctxt.nil?
191
+ no_msgctxt_message = "This POEntry is a kind of msgctxt " +
192
+ "but the msgctxt property is nil. " +
193
+ "msgid: #{msgid}"
194
+ raise(NoMsgctxtError, no_msgctxt_message)
195
+ end
196
+ str << "msgctxt \"" << msgctxt << "\"\n"
197
+ end
198
+
199
+ str << "msgid \"" << escaped(:msgid) << "\"\n"
200
+ if plural?
201
+ if @msgid_plural.nil?
202
+ no_plural_message = "This POEntry is a kind of plural " +
203
+ "but the msgid_plural property is nil. " +
204
+ "msgid: #{msgid}"
205
+ raise(NoMsgidPluralError, no_plural_message)
206
+ end
207
+
208
+ str << "msgid_plural \"" << escaped(:msgid_plural) << "\"\n"
209
+
210
+ if msgstr.nil?
211
+ str << "msgstr[0] \"\"\n"
212
+ str << "msgstr[1] \"\"\n"
213
+ else
214
+ msgstrs = msgstr.split("\000", -1)
215
+ msgstrs.each_with_index do |msgstr, index|
216
+ str << "msgstr[#{index}] \"#{escape(msgstr)}\"\n"
217
+ end
218
+ end
219
+ else
220
+ str << "msgstr "
221
+ str << format_message(msgstr)
222
+ end
223
+ str
224
+ end
225
+
226
+ def format_translator_comment
227
+ format_comment("#", translator_comment)
228
+ end
229
+
230
+ def format_extracted_comment
231
+ format_comment("#.", extracted_comment)
232
+ end
233
+
234
+ def format_reference_comment
235
+ max_line_length = 70
236
+ formatted_reference = ""
237
+ if not references.nil? and not references.empty?
238
+ formatted_reference << "#:"
239
+ line_size = 2
240
+ references.each do |reference|
241
+ if line_size + reference.size > max_line_length
242
+ formatted_reference << "\n"
243
+ formatted_reference << "#: #{reference}"
244
+ line_size = 3 + reference.size
245
+ else
246
+ formatted_reference << " #{reference}"
247
+ line_size += 1 + reference.size
248
+ end
249
+ end
250
+
251
+ formatted_reference << "\n"
252
+ end
253
+ formatted_reference
254
+ end
255
+
256
+ def format_flag_comment
257
+ format_comment("#,", flag)
258
+ end
259
+
260
+ def format_previous_comment
261
+ format_comment("#|", previous)
262
+ end
263
+
264
+ def format_comment(mark, comment)
265
+ return "" if comment.nil?
266
+
267
+ formatted_comment = ""
268
+ comment.each_line do |comment_line|
269
+ if comment_line == "\n"
270
+ formatted_comment << "#{mark}\n"
271
+ else
272
+ formatted_comment << "#{mark} #{comment_line.strip}\n"
273
+ end
274
+ end
275
+ formatted_comment
276
+ end
277
+
278
+ def format_message(message)
279
+ formatted_message = ""
280
+ if not message.nil? and message.include?("\n")
281
+ formatted_message << "\"\"\n"
282
+ message.each_line.each do |line|
283
+ formatted_message << "\"#{escape(line)}\"\n"
284
+ end
285
+ else
286
+ formatted_message << "\"#{escape(message)}\"\n"
287
+ end
288
+ formatted_message
289
+ end
290
+
291
+ # Returns true if the type is kind of msgctxt.
292
+ def msgctxt?
293
+ [:msgctxt, :msgctxt_plural].include?(@type)
294
+ end
295
+
296
+ # Returns true if the type is kind of plural.
297
+ def plural?
298
+ [:plural, :msgctxt_plural].include?(@type)
299
+ end
300
+
301
+ private
302
+
303
+ # sets or extends the value of a translation target params like msgid,
304
+ # msgctxt etc.
305
+ # param is symbol with the name of param
306
+ # value - new value
307
+ def set_value(param, value)
308
+ send "#{param}=", (send(param) || '') + value.gsub(/\n/, '\n')
309
+ end
310
+
311
+ def escape(value)
312
+ self.class.escape((value || "").gsub(/\r/, ""))
313
+ end
314
+
315
+ public
316
+ # For backward comatibility. This doesn't support "comment".
317
+ # ary = [msgid1, "file1:line1", "file2:line"]
318
+ def self.new_from_ary(ary)
319
+ ary = ary.dup
320
+ msgid = ary.shift
321
+ references = ary
322
+ type = :normal
323
+ msgctxt = nil
324
+ msgid_plural = nil
325
+
326
+ if msgid.include? "\004"
327
+ msgctxt, msgid = msgid.split(/\004/)
328
+ type = :msgctxt
329
+ end
330
+ if msgid.include? "\000"
331
+ ids = msgid.split(/\000/)
332
+ msgid = ids[0]
333
+ msgid_plural = ids[1]
334
+ if type == :msgctxt
335
+ type = :msgctxt_plural
336
+ else
337
+ type = :plural
338
+ end
339
+ end
340
+ ret = self.new(type)
341
+ ret.msgid = msgid
342
+ ret.references = references
343
+ ret.msgctxt = msgctxt
344
+ ret.msgid_plural = msgid_plural
345
+ ret
346
+ end
347
+
348
+ def [](number)
349
+ param = @param_type[number]
350
+ raise ParseError, 'no more string parameters expected' unless param
351
+ send param
352
+ end
353
+ end
354
+
355
+ end