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
data/doc/text/news.md
CHANGED
@@ -1,3 +1,40 @@
|
|
1
|
+
# News
|
2
|
+
## <a id="2-3-4">2.3.4</a>: 2012-12-11
|
3
|
+
|
4
|
+
This is a many changes and new implements release.
|
5
|
+
|
6
|
+
### Improvements
|
7
|
+
|
8
|
+
* [Merger] Implemented "fuzzy-match" with Levenshtein distance.
|
9
|
+
* Added the class "PO" for management PO entries. Please use PO
|
10
|
+
instead of PoData. (see details in
|
11
|
+
http://rubydoc.info/gems/gettext/GetText/PO.html)
|
12
|
+
* [POEntry (renamed from PoMessages)] Supported to specify msgstr.
|
13
|
+
* [POEntry] Stored comments each type
|
14
|
+
(translator\_comment, extracted\_comment, flag, previous).
|
15
|
+
see
|
16
|
+
http://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
17
|
+
for details of comment type.
|
18
|
+
* [POEntry] Checked if specified type is valid in #type=.
|
19
|
+
* [PoParser][MO] Concatenated msgctxt, msgid, msgid\_plural to
|
20
|
+
"#{msgctxt}\004#{msgid}\000"{msgid\_plural}" by MO instead of
|
21
|
+
PoParser. PoData and MO treat a concatenated string as msgid, but
|
22
|
+
PO doesn't.
|
23
|
+
* [PoParser] Parsed each type comment from whole comment.
|
24
|
+
|
25
|
+
### Changes
|
26
|
+
|
27
|
+
* Rename some classes and methods.
|
28
|
+
* PoMessage to PoEntry. This isn't "message" but "entry".
|
29
|
+
(See http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files)
|
30
|
+
* PoMessages#== to POEntry#mergeable?.
|
31
|
+
* PoMessages#to\_po\_str to POEntry#to\_s.
|
32
|
+
* PoMessages#sources(sources=) to POEntry#references(references=)
|
33
|
+
* MoFile to MO. For backword compatible, MoFile can be used now.
|
34
|
+
* PoParser to POParser. For backword compatible, PoParser can be used now.
|
35
|
+
* Raised no error when POEntry doesn't have references.
|
36
|
+
It is useful for no references in .PO file.
|
37
|
+
|
1
38
|
# News
|
2
39
|
## <a id="2-3-3">2.3.3</a>: 2012-10-18
|
3
40
|
|
data/gettext.gemspec
CHANGED
@@ -0,0 +1,382 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
=begin
|
4
|
+
mo.rb - A simple class for operating GNU MO file.
|
5
|
+
|
6
|
+
Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
|
7
|
+
Copyright (C) 2003-2009 Masao Mutoh
|
8
|
+
Copyright (C) 2002 Masahiro Sakai, Masao Mutoh
|
9
|
+
Copyright (C) 2001 Masahiro Sakai
|
10
|
+
|
11
|
+
Masahiro Sakai <s01397ms at sfc.keio.ac.jp>
|
12
|
+
Masao Mutoh <mutomasa at gmail.com>
|
13
|
+
|
14
|
+
You can redistribute this file and/or modify it under the same term
|
15
|
+
of Ruby. License of Ruby is included with Ruby distribution in
|
16
|
+
the file "README".
|
17
|
+
|
18
|
+
=end
|
19
|
+
|
20
|
+
require 'stringio'
|
21
|
+
|
22
|
+
module GetText
|
23
|
+
class MO < Hash
|
24
|
+
class InvalidFormat < RuntimeError; end;
|
25
|
+
|
26
|
+
attr_reader :filename
|
27
|
+
|
28
|
+
Header = Struct.new(:magic,
|
29
|
+
:revision,
|
30
|
+
:nstrings,
|
31
|
+
:orig_table_offset,
|
32
|
+
:translated_table_offset,
|
33
|
+
:hash_table_size,
|
34
|
+
:hash_table_offset)
|
35
|
+
|
36
|
+
# The following are only used in .mo files
|
37
|
+
# with minor revision >= 1.
|
38
|
+
class HeaderRev1 < Header
|
39
|
+
attr_accessor :n_sysdep_segments,
|
40
|
+
:sysdep_segments_offset,
|
41
|
+
:n_sysdep_strings,
|
42
|
+
:orig_sysdep_tab_offset,
|
43
|
+
:trans_sysdep_tab_offset
|
44
|
+
end
|
45
|
+
|
46
|
+
MAGIC_BIG_ENDIAN = "\x95\x04\x12\xde"
|
47
|
+
MAGIC_LITTLE_ENDIAN = "\xde\x12\x04\x95"
|
48
|
+
if "".respond_to?(:force_encoding)
|
49
|
+
MAGIC_BIG_ENDIAN.force_encoding("ASCII-8BIT")
|
50
|
+
MAGIC_LITTLE_ENDIAN.force_encoding("ASCII-8BIT")
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.open(arg = nil, output_charset = nil)
|
54
|
+
result = self.new(output_charset)
|
55
|
+
result.load(arg)
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(output_charset = nil)
|
59
|
+
@filename = nil
|
60
|
+
@last_modified = nil
|
61
|
+
@little_endian = true
|
62
|
+
@output_charset = output_charset
|
63
|
+
@plural_proc = nil
|
64
|
+
super()
|
65
|
+
end
|
66
|
+
|
67
|
+
def store(msgid, msgstr, options)
|
68
|
+
string = generate_original_string(msgid, options)
|
69
|
+
self[string] = msgstr
|
70
|
+
end
|
71
|
+
|
72
|
+
def update!
|
73
|
+
if FileTest.exist?(@filename)
|
74
|
+
st = File.stat(@filename)
|
75
|
+
load(@filename) unless (@last_modified == [st.ctime, st.mtime])
|
76
|
+
else
|
77
|
+
warn "#{@filename} was lost." if $DEBUG
|
78
|
+
clear
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def load(arg)
|
84
|
+
if arg.kind_of? String
|
85
|
+
begin
|
86
|
+
st = File.stat(arg)
|
87
|
+
@last_modified = [st.ctime, st.mtime]
|
88
|
+
rescue Exception
|
89
|
+
end
|
90
|
+
load_from_file(arg)
|
91
|
+
else
|
92
|
+
load_from_stream(arg)
|
93
|
+
end
|
94
|
+
@filename = arg
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def load_from_stream(io)
|
99
|
+
magic = io.read(4)
|
100
|
+
case magic
|
101
|
+
when MAGIC_BIG_ENDIAN
|
102
|
+
@little_endian = false
|
103
|
+
when MAGIC_LITTLE_ENDIAN
|
104
|
+
@little_endian = true
|
105
|
+
else
|
106
|
+
raise InvalidFormat.new(sprintf("Unknown signature %s", magic.dump))
|
107
|
+
end
|
108
|
+
|
109
|
+
endian_type6 = @little_endian ? 'V6' : 'N6'
|
110
|
+
endian_type_astr = @little_endian ? 'V*' : 'N*'
|
111
|
+
|
112
|
+
header = HeaderRev1.new(magic, *(io.read(4 * 6).unpack(endian_type6)))
|
113
|
+
|
114
|
+
if header.revision == 1
|
115
|
+
# FIXME: It doesn't support sysdep correctly.
|
116
|
+
header.n_sysdep_segments = io.read(4).unpack(endian_type6)
|
117
|
+
header.sysdep_segments_offset = io.read(4).unpack(endian_type6)
|
118
|
+
header.n_sysdep_strings = io.read(4).unpack(endian_type6)
|
119
|
+
header.orig_sysdep_tab_offset = io.read(4).unpack(endian_type6)
|
120
|
+
header.trans_sysdep_tab_offset = io.read(4).unpack(endian_type6)
|
121
|
+
elsif header.revision > 1
|
122
|
+
raise InvalidFormat.new(sprintf("file format revision %d isn't supported", header.revision))
|
123
|
+
end
|
124
|
+
io.pos = header.orig_table_offset
|
125
|
+
orig_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
|
126
|
+
|
127
|
+
io.pos = header.translated_table_offset
|
128
|
+
trans_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
|
129
|
+
|
130
|
+
msgids = Array.new(header.nstrings)
|
131
|
+
for i in 0...header.nstrings
|
132
|
+
io.pos = orig_table_data[i * 2 + 1]
|
133
|
+
msgids[i] = io.read(orig_table_data[i * 2 + 0])
|
134
|
+
end
|
135
|
+
|
136
|
+
clear
|
137
|
+
for i in 0...header.nstrings
|
138
|
+
io.pos = trans_table_data[i * 2 + 1]
|
139
|
+
msgstr = io.read(trans_table_data[i * 2 + 0])
|
140
|
+
|
141
|
+
msgid = msgids[i]
|
142
|
+
if msgid.nil? || msgid.empty?
|
143
|
+
if msgstr
|
144
|
+
@charset = nil
|
145
|
+
@nplurals = nil
|
146
|
+
@plural = nil
|
147
|
+
msgstr.each_line{|line|
|
148
|
+
if /^Content-Type:/i =~ line and /charset=((?:\w|-)+)/i =~ line
|
149
|
+
@charset = $1
|
150
|
+
elsif /^Plural-Forms:\s*nplurals\s*\=\s*(\d*);\s*plural\s*\=\s*([^;]*)\n?/ =~ line
|
151
|
+
@nplurals = $1
|
152
|
+
@plural = $2
|
153
|
+
end
|
154
|
+
break if @charset and @nplurals
|
155
|
+
}
|
156
|
+
@nplurals = "1" unless @nplurals
|
157
|
+
@plural = "0" unless @plural
|
158
|
+
end
|
159
|
+
else
|
160
|
+
unless msgstr.nil?
|
161
|
+
msgstr = convert_encoding(msgstr, msgid)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
self[convert_encoding(msgid, msgid)] = msgstr.freeze
|
165
|
+
end
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
def prime?(number)
|
170
|
+
('1' * number) !~ /^1?$|^(11+?)\1+$/
|
171
|
+
end
|
172
|
+
|
173
|
+
begin
|
174
|
+
require 'prime'
|
175
|
+
def next_prime(seed)
|
176
|
+
Prime.instance.find{|x| x > seed }
|
177
|
+
end
|
178
|
+
rescue LoadError
|
179
|
+
def next_prime(seed)
|
180
|
+
require 'mathn'
|
181
|
+
prime = Prime.new
|
182
|
+
while current = prime.succ
|
183
|
+
return current if current > seed
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
HASHWORDBITS = 32
|
189
|
+
# From gettext-0.12.1/gettext-runtime/intl/hash-string.h
|
190
|
+
# Defines the so called `hashpjw' function by P.J. Weinberger
|
191
|
+
# [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
|
192
|
+
# 1986, 1987 Bell Telephone Laboratories, Inc.]
|
193
|
+
def hash_string(str)
|
194
|
+
hval = 0
|
195
|
+
str.each_byte do |b|
|
196
|
+
break if b == '\0'
|
197
|
+
hval <<= 4
|
198
|
+
hval += b.to_i
|
199
|
+
g = hval & (0xf << (HASHWORDBITS - 4))
|
200
|
+
if (g != 0)
|
201
|
+
hval ^= g >> (HASHWORDBITS - 8)
|
202
|
+
hval ^= g
|
203
|
+
end
|
204
|
+
end
|
205
|
+
hval
|
206
|
+
end
|
207
|
+
|
208
|
+
#Save data as little endian format.
|
209
|
+
def save_to_stream(io)
|
210
|
+
# remove untranslated message
|
211
|
+
translated_messages = reject do |msgid, msgstr|
|
212
|
+
msgstr.nil?
|
213
|
+
end
|
214
|
+
|
215
|
+
size = translated_messages.size
|
216
|
+
header_size = 4 * 7
|
217
|
+
table_size = 4 * 2 * size
|
218
|
+
|
219
|
+
hash_table_size = next_prime((size * 4) / 3)
|
220
|
+
hash_table_size = 3 if hash_table_size <= 2
|
221
|
+
header = Header.new(
|
222
|
+
MAGIC_LITTLE_ENDIAN, # magic
|
223
|
+
0, # revision
|
224
|
+
size, # nstrings
|
225
|
+
header_size, # orig_table_offset
|
226
|
+
header_size + table_size, # translated_table_offset
|
227
|
+
hash_table_size, # hash_table_size
|
228
|
+
header_size + table_size * 2 # hash_table_offset
|
229
|
+
)
|
230
|
+
io.write(header.to_a.pack('a4V*'))
|
231
|
+
|
232
|
+
ary = translated_messages.to_a
|
233
|
+
ary.sort!{|a, b| a[0] <=> b[0]} # sort by original string
|
234
|
+
|
235
|
+
pos = header.hash_table_size * 4 + header.hash_table_offset
|
236
|
+
|
237
|
+
orig_table_data = Array.new()
|
238
|
+
ary.each{|item, _|
|
239
|
+
orig_table_data.push(item.bytesize)
|
240
|
+
orig_table_data.push(pos)
|
241
|
+
pos += item.bytesize + 1 # +1 is <NUL>
|
242
|
+
}
|
243
|
+
io.write(orig_table_data.pack('V*'))
|
244
|
+
|
245
|
+
trans_table_data = Array.new()
|
246
|
+
ary.each{|_, item|
|
247
|
+
trans_table_data.push(item.bytesize)
|
248
|
+
trans_table_data.push(pos)
|
249
|
+
pos += item.bytesize + 1 # +1 is <NUL>
|
250
|
+
}
|
251
|
+
io.write(trans_table_data.pack('V*'))
|
252
|
+
|
253
|
+
hash_tab = Array.new(hash_table_size)
|
254
|
+
j = 0
|
255
|
+
ary[0...size].each {|key, _|
|
256
|
+
hash_val = hash_string(key)
|
257
|
+
idx = hash_val % hash_table_size
|
258
|
+
if hash_tab[idx] != nil
|
259
|
+
incr = 1 + (hash_val % (hash_table_size - 2))
|
260
|
+
begin
|
261
|
+
if (idx >= hash_table_size - incr)
|
262
|
+
idx -= hash_table_size - incr
|
263
|
+
else
|
264
|
+
idx += incr
|
265
|
+
end
|
266
|
+
end until (hash_tab[idx] == nil)
|
267
|
+
end
|
268
|
+
hash_tab[idx] = j + 1
|
269
|
+
j += 1
|
270
|
+
}
|
271
|
+
hash_tab.collect!{|i| i ? i : 0}
|
272
|
+
|
273
|
+
io.write(hash_tab.pack('V*'))
|
274
|
+
|
275
|
+
ary.each{|item, _| io.write(item); io.write("\0") }
|
276
|
+
ary.each{|_, item| io.write(item); io.write("\0") }
|
277
|
+
|
278
|
+
self
|
279
|
+
end
|
280
|
+
|
281
|
+
def load_from_file(filename)
|
282
|
+
@filename = filename
|
283
|
+
begin
|
284
|
+
File.open(filename, 'rb'){|f| load_from_stream(f)}
|
285
|
+
rescue => e
|
286
|
+
e.set_backtrace("File: #{@filename}")
|
287
|
+
raise e
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def save_to_file(filename)
|
292
|
+
File.open(filename, 'wb'){|f| save_to_stream(f)}
|
293
|
+
end
|
294
|
+
|
295
|
+
def set_comment(msgid_or_sym, comment)
|
296
|
+
#Do nothing
|
297
|
+
end
|
298
|
+
|
299
|
+
def plural_as_proc
|
300
|
+
unless @plural_proc
|
301
|
+
@plural_proc = Proc.new{|n| eval(@plural)}
|
302
|
+
begin
|
303
|
+
@plural_proc.call(1)
|
304
|
+
rescue
|
305
|
+
@plural_proc = Proc.new{|n| 0}
|
306
|
+
end
|
307
|
+
end
|
308
|
+
@plural_proc
|
309
|
+
end
|
310
|
+
|
311
|
+
attr_accessor :little_endian, :path, :last_modified
|
312
|
+
attr_reader :charset, :nplurals, :plural
|
313
|
+
|
314
|
+
private
|
315
|
+
if "".respond_to?(:encode)
|
316
|
+
def convert_encoding(string, original_string)
|
317
|
+
return string if @output_charset.nil? or @charset.nil?
|
318
|
+
|
319
|
+
begin
|
320
|
+
string.encode(@output_charset, @charset)
|
321
|
+
rescue EncodingError
|
322
|
+
if $DEBUG
|
323
|
+
warn "@charset = ", @charset
|
324
|
+
warn "@output_charset = ", @output_charset
|
325
|
+
warn "msgid = ", original_string
|
326
|
+
warn "msgstr = ", string
|
327
|
+
end
|
328
|
+
string
|
329
|
+
end
|
330
|
+
end
|
331
|
+
else
|
332
|
+
require 'gettext/core_ext/iconv'
|
333
|
+
def convert_encoding(string, original_string)
|
334
|
+
begin
|
335
|
+
Iconv.conv(@output_charset, @charset, string)
|
336
|
+
rescue Iconv::Failure
|
337
|
+
if $DEBUG
|
338
|
+
warn "@charset = ", @charset
|
339
|
+
warn "@output_charset = ", @output_charset
|
340
|
+
warn "msgid = ", original_string
|
341
|
+
warn "msgstr = ", str
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def generate_original_string(msgid, options)
|
348
|
+
string = ""
|
349
|
+
|
350
|
+
msgctxt = options.delete(:msgctxt)
|
351
|
+
msgid_plural = options.delete(:msgid_plural)
|
352
|
+
|
353
|
+
string << msgctxt << "\004" unless msgctxt.nil?
|
354
|
+
string << msgid
|
355
|
+
string << "\000" << msgid_plural unless msgid_plural.nil?
|
356
|
+
string
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Test
|
362
|
+
|
363
|
+
if $0 == __FILE__
|
364
|
+
if (ARGV.include? "-h") or (ARGV.include? "--help")
|
365
|
+
STDERR.puts("mo.rb [filename.mo ...]")
|
366
|
+
exit
|
367
|
+
end
|
368
|
+
|
369
|
+
ARGV.each{ |item|
|
370
|
+
mo = GetText::MO.open(item)
|
371
|
+
puts "------------------------------------------------------------------"
|
372
|
+
puts "charset = \"#{mo.charset}\""
|
373
|
+
puts "nplurals = \"#{mo.nplurals}\""
|
374
|
+
puts "plural = \"#{mo.plural}\""
|
375
|
+
puts "------------------------------------------------------------------"
|
376
|
+
mo.each do |key, value|
|
377
|
+
puts "original message = #{key.inspect}"
|
378
|
+
puts "translated message = #{value.inspect}"
|
379
|
+
puts "--------------------------------------------------------------------"
|
380
|
+
end
|
381
|
+
}
|
382
|
+
end
|
@@ -1,368 +1,26 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
require
|
21
|
-
|
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/runtime/mo"
|
21
|
+
|
22
|
+
# Just for backward compatibility.
|
22
23
|
module GetText
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
attr_reader :filename
|
27
|
-
|
28
|
-
Header = Struct.new(:magic,
|
29
|
-
:revision,
|
30
|
-
:nstrings,
|
31
|
-
:orig_table_offset,
|
32
|
-
:translated_table_offset,
|
33
|
-
:hash_table_size,
|
34
|
-
:hash_table_offset)
|
35
|
-
|
36
|
-
# The following are only used in .mo files
|
37
|
-
# with minor revision >= 1.
|
38
|
-
class HeaderRev1 < Header
|
39
|
-
attr_accessor :n_sysdep_segments,
|
40
|
-
:sysdep_segments_offset,
|
41
|
-
:n_sysdep_strings,
|
42
|
-
:orig_sysdep_tab_offset,
|
43
|
-
:trans_sysdep_tab_offset
|
44
|
-
end
|
45
|
-
|
46
|
-
MAGIC_BIG_ENDIAN = "\x95\x04\x12\xde"
|
47
|
-
MAGIC_LITTLE_ENDIAN = "\xde\x12\x04\x95"
|
48
|
-
if "".respond_to?(:force_encoding)
|
49
|
-
MAGIC_BIG_ENDIAN.force_encoding("ASCII-8BIT")
|
50
|
-
MAGIC_LITTLE_ENDIAN.force_encoding("ASCII-8BIT")
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.open(arg = nil, output_charset = nil)
|
54
|
-
result = self.new(output_charset)
|
55
|
-
result.load(arg)
|
56
|
-
end
|
57
|
-
|
58
|
-
def initialize(output_charset = nil)
|
59
|
-
@filename = nil
|
60
|
-
@last_modified = nil
|
61
|
-
@little_endian = true
|
62
|
-
@output_charset = output_charset
|
63
|
-
@plural_proc = nil
|
64
|
-
super()
|
65
|
-
end
|
66
|
-
|
67
|
-
def update!
|
68
|
-
if FileTest.exist?(@filename)
|
69
|
-
st = File.stat(@filename)
|
70
|
-
load(@filename) unless (@last_modified == [st.ctime, st.mtime])
|
71
|
-
else
|
72
|
-
warn "#{@filename} was lost." if $DEBUG
|
73
|
-
clear
|
74
|
-
end
|
75
|
-
self
|
76
|
-
end
|
77
|
-
|
78
|
-
def load(arg)
|
79
|
-
if arg.kind_of? String
|
80
|
-
begin
|
81
|
-
st = File.stat(arg)
|
82
|
-
@last_modified = [st.ctime, st.mtime]
|
83
|
-
rescue Exception
|
84
|
-
end
|
85
|
-
load_from_file(arg)
|
86
|
-
else
|
87
|
-
load_from_stream(arg)
|
88
|
-
end
|
89
|
-
@filename = arg
|
90
|
-
self
|
91
|
-
end
|
92
|
-
|
93
|
-
def load_from_stream(io)
|
94
|
-
magic = io.read(4)
|
95
|
-
case magic
|
96
|
-
when MAGIC_BIG_ENDIAN
|
97
|
-
@little_endian = false
|
98
|
-
when MAGIC_LITTLE_ENDIAN
|
99
|
-
@little_endian = true
|
100
|
-
else
|
101
|
-
raise InvalidFormat.new(sprintf("Unknown signature %s", magic.dump))
|
102
|
-
end
|
103
|
-
|
104
|
-
endian_type6 = @little_endian ? 'V6' : 'N6'
|
105
|
-
endian_type_astr = @little_endian ? 'V*' : 'N*'
|
106
|
-
|
107
|
-
header = HeaderRev1.new(magic, *(io.read(4 * 6).unpack(endian_type6)))
|
108
|
-
|
109
|
-
if header.revision == 1
|
110
|
-
# FIXME: It doesn't support sysdep correctly.
|
111
|
-
header.n_sysdep_segments = io.read(4).unpack(endian_type6)
|
112
|
-
header.sysdep_segments_offset = io.read(4).unpack(endian_type6)
|
113
|
-
header.n_sysdep_strings = io.read(4).unpack(endian_type6)
|
114
|
-
header.orig_sysdep_tab_offset = io.read(4).unpack(endian_type6)
|
115
|
-
header.trans_sysdep_tab_offset = io.read(4).unpack(endian_type6)
|
116
|
-
elsif header.revision > 1
|
117
|
-
raise InvalidFormat.new(sprintf("file format revision %d isn't supported", header.revision))
|
118
|
-
end
|
119
|
-
io.pos = header.orig_table_offset
|
120
|
-
orig_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
|
121
|
-
|
122
|
-
io.pos = header.translated_table_offset
|
123
|
-
trans_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
|
124
|
-
|
125
|
-
msgids = Array.new(header.nstrings)
|
126
|
-
for i in 0...header.nstrings
|
127
|
-
io.pos = orig_table_data[i * 2 + 1]
|
128
|
-
msgids[i] = io.read(orig_table_data[i * 2 + 0])
|
129
|
-
end
|
130
|
-
|
131
|
-
clear
|
132
|
-
for i in 0...header.nstrings
|
133
|
-
io.pos = trans_table_data[i * 2 + 1]
|
134
|
-
msgstr = io.read(trans_table_data[i * 2 + 0])
|
135
|
-
|
136
|
-
msgid = msgids[i]
|
137
|
-
if msgid.nil? || msgid.empty?
|
138
|
-
if msgstr
|
139
|
-
@charset = nil
|
140
|
-
@nplurals = nil
|
141
|
-
@plural = nil
|
142
|
-
msgstr.each_line{|line|
|
143
|
-
if /^Content-Type:/i =~ line and /charset=((?:\w|-)+)/i =~ line
|
144
|
-
@charset = $1
|
145
|
-
elsif /^Plural-Forms:\s*nplurals\s*\=\s*(\d*);\s*plural\s*\=\s*([^;]*)\n?/ =~ line
|
146
|
-
@nplurals = $1
|
147
|
-
@plural = $2
|
148
|
-
end
|
149
|
-
break if @charset and @nplurals
|
150
|
-
}
|
151
|
-
@nplurals = "1" unless @nplurals
|
152
|
-
@plural = "0" unless @plural
|
153
|
-
end
|
154
|
-
else
|
155
|
-
unless msgstr.nil?
|
156
|
-
msgstr = convert_encoding(msgstr, msgid)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
self[convert_encoding(msgid, msgid)] = msgstr.freeze
|
160
|
-
end
|
161
|
-
self
|
162
|
-
end
|
163
|
-
|
164
|
-
def prime?(number)
|
165
|
-
('1' * number) !~ /^1?$|^(11+?)\1+$/
|
166
|
-
end
|
167
|
-
|
168
|
-
begin
|
169
|
-
require 'prime'
|
170
|
-
def next_prime(seed)
|
171
|
-
Prime.instance.find{|x| x > seed }
|
172
|
-
end
|
173
|
-
rescue LoadError
|
174
|
-
def next_prime(seed)
|
175
|
-
require 'mathn'
|
176
|
-
prime = Prime.new
|
177
|
-
while current = prime.succ
|
178
|
-
return current if current > seed
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
HASHWORDBITS = 32
|
184
|
-
# From gettext-0.12.1/gettext-runtime/intl/hash-string.h
|
185
|
-
# Defines the so called `hashpjw' function by P.J. Weinberger
|
186
|
-
# [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
|
187
|
-
# 1986, 1987 Bell Telephone Laboratories, Inc.]
|
188
|
-
def hash_string(str)
|
189
|
-
hval = 0
|
190
|
-
str.each_byte do |b|
|
191
|
-
break if b == '\0'
|
192
|
-
hval <<= 4
|
193
|
-
hval += b.to_i
|
194
|
-
g = hval & (0xf << (HASHWORDBITS - 4))
|
195
|
-
if (g != 0)
|
196
|
-
hval ^= g >> (HASHWORDBITS - 8)
|
197
|
-
hval ^= g
|
198
|
-
end
|
199
|
-
end
|
200
|
-
hval
|
201
|
-
end
|
202
|
-
|
203
|
-
#Save data as little endian format.
|
204
|
-
def save_to_stream(io)
|
205
|
-
# remove untranslated message
|
206
|
-
translated_messages = reject do |msgid, msgstr|
|
207
|
-
msgstr.nil?
|
208
|
-
end
|
209
|
-
|
210
|
-
size = translated_messages.size
|
211
|
-
header_size = 4 * 7
|
212
|
-
table_size = 4 * 2 * size
|
213
|
-
|
214
|
-
hash_table_size = next_prime((size * 4) / 3)
|
215
|
-
hash_table_size = 3 if hash_table_size <= 2
|
216
|
-
header = Header.new(
|
217
|
-
MAGIC_LITTLE_ENDIAN, # magic
|
218
|
-
0, # revision
|
219
|
-
size, # nstrings
|
220
|
-
header_size, # orig_table_offset
|
221
|
-
header_size + table_size, # translated_table_offset
|
222
|
-
hash_table_size, # hash_table_size
|
223
|
-
header_size + table_size * 2 # hash_table_offset
|
224
|
-
)
|
225
|
-
io.write(header.to_a.pack('a4V*'))
|
226
|
-
|
227
|
-
ary = translated_messages.to_a
|
228
|
-
ary.sort!{|a, b| a[0] <=> b[0]} # sort by original string
|
229
|
-
|
230
|
-
pos = header.hash_table_size * 4 + header.hash_table_offset
|
231
|
-
|
232
|
-
orig_table_data = Array.new()
|
233
|
-
ary.each{|item, _|
|
234
|
-
orig_table_data.push(item.bytesize)
|
235
|
-
orig_table_data.push(pos)
|
236
|
-
pos += item.bytesize + 1 # +1 is <NUL>
|
237
|
-
}
|
238
|
-
io.write(orig_table_data.pack('V*'))
|
239
|
-
|
240
|
-
trans_table_data = Array.new()
|
241
|
-
ary.each{|_, item|
|
242
|
-
trans_table_data.push(item.bytesize)
|
243
|
-
trans_table_data.push(pos)
|
244
|
-
pos += item.bytesize + 1 # +1 is <NUL>
|
245
|
-
}
|
246
|
-
io.write(trans_table_data.pack('V*'))
|
247
|
-
|
248
|
-
hash_tab = Array.new(hash_table_size)
|
249
|
-
j = 0
|
250
|
-
ary[0...size].each {|key, _|
|
251
|
-
hash_val = hash_string(key)
|
252
|
-
idx = hash_val % hash_table_size
|
253
|
-
if hash_tab[idx] != nil
|
254
|
-
incr = 1 + (hash_val % (hash_table_size - 2))
|
255
|
-
begin
|
256
|
-
if (idx >= hash_table_size - incr)
|
257
|
-
idx -= hash_table_size - incr
|
258
|
-
else
|
259
|
-
idx += incr
|
260
|
-
end
|
261
|
-
end until (hash_tab[idx] == nil)
|
262
|
-
end
|
263
|
-
hash_tab[idx] = j + 1
|
264
|
-
j += 1
|
265
|
-
}
|
266
|
-
hash_tab.collect!{|i| i ? i : 0}
|
267
|
-
|
268
|
-
io.write(hash_tab.pack('V*'))
|
269
|
-
|
270
|
-
ary.each{|item, _| io.write(item); io.write("\0") }
|
271
|
-
ary.each{|_, item| io.write(item); io.write("\0") }
|
272
|
-
|
273
|
-
self
|
274
|
-
end
|
275
|
-
|
276
|
-
def load_from_file(filename)
|
277
|
-
@filename = filename
|
278
|
-
begin
|
279
|
-
File.open(filename, 'rb'){|f| load_from_stream(f)}
|
280
|
-
rescue => e
|
281
|
-
e.set_backtrace("File: #{@filename}")
|
282
|
-
raise e
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def save_to_file(filename)
|
287
|
-
File.open(filename, 'wb'){|f| save_to_stream(f)}
|
288
|
-
end
|
289
|
-
|
290
|
-
def set_comment(msgid_or_sym, comment)
|
291
|
-
#Do nothing
|
292
|
-
end
|
293
|
-
|
294
|
-
def plural_as_proc
|
295
|
-
unless @plural_proc
|
296
|
-
@plural_proc = Proc.new{|n| eval(@plural)}
|
297
|
-
begin
|
298
|
-
@plural_proc.call(1)
|
299
|
-
rescue
|
300
|
-
@plural_proc = Proc.new{|n| 0}
|
301
|
-
end
|
302
|
-
end
|
303
|
-
@plural_proc
|
304
|
-
end
|
305
|
-
|
306
|
-
attr_accessor :little_endian, :path, :last_modified
|
307
|
-
attr_reader :charset, :nplurals, :plural
|
308
|
-
|
309
|
-
private
|
310
|
-
if "".respond_to?(:encode)
|
311
|
-
def convert_encoding(string, original_string)
|
312
|
-
return string if @output_charset.nil? or @charset.nil?
|
313
|
-
|
314
|
-
begin
|
315
|
-
string.encode(@output_charset, @charset)
|
316
|
-
rescue EncodingError
|
317
|
-
if $DEBUG
|
318
|
-
warn "@charset = ", @charset
|
319
|
-
warn "@output_charset = ", @output_charset
|
320
|
-
warn "msgid = ", original_string
|
321
|
-
warn "msgstr = ", string
|
322
|
-
end
|
323
|
-
string
|
324
|
-
end
|
325
|
-
end
|
326
|
-
else
|
327
|
-
require 'gettext/core_ext/iconv'
|
328
|
-
def convert_encoding(string, original_string)
|
329
|
-
begin
|
330
|
-
Iconv.conv(@output_charset, @charset, string)
|
331
|
-
rescue Iconv::Failure
|
332
|
-
if $DEBUG
|
333
|
-
warn "@charset = ", @charset
|
334
|
-
warn "@output_charset = ", @output_charset
|
335
|
-
warn "msgid = ", original_string
|
336
|
-
warn "msgstr = ", str
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
# Just for backward compatibility.
|
344
|
-
MOFile = MoFile
|
345
|
-
end
|
346
|
-
|
347
|
-
# Test
|
348
|
-
|
349
|
-
if $0 == __FILE__
|
350
|
-
if (ARGV.include? "-h") or (ARGV.include? "--help")
|
351
|
-
STDERR.puts("mo.rb [filename.mo ...]")
|
352
|
-
exit
|
353
|
-
end
|
354
|
-
|
355
|
-
ARGV.each{ |item|
|
356
|
-
mo = GetText::MoFile.open(item)
|
357
|
-
puts "------------------------------------------------------------------"
|
358
|
-
puts "charset = \"#{mo.charset}\""
|
359
|
-
puts "nplurals = \"#{mo.nplurals}\""
|
360
|
-
puts "plural = \"#{mo.plural}\""
|
361
|
-
puts "------------------------------------------------------------------"
|
362
|
-
mo.each do |key, value|
|
363
|
-
puts "original message = #{key.inspect}"
|
364
|
-
puts "translated message = #{value.inspect}"
|
365
|
-
puts "--------------------------------------------------------------------"
|
366
|
-
end
|
367
|
-
}
|
24
|
+
MoFile = MO
|
25
|
+
MOFile = MO
|
368
26
|
end
|