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.
- data/doc/text/news.md +37 -0
- data/gettext.gemspec +1 -0
- data/lib/gettext/runtime/mo.rb +382 -0
- data/lib/gettext/runtime/mofile.rb +24 -366
- data/lib/gettext/runtime/textdomain.rb +17 -17
- data/lib/gettext/tools.rb +1 -1
- data/lib/gettext/tools/msgfmt.rb +2 -2
- data/lib/gettext/tools/msginit.rb +16 -15
- data/lib/gettext/tools/msgmerge.rb +258 -255
- data/lib/gettext/tools/parser/ruby.rb +24 -24
- data/lib/gettext/tools/po.rb +256 -0
- data/lib/gettext/tools/po_entry.rb +355 -0
- data/lib/gettext/tools/poparser.rb +118 -16
- data/lib/gettext/tools/xgettext.rb +56 -58
- data/lib/gettext/version.rb +1 -1
- data/samples/po/hello.pot +3 -3
- data/samples/po/hello2.pot +3 -3
- data/samples/po/hello_glade2.pot +3 -3
- data/samples/po/hello_gtk2.pot +3 -3
- data/samples/po/hello_noop.pot +3 -3
- data/samples/po/hello_plural.pot +3 -3
- data/samples/po/hello_tk.pot +3 -3
- data/src/poparser.ry +111 -9
- data/test/parser/test_ruby.rb +17 -13
- data/test/po/_.pot +3 -3
- data/test/po/backslash.pot +3 -3
- data/test/po/non_ascii.pot +3 -3
- data/test/po/np_.pot +5 -4
- data/test/po/ns_.pot +3 -3
- data/test/po/p_.pot +3 -3
- data/test/po/s_.pot +3 -3
- data/test/po/untranslated.pot +3 -3
- data/test/{test_mofile.rb → test_mo.rb} +3 -3
- data/test/test_parser.rb +13 -12
- data/test/test_po_entry.rb +329 -0
- data/test/test_po_parser.rb +209 -8
- data/test/tools/test_msginit.rb +0 -2
- data/test/tools/test_msgmerge.rb +427 -50
- data/test/tools/test_po.rb +487 -0
- data/test/tools/test_xgettext.rb +1 -1
- metadata +28 -45
- data/data/locale/de/LC_MESSAGES/gettext.mo +0 -0
- data/data/locale/de/LC_MESSAGES/rgettext.mo +0 -0
- data/data/locale/el/LC_MESSAGES/gettext.mo +0 -0
- data/data/locale/el/LC_MESSAGES/rgettext.mo +0 -0
- data/data/locale/sr/LC_MESSAGES/gettext.mo +0 -0
- data/data/locale/sr/LC_MESSAGES/rgettext.mo +0 -0
- data/data/locale/uk/LC_MESSAGES/gettext.mo +0 -0
- data/data/locale/uk/LC_MESSAGES/rgettext.mo +0 -0
- data/lib/gettext/tools/pomessage.rb +0 -232
- data/samples/locale/bg/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/bs/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/ca/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/cs/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/de/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/el/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/eo/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/es/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/fr/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/hr/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/hu/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/it/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/ja/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/ko/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/lv/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/nb/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/nl/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/pt_BR/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/ru/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/sr/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/sv/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/uk/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/vi/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/zh/LC_MESSAGES/hello_gtk.mo +0 -0
- data/samples/locale/zh_TW/LC_MESSAGES/hello_gtk.mo +0 -0
- data/test/po/ascii.pot +0 -23
- data/test/po/no_exist_msgid.pot +0 -20
- data/test/po/not_existed_msgid.pot +0 -20
- 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/
|
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
|
82
|
+
# Extends POEntry for RubyParser.
|
83
83
|
# Implements a sort of state machine to assist the parser.
|
84
|
-
module
|
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
|
102
|
-
include
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
163
|
+
store_po_entry(po, po_entry, path, line_no, last_comment)
|
164
164
|
if ID.include?(tk.name)
|
165
|
-
|
165
|
+
po_entry = POEntry.new(:normal)
|
166
166
|
elsif PLURAL_ID.include?(tk.name)
|
167
|
-
|
167
|
+
po_entry = POEntry.new(:plural)
|
168
168
|
elsif MSGCTXT_ID.include?(tk.name)
|
169
|
-
|
169
|
+
po_entry = POEntry.new(:msgctxt)
|
170
170
|
elsif MSGCTXT_PLURAL_ID.include?(tk.name)
|
171
|
-
|
171
|
+
po_entry = POEntry.new(:msgctxt_plural)
|
172
172
|
else
|
173
|
-
|
173
|
+
po_entry = nil
|
174
174
|
end
|
175
175
|
line_no = tk.line_no.to_s
|
176
176
|
when RubyToken::TkSTRING, RubyToken::TkDSTRING
|
177
|
-
|
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
|
-
|
184
|
+
po_entry.advance_to_next_attribute if po_entry
|
185
185
|
end
|
186
186
|
else
|
187
|
-
if
|
188
|
-
|
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
|
-
|
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
|
229
|
-
if
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|