gettext 2.3.3 → 2.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|