gettext 2.3.3 → 2.3.4

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.
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