grosser-pomo 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,7 +1,6 @@
1
1
  A simple and extendable .mo and .po file parser/generator.
2
- --mo file parser and writer are missing atm--
3
2
 
4
- Advanteges over [original po-parser](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/poparser.rb):
3
+ Advanteges over original [mo](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/mofile.rb) / [po](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/poparser.rb)-parser:
5
4
 
6
5
  - simple architecture + easy to extend/modify
7
6
  - emtpy msgstr translations are read
@@ -9,6 +8,8 @@ Advanteges over [original po-parser](http://github.com/mutoh/gettext/blob/abf967
9
8
  - fuzzy can be set/unset
10
9
  - multiple translations can be combined in a new po file(with comments and fuzzy and ...)
11
10
  - po files can be written from any kind of input
11
+ - easy mo-file handling/merging
12
+ - po/mo file handling is identical, if you know one, you know both
12
13
 
13
14
  Setup
14
15
  =====
@@ -24,6 +25,7 @@ Setup
24
25
  #or write a new po file (unique by msgid)...
25
26
  File.open('xxx.po','w){|f|f.print(Pomo::PoFile.to_text(translations))}
26
27
 
28
+
27
29
  ###Instance interface
28
30
  p = PoMo::PoFile.new
29
31
  p.add_translations_from_text(File.read('...'))
@@ -31,10 +33,12 @@ Setup
31
33
  p.translations
32
34
  p.to_text
33
35
 
36
+ `Pomo::MoFile` behaves identical.
37
+
34
38
  TODO
35
39
  ====
36
40
  - extracting of version/pluralisation_rule/plurals/translator... (from msgid "")
37
- - mo writing/reading (this is the hardest part imo...)
41
+ - the vendor/mofile is really complex, maybe it can be refactored (also some parts are not needed)
38
42
 
39
43
  Author
40
44
  ======
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 4
3
- :patch: 1
2
+ :minor: 5
3
+ :patch: 0
4
4
  :major: 0
@@ -0,0 +1,62 @@
1
+ require 'pomo/translation'
2
+ require File.join(File.dirname(__FILE__),'..','..','vendor','mofile')
3
+
4
+ module Pomo
5
+ class MoFile
6
+ PLURAL_SEPERATOR = "\000"
7
+
8
+ def self.parse(text)
9
+ MoFile.new.add_translations_from_text(text)
10
+ end
11
+
12
+ def self.to_text(translations)
13
+ m = MoFile.new(:translations=>translations)
14
+ m.to_text
15
+ end
16
+
17
+ attr_reader :translations
18
+
19
+ def initialize(options = {})
20
+ @translations = options[:translations] || []
21
+ end
22
+
23
+ def add_translations_from_text(text)
24
+ text = StringIO.new(text)
25
+ @translations += Pomo::GetText::MOFile.open(text, "UTF-8").map do |msgid,msgstr|
26
+ translation = Translation.new
27
+ if plural? msgid or plural? msgstr
28
+ translation.msgid = split_plural(msgid)
29
+ translation.msgstr = split_plural(msgstr)
30
+ else
31
+ translation.msgid = msgid
32
+ translation.msgstr = msgstr
33
+ end
34
+ translation
35
+ end
36
+ end
37
+
38
+ def to_text
39
+ m = Pomo::GetText::MOFile.new
40
+ Pomo.unique_translations(translations).each {|t| m[plural_to_string(t.msgid)] = plural_to_string(t.msgstr)}
41
+
42
+ io = StringIO.new
43
+ m.save_to_stream io
44
+ io.rewind
45
+ io.read
46
+ end
47
+
48
+ private
49
+
50
+ def plural_to_string(plural_or_singular)
51
+ [*plural_or_singular] * PLURAL_SEPERATOR
52
+ end
53
+
54
+ def plural? string
55
+ string.include? PLURAL_SEPERATOR
56
+ end
57
+
58
+ def split_plural string
59
+ string.split PLURAL_SEPERATOR
60
+ end
61
+ end
62
+ end
data/lib/pomo/po_file.rb CHANGED
@@ -11,12 +11,6 @@ module Pomo
11
11
  p.to_text
12
12
  end
13
13
 
14
- def self.unique_translations(translations)
15
- last_seen_at_index = {}
16
- translations.each_with_index {|translation,index|last_seen_at_index[translation.msgid]=index}
17
- last_seen_at_index.values.sort.map{|index| translations[index]}
18
- end
19
-
20
14
  attr_reader :translations
21
15
 
22
16
  def initialize(options = {})
@@ -44,7 +38,7 @@ module Pomo
44
38
  end
45
39
 
46
40
  def to_text
47
- self.class.unique_translations(translations).map {|translation|
41
+ Pomo.unique_translations(translations).map {|translation|
48
42
  comment = translation.comment.to_s.split(/\n|\r\n/).map{|line|"##{line}\n"}*''
49
43
  msgid_and_msgstr = if translation.plural?
50
44
  msgids =
data/lib/pomo.rb CHANGED
@@ -1,3 +1,10 @@
1
1
  require 'pomo/po_file'
2
2
  module Pomo
3
+ extend self
4
+
5
+ def self.unique_translations(translations)
6
+ last_seen_at_index = {}
7
+ translations.each_with_index {|translation,index|last_seen_at_index[translation.msgid]=index}
8
+ last_seen_at_index.values.sort.map{|index| translations[index]}
9
+ end
3
10
  end
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,58 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+ require 'pomo/mo_file'
3
+
4
+ include Pomo
5
+ describe Pomo::MoFile do
6
+ it "parses empty mo file" do
7
+ MoFile.parse(File.read('spec/files/empty.mo')).should == []
8
+ end
9
+
10
+ it "parses empty strings" do
11
+ MoFile.parse(File.read('spec/files/empty.mo')).should == []
12
+ end
13
+
14
+ it "reads singulars" do
15
+ t = MoFile.parse(File.read('spec/files/singular.mo'))[0]
16
+ t.to_hash.should == {:msgid=>'Back',:msgstr=>'Zurück'}
17
+ end
18
+
19
+ it "reads plurals" do
20
+ t = MoFile.parse(File.read('spec/files/plural.mo'))[0]
21
+ t.to_hash.should == {:msgid=>['Axis','Axis'],:msgstr=>['Achse','Achsen']}
22
+ end
23
+
24
+ describe 'instance methods' do
25
+ it "combines multiple translations" do
26
+ m = MoFile.new
27
+ m.add_translations_from_text(File.read('spec/files/plural.mo'))
28
+ m.add_translations_from_text(File.read('spec/files/singular.mo'))
29
+ m.should have(2).translations
30
+ m.translations[0].msgid.should_not == m.translations[1].msgid
31
+ end
32
+
33
+ it "can be initialized with translations" do
34
+ m = MoFile.new(:translations=>['x'])
35
+ m.translations.should == ['x']
36
+ end
37
+
38
+ it "does not generate duplicate translations" do
39
+ second_version = File.read('spec/files/singular_2.mo')
40
+ m = MoFile.new
41
+ m.add_translations_from_text(File.read('spec/files/singular.mo'))
42
+ m.add_translations_from_text(second_version)
43
+ m.to_text.should == second_version
44
+ end
45
+ end
46
+
47
+ it "reads metadata" do
48
+ meta = MoFile.parse(File.read('spec/files/complex.mo')).detect {|t|t.msgid == ''}
49
+ meta.msgstr.should_not be_empty
50
+ end
51
+
52
+ describe :to_text do
53
+ it "writes singulars" do
54
+ text = File.read('spec/files/singular.mo')
55
+ MoFile.to_text(MoFile.parse(text)).should == text
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,236 @@
1
+ = Ruby-GetText-Package
2
+
3
+ Ruby-GetText-Package is a Localization(L10n) library and tool
4
+ which is modeled after the GNU gettext package.
5
+
6
+ This library translates original messages to localized
7
+ messages using client-side locale information(environment
8
+ variable or CGI variable).
9
+
10
+ The tools for developers support creating, useing, and modifying
11
+ localized message files(message catalogs).
12
+
13
+ ((*Rails*))
14
+ Rails support has been removed.
15
+ Rails / ActiveRecord specific code now lives in gettext_rails and gettext_activerecord.
16
+
17
+ == Website
18
+ * homepage[http://www.yotabanana.com/hiki/ruby-gettext.html]
19
+ * on rubyforge[http://gettext/rubyforge.org/]
20
+ * on github[http://github.com/gettext/]
21
+
22
+ == Features
23
+ * Simple APIs(similar to GNU gettext)
24
+
25
+ * rgettext creates po-files from
26
+ * ruby scripts
27
+ * glade-2 XML file(.glade)
28
+ * ERB file(.rhtml, .erb)
29
+ * Anything (with your own parsers)
30
+ * The po-files are compatible to GNU gettext.
31
+
32
+ * rmsgfmt creates a mo-file from a po-file.
33
+ The mo-file is compatible to GNU gettext(msgfmt).
34
+
35
+ * textdomain's scope is adapt to ruby class/module mechanism.
36
+ * A class/module can have plural textdomains.
37
+ * a message is looked up in its class/module and ancestors.
38
+
39
+ * CGI support (gettext/cgi)
40
+ * Locale is retrieved from client informations
41
+ (HTTP_ACCEPT_LANGUAGE, HTTP_ACCEPT_CHARSET, QUERY_STRING(lang), Cookies(lang)).
42
+
43
+ * String%() is extended to use named argument such as <tt>%{foo}" %{:foo => 1}</tt>.
44
+ Notes that Ruby-1.9.x supports this format by itself.
45
+
46
+ == Requirements
47
+ * {Ruby 1.8.3 or later}[http://www.ruby-lang.org]
48
+ * {Rubygems}[http://www.rubygems.org/]
49
+ * {locale gem}[http://rubyforge.org/projects/locale/]
50
+ * $ gem install locale
51
+ * (for development only)
52
+ * {GNU gettext 0.10.35 or later}[http://www.gnu.org/software/gettext/gettext.html]
53
+ * {Racc-1.4.3 or later}[http://www.ruby-lang.org/raa/list.rhtml?name=racc]
54
+ * (for compiling src/rmsgfmt.ry only)
55
+
56
+ == Install
57
+ * Uninstall old gettext if exists.
58
+ (sudo/su on POSIX system)
59
+ gem uninstall gettext
60
+
61
+ * gem
62
+ #from github (edge/unstable)
63
+ (sudo/su on POSIX system)
64
+ gem install locale
65
+ gem install mutoh-gettext -s http://gems.github.com/
66
+
67
+ #from rubyforge (stable)
68
+ (sudo/su on POSIX system)
69
+ gem install locale
70
+ gem install gettext
71
+
72
+ * download tar-ball
73
+ # De-Compress archive and enter its top directory.
74
+ (sudo/su on POSIX system)
75
+ ruby setup.rb
76
+
77
+ You can also install files in your favorite directory by
78
+ supplying setup.rb some options. Try <tt>ruby setup.rb --help</tt>.
79
+
80
+ == Usage
81
+ ===Translation
82
+ - _: Basic translation method
83
+ Translates the message.
84
+ _("Hello")
85
+
86
+ The gettext methods comes in 3 combinable flavors
87
+ - n: Pluralized
88
+ Returns singular or plural form, depending on how many you have.
89
+ n_("Apple", "%{num} Apples", 3)
90
+ n_(["Apple", "%{num} Apples"], 3)
91
+
92
+ - p: context aware
93
+ A context is a prefix to your translation, usefull when one word has different meanings, depending on its context.
94
+ p_("Printer","Open") <=> p_("File","Open")
95
+ is the same as s_("Printer|Open") <=> s_("File|Open")
96
+
97
+ - s: without context
98
+ If a translation could not be found, return the msgid without context.
99
+ s_("Printer|Open") => "Öffnen" #translation found
100
+ s_("Printer|Open") => "Open" #translation not found
101
+
102
+ - combinations
103
+ np_("Fruit", "Apple", "%{num} Apples", 3)
104
+ ns_("Fruit|Apple","%{num} Apples", 3)
105
+
106
+ np_(["Fruit","Apple","%{num} Apples"], 3)
107
+ ns_(["Fruit|Apple","%{num} Apples"], 3)
108
+
109
+ - N_, Nn_: Makes dynamic translation messages readable for the gettext parser.
110
+ <tt>_(fruit)</tt> cannot be understood by the gettext parser. To help the parser find all your translations,
111
+ you can add <tt>fruit = N_("Apple")</tt> which does not translate, but tells the parser: "Apple" needs translation.
112
+
113
+ fruit = N_("Apple") # same as fruit = "Apple"
114
+ _(fruit) # does a normal translation
115
+
116
+ fruits = Nn_("Apple", "%{num} Apples")
117
+ n_(fruits, 3)
118
+
119
+ === Locale / Domain
120
+ GetText stores the locale your are using
121
+ GetText.locale = "en_US" # translate into english from now on
122
+ GetText.locale # => en_US
123
+ Or
124
+ include GetText
125
+ set_locale "en_US"
126
+
127
+ Each locale can have different sets of translations (text domains) (e.g. Financial terms + Human-resource terms)
128
+ GetText.bindtextdomain('financial')
129
+ Or
130
+ include GetText
131
+ bindtextdomain('financial')
132
+
133
+ For more details and options, have a look at the samples folder or
134
+ consult the tutorial[http://www.yotabanana.com/hiki/ruby-gettext-howto.html].
135
+
136
+
137
+ == License
138
+ This program is licenced under the same licence as Ruby.
139
+ (See the file 'COPYING'.)
140
+
141
+ * mofile.rb
142
+ * Copyright (C) 2001-2009 Masao Mutoh <mutoh at highwhay.ne.jp>
143
+ * Copyright (C) 2001,2002 Masahiro Sakai <s01397ms at sfc.keio.ac.jp>
144
+
145
+ * gettext.rb
146
+ * Copyright (C) 2001-2009 Masao Mutoh <mutoh at highwhay.ne.jp>
147
+ * Copyright (C) 2001,2002 Masahiro Sakai <s01397ms at sfc.keio.ac.jp>
148
+
149
+ * rgettext
150
+ * Copyright (C) 2001-2009 Masao Mutoh <mutoh at highwhay.ne.jp>
151
+ * Copyright (C) 2001,2002 Yasushi Shoji <yashi at atmark-techno.com>
152
+
153
+ * setup.rb
154
+ * Copyright (C) 2000-2005 Minero Aoki <aamine at loveruby.net>
155
+ * This file is released under LGPL. See the top of the install.rb.
156
+
157
+ * Others
158
+ * Copyright (C) 2001-2009 Masao Mutoh <mutoh at highwhay.ne.jp>
159
+
160
+
161
+ == Translators
162
+ * Bosnian(bs) - Sanjin Sehic <saserr at gmail.com>
163
+ * Bulgarian(bg) - Sava Chankov <sava.chankov at gmail.com>
164
+ * Catalan(ca) - Ramon Salvadó <rsalvado at gnuine.com>
165
+ * Chinese(Simplified)(zh_CN)
166
+ * Yang Bob <bob.yang.dev at gmail.com> (current)
167
+ * Yingfeng <blogyingfeng at gmail.com>
168
+ * Chinese(Traditional)(zh_TW)
169
+ * Yang Bob <bob.yang.dev at gmail.com> (current)
170
+ * LIN CHUNG-YI <xmarsh at gmail.com>
171
+ * Croatian(hr) - Sanjin Sehic <saserr at gmail.com>
172
+ * Czech(cs) - Karel Miarka <kajism at yahoo.com>
173
+ * Dutch(nl) - Menno Jonkers <ruby-gettext at jonkers.com>
174
+ * Esperanto(eo) - Malte Milatz <malte at gmx-topmail.de>
175
+ * Estonian(et) - Erkki Eilonen <erkki at itech.ee>
176
+ * French(fr)
177
+ * Vincent Isambart <vincent.isambart at gmail.com> (current)
178
+ * David Sulc <davidsulc at gmail.com>
179
+ * Laurent Sansonetti <laurent.sansonetti at gmail.com>
180
+ * German(de)
181
+ * Patrick Lenz <patrick at limited-overload.de> (current)
182
+ * Detlef Reichl <detlef.reichl at gmx.org>
183
+ * Sven Herzberg <herzi at abi02.de>
184
+ * Sascha Ebach <se at digitale-wertschoepfung.de>
185
+ * Greek(el) - Vassilis Rizopoulos <damphyr at gmx.net>
186
+ * Hungarian(hu) - Tamás Tompa <tompata at gmail.com>
187
+ * Italian(it)
188
+ * Marco Lazzeri <marco.lazzeri at gmail.com>
189
+ * Gabriele Renzi <surrender_it at yahoo.it>
190
+ * Japanese(ja) - Masao Mutoh <mutoh at highway.ne.jp>
191
+ * Korean(ko) - Gyoung-Yoon Noh <nohmad at gmail.com>
192
+ * Latvian(lv) - Aivars Akots <aivars.akots at gmail.com>
193
+ * Norwegian(nb) - Runar Ingebrigtsen <runar at mopo.no>
194
+ * Portuguese(Brazil)(pt_BR)
195
+ * Antonio S. de A. Terceiro <terceiro at softwarelivre.org> (current)
196
+ * Joao Pedrosa <joaopedrosa at gmail.com>
197
+ * Russian(ru) - Yuri Kozlov <kozlov.y at gmail.com>
198
+ * Serbian(sr) - Slobodan Paunović" <slobodan.paunovic at gmail.com>
199
+ * Spanish(es)
200
+ * David Espada <davinci at escomposlinux.org> (current)
201
+ * David Moreno Garza <damog at damog.net>
202
+ * Swedish(sv) - Nikolai Weibull <mailing-lists.ruby-talk at rawuncut.elitemail.org>
203
+ * Ukrainian(ua) - Alex Rootoff <rootoff at pisem.net>
204
+ * Vietnamese(vi) - Ngoc Dao Thanh <ngocdaothanh at gmail.com>
205
+
206
+ == Status of translations
207
+ * Bosnian(bs) - 1.90.0 (old)
208
+ * Bulgarian(bg) - 2.0.0pre1 (new)
209
+ * Catalan(ca) - 2.0.0pre1
210
+ * Croatian(hr) - 1.90.0 (old)
211
+ * Chinese(zh_CN) - 2.0.0pre1
212
+ * Chinese(zh_TW) - 2.0.0pre1
213
+ * Czech(cs) - 1.9.0 (old)
214
+ * Dutch(nl) - 1.90.0 (old)
215
+ * English(default) - 1.90.0 (old)
216
+ * Esperanto(eo) - 2.0.0pre1
217
+ * Estonian(et) - 2.0.0pre1
218
+ * French(fr) - 2.0.0pre1
219
+ * German(de) - 2.0.0pre1
220
+ * Greek(el) - 2.0.0pre1
221
+ * Hungarian(hu) - 2.0.0pre1
222
+ * Italian(it) - 1.6.0 (old)
223
+ * Japanese(ja) - 2.0.0pre1
224
+ * Korean(ko) - 1.9.0 (old)
225
+ * Latvian(lv) - 2.0.0pre1 (new)
226
+ * Norwegian(nb) - 2.0.0pre1
227
+ * Portuguese(Brazil)(pt_BR) - 2.0.0pre1
228
+ * Russian(ru) - 2.0.0pre1
229
+ * Serbian(sr) - 1.91.0 (old)
230
+ * Spanish(es) - 2.0.0pre1
231
+ * Swedish(sv) - 0.8.0 (too much old)
232
+ * Ukrainian(ua) - 2.0.0pre1
233
+ * Vietnamese(vi) - 2.0.0pre1
234
+
235
+ == Maintainer
236
+ Masao Mutoh <mutoh at highway.ne.jp>
data/vendor/iconv.rb ADDED
@@ -0,0 +1,107 @@
1
+ =begin
2
+ iconv.rb - Pseudo Iconv class. Supports Iconv.iconv, Iconv.conv.
3
+
4
+ For Matz Ruby:
5
+ If you don't have iconv but glib2, this library uses glib2 iconv functions.
6
+
7
+ For JRuby:
8
+ Use Java String class to convert strings.
9
+
10
+ Copyright (C) 2004-2007 Masao Mutoh
11
+
12
+ You may redistribute it and/or modify it under the same
13
+ license terms as Ruby.
14
+
15
+ $Id: iconv.rb,v 1.6 2007/11/08 14:21:22 mutoh Exp $
16
+ =end
17
+
18
+ #Modifications
19
+ #wrapped inside FastGettext namespace to reduce conflic
20
+
21
+ begin
22
+ require 'iconv'
23
+ rescue LoadError
24
+ # Provides Iconv.iconv which normally is provided through Ruby/GLib(1) functions.
25
+ # This library is required for 'gettext'.
26
+ # If you require 'gettext/iconv', it tries to call Ruby/GLib function
27
+ # when it doesn't find original Iconv class(iconv.so) it adds a pseudo class.
28
+ #
29
+ # One-click Ruby Installer for Win32 hadn’t had iconv and there hadn’t been a way to install iconv.so itself for Win32.
30
+ # And JRuby hadn’t had Iconv.
31
+ # I’ve not checked them currently, but if they’ve supported iconv now, we don’t need this anymore...
32
+ #
33
+ # (1) Ruby/GLib is a module which is provided from Ruby-GNOME2 Project.
34
+ # You can get binaries for Win32(One-Click Ruby Installer).
35
+ # <URL: http://ruby-gnome2.sourceforge.jp/>
36
+ module Pomo
37
+ class Iconv2
38
+ module Failure; end
39
+ class InvalidEncoding < ArgumentError; include Failure; end
40
+ class IllegalSequence < ArgumentError; include Failure; end
41
+ class InvalidCharacter < ArgumentError; include Failure; end
42
+
43
+ if RUBY_PLATFORM =~ /java/
44
+ def self.conv(to, from, str)
45
+ raise InvalidCharacter, "the 3rd argument is nil" unless str
46
+ begin
47
+ str = java.lang.String.new(str.unpack("C*").to_java(:byte), from)
48
+ str.getBytes(to).to_ary.pack("C*")
49
+ rescue java.io.UnsupportedEncodingException
50
+ raise InvalidEncoding
51
+ end
52
+ end
53
+ else
54
+ begin
55
+ require 'glib2'
56
+
57
+ def self.check_glib_version?(major, minor, micro) # :nodoc:
58
+ (GLib::BINDING_VERSION[0] > major ||
59
+ (GLib::BINDING_VERSION[0] == major &&
60
+ GLib::BINDING_VERSION[1] > minor) ||
61
+ (GLib::BINDING_VERSION[0] == major &&
62
+ GLib::BINDING_VERSION[1] == minor &&
63
+ GLib::BINDING_VERSION[2] >= micro))
64
+ end
65
+
66
+ if check_glib_version?(0, 11, 0)
67
+ # This is a function equivalent of Iconv.iconv.
68
+ # * to: encoding name for destination
69
+ # * from: encoding name for source
70
+ # * str: strings to be converted
71
+ # * Returns: Returns an Array of converted strings.
72
+ def self.conv(to, from, str)
73
+ begin
74
+ GLib.convert(str, to, from)
75
+ rescue GLib::ConvertError => e
76
+ case e.code
77
+ when GLib::ConvertError::NO_CONVERSION
78
+ raise InvalidEncoding.new(str)
79
+ when GLib::ConvertError::ILLEGAL_SEQUENCE
80
+ raise IllegalSequence.new(str)
81
+ else
82
+ raise InvalidCharacter.new(str)
83
+ end
84
+ end
85
+ end
86
+ else
87
+ def self.conv(to, from, str) # :nodoc:
88
+ begin
89
+ GLib.convert(str, to, from)
90
+ rescue
91
+ raise IllegalSequence.new(str)
92
+ end
93
+ end
94
+ end
95
+ rescue LoadError
96
+ def self.conv(to, from, str) # :nodoc:
97
+ warn "Iconv was not found." if $DEBUG
98
+ str
99
+ end
100
+ end
101
+ end
102
+ def self.iconv(to, from, str)
103
+ conv(to, from, str).split(//)
104
+ end
105
+ end
106
+ end
107
+ end
data/vendor/mofile.rb ADDED
@@ -0,0 +1,296 @@
1
+ =begin
2
+ mofile.rb - A simple class for operating GNU MO file.
3
+
4
+ Copyright (C) 2003-2008 Masao Mutoh
5
+ Copyright (C) 2002 Masahiro Sakai, Masao Mutoh
6
+ Copyright (C) 2001 Masahiro Sakai
7
+
8
+ Masahiro Sakai <s01397ms at sfc.keio.ac.jp>
9
+ Masao Mutoh <mutoh at highway.ne.jp>
10
+
11
+ You can redistribute this file and/or modify it under the same term
12
+ of Ruby. License of Ruby is included with Ruby distribution in
13
+ the file "README".
14
+
15
+ $Id: mo.rb,v 1.10 2008/06/17 16:40:52 mutoh Exp $
16
+ =end
17
+
18
+ require File.join(File.dirname(__FILE__),'iconv')
19
+ require 'stringio'
20
+
21
+ #Modifications:
22
+ # use Iconv or FastGettext::Icvon
23
+
24
+ module Pomo
25
+ module GetText
26
+ class MOFile < Hash
27
+ class InvalidFormat < RuntimeError; end;
28
+
29
+ attr_reader :filename
30
+
31
+ Header = Struct.new(:magic,
32
+ :revision,
33
+ :nstrings,
34
+ :orig_table_offset,
35
+ :translated_table_offset,
36
+ :hash_table_size,
37
+ :hash_table_offset)
38
+
39
+ # The following are only used in .mo files
40
+ # with minor revision >= 1.
41
+ class HeaderRev1 < Header
42
+ attr_accessor :n_sysdep_segments,
43
+ :sysdep_segments_offset,
44
+ :n_sysdep_strings,
45
+ :orig_sysdep_tab_offset,
46
+ :trans_sysdep_tab_offset
47
+ end
48
+
49
+ MAGIC_BIG_ENDIAN = "\x95\x04\x12\xde"
50
+ MAGIC_LITTLE_ENDIAN = "\xde\x12\x04\x95"
51
+
52
+ def self.open(arg = nil, output_charset = nil)
53
+ result = self.new(output_charset)
54
+ result.load(arg)
55
+ end
56
+
57
+ def initialize(output_charset = nil)
58
+ @filename = nil
59
+ @last_modified = nil
60
+ @little_endian = true
61
+ @output_charset = output_charset
62
+ super()
63
+ end
64
+
65
+ def update!
66
+ if FileTest.exist?(@filename)
67
+ st = File.stat(@filename)
68
+ load(@filename) unless (@last_modified == [st.ctime, st.mtime])
69
+ else
70
+ warn "#{@filename} was lost." if $DEBUG
71
+ clear
72
+ end
73
+ self
74
+ end
75
+
76
+ def load(arg)
77
+ if arg.kind_of? String
78
+ begin
79
+ st = File.stat(arg)
80
+ @last_modified = [st.ctime, st.mtime]
81
+ rescue Exception
82
+ end
83
+ load_from_file(arg)
84
+ else
85
+ load_from_stream(arg)
86
+ end
87
+ @filename = arg
88
+ self
89
+ end
90
+
91
+ def load_from_stream(io)
92
+ magic = io.read(4)
93
+ case magic
94
+ when MAGIC_BIG_ENDIAN
95
+ @little_endian = false
96
+ when MAGIC_LITTLE_ENDIAN
97
+ @little_endian = true
98
+ else
99
+ raise InvalidFormat.new(sprintf("Unknown signature %s", magic.dump))
100
+ end
101
+
102
+ endian_type6 = @little_endian ? 'V6' : 'N6'
103
+ endian_type_astr = @little_endian ? 'V*' : 'N*'
104
+
105
+ header = HeaderRev1.new(magic, *(io.read(4 * 6).unpack(endian_type6)))
106
+
107
+ if header.revision == 1
108
+ # FIXME: It doesn't support sysdep correctly.
109
+ header.n_sysdep_segments = io.read(4).unpack(endian_type6)
110
+ header.sysdep_segments_offset = io.read(4).unpack(endian_type6)
111
+ header.n_sysdep_strings = io.read(4).unpack(endian_type6)
112
+ header.orig_sysdep_tab_offset = io.read(4).unpack(endian_type6)
113
+ header.trans_sysdep_tab_offset = io.read(4).unpack(endian_type6)
114
+ elsif header.revision > 1
115
+ raise InvalidFormat.new(sprintf("file format revision %d isn't supported", header.revision))
116
+ end
117
+ io.pos = header.orig_table_offset
118
+ orig_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
119
+
120
+ io.pos = header.translated_table_offset
121
+ trans_table_data = io.read((4 * 2) * header.nstrings).unpack(endian_type_astr)
122
+
123
+ original_strings = Array.new(header.nstrings)
124
+ for i in 0...header.nstrings
125
+ io.pos = orig_table_data[i * 2 + 1]
126
+ original_strings[i] = io.read(orig_table_data[i * 2 + 0])
127
+ end
128
+
129
+ clear
130
+ for i in 0...header.nstrings
131
+ io.pos = trans_table_data[i * 2 + 1]
132
+ str = io.read(trans_table_data[i * 2 + 0])
133
+
134
+ if (! original_strings[i]) || original_strings[i] == ""
135
+ if str
136
+ @charset = nil
137
+ @nplurals = nil
138
+ @plural = nil
139
+ str.each_line{|line|
140
+ if /^Content-Type:/i =~ line and /charset=((?:\w|-)+)/i =~ line
141
+ @charset = $1
142
+ elsif /^Plural-Forms:\s*nplurals\s*\=\s*(\d*);\s*plural\s*\=\s*([^;]*)\n?/ =~ line
143
+ @nplurals = $1
144
+ @plural = $2
145
+ end
146
+ break if @charset and @nplurals
147
+ }
148
+ @nplurals = "1" unless @nplurals
149
+ @plural = "0" unless @plural
150
+ end
151
+ else
152
+ if @output_charset
153
+ begin
154
+ iconv = Iconv || FastGettext::Iconv
155
+ str = iconv.conv(@output_charset, @charset, str) if @charset
156
+ rescue iconv::Failure
157
+ if $DEBUG
158
+ warn "@charset = ", @charset
159
+ warn"@output_charset = ", @output_charset
160
+ warn "msgid = ", original_strings[i]
161
+ warn "msgstr = ", str
162
+ end
163
+ end
164
+ end
165
+ end
166
+ self[original_strings[i]] = str.freeze
167
+ end
168
+ self
169
+ end
170
+
171
+ # Is this number a prime number ?
172
+ # http://apidock.com/ruby/Prime
173
+ def prime?(number)
174
+ ('1' * number) !~ /^1?$|^(11+?)\1+$/
175
+ end
176
+
177
+ def next_prime(seed)
178
+ require 'mathn'
179
+ prime = Prime.new
180
+ while current = prime.succ
181
+ return current if current > seed
182
+ end
183
+ end
184
+
185
+ # From gettext-0.12.1/gettext-runtime/intl/hash-string.h
186
+ # Defines the so called `hashpjw' function by P.J. Weinberger
187
+ # [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
188
+ # 1986, 1987 Bell Telephone Laboratories, Inc.]
189
+ HASHWORDBITS = 32
190
+ def hash_string(str)
191
+ hval = 0
192
+ i = 0
193
+ str.each_byte do |b|
194
+ break if b == '\0'
195
+ hval <<= 4
196
+ hval += b.to_i
197
+ g = hval & (0xf << (HASHWORDBITS - 4))
198
+ if (g != 0)
199
+ hval ^= g >> (HASHWORDBITS - 8)
200
+ hval ^= g
201
+ end
202
+ end
203
+ hval
204
+ end
205
+
206
+ def save_to_stream(io)
207
+ #Save data as little endian format.
208
+ header_size = 4 * 7
209
+ table_size = 4 * 2 * size
210
+
211
+ hash_table_size = next_prime((size * 4) / 3)
212
+ hash_table_size = 3 if hash_table_size <= 2
213
+ header = Header.new(
214
+ MAGIC_LITTLE_ENDIAN, # magic
215
+ 0, # revision
216
+ size, # nstrings
217
+ header_size, # orig_table_offset
218
+ header_size + table_size, # translated_table_offset
219
+ hash_table_size, # hash_table_size
220
+ header_size + table_size * 2 # hash_table_offset
221
+ )
222
+ io.write(header.to_a.pack('a4V*'))
223
+
224
+ ary = to_a
225
+ ary.sort!{|a, b| a[0] <=> b[0]} # sort by original string
226
+
227
+ pos = header.hash_table_size * 4 + header.hash_table_offset
228
+
229
+ orig_table_data = Array.new()
230
+ ary.each{|item, _|
231
+ orig_table_data.push(item.size)
232
+ orig_table_data.push(pos)
233
+ pos += item.size + 1 # +1 is <NUL>
234
+ }
235
+ io.write(orig_table_data.pack('V*'))
236
+
237
+ trans_table_data = Array.new()
238
+ ary.each{|_, item|
239
+ trans_table_data.push(item.size)
240
+ trans_table_data.push(pos)
241
+ pos += item.size + 1 # +1 is <NUL>
242
+ }
243
+ io.write(trans_table_data.pack('V*'))
244
+
245
+ hash_tab = Array.new(hash_table_size)
246
+ j = 0
247
+ ary[0...size].each {|key, _|
248
+ hash_val = hash_string(key)
249
+ idx = hash_val % hash_table_size
250
+ if hash_tab[idx] != nil
251
+ incr = 1 + (hash_val % (hash_table_size - 2))
252
+ begin
253
+ if (idx >= hash_table_size - incr)
254
+ idx -= hash_table_size - incr
255
+ else
256
+ idx += incr
257
+ end
258
+ end until (hash_tab[idx] == nil)
259
+ end
260
+ hash_tab[idx] = j + 1
261
+ j += 1
262
+ }
263
+ hash_tab.collect!{|i| i ? i : 0}
264
+
265
+ io.write(hash_tab.pack('V*'))
266
+
267
+ ary.each{|item, _| io.write(item); io.write("\0") }
268
+ ary.each{|_, item| io.write(item); io.write("\0") }
269
+
270
+ self
271
+ end
272
+
273
+ def load_from_file(filename)
274
+ @filename = filename
275
+ begin
276
+ File.open(filename, 'rb'){|f| load_from_stream(f)}
277
+ rescue => e
278
+ e.set_backtrace("File: #{@filename}")
279
+ raise e
280
+ end
281
+ end
282
+
283
+ def save_to_file(filename)
284
+ File.open(filename, 'wb'){|f| save_to_stream(f)}
285
+ end
286
+
287
+ def set_comment(msgid_or_sym, comment)
288
+ #Do nothing
289
+ end
290
+
291
+
292
+ attr_accessor :little_endian, :path, :last_modified
293
+ attr_reader :charset, :nplurals, :plural
294
+ end
295
+ end
296
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grosser-pomo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -22,16 +22,27 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
- - VERSION.yml
26
25
  - README.markdown
26
+ - VERSION.yml
27
27
  - lib/pomo
28
+ - lib/pomo.rb
29
+ - lib/pomo/mo_file.rb
28
30
  - lib/pomo/po_file.rb
29
31
  - lib/pomo/translation.rb
30
- - lib/pomo.rb
32
+ - spec/files
33
+ - spec/files/complex.mo
34
+ - spec/files/empty.mo
35
+ - spec/files/plural.mo
36
+ - spec/files/singular.mo
37
+ - spec/files/singular_2.mo
31
38
  - spec/pomo
39
+ - spec/pomo/mo_file_spec.rb
32
40
  - spec/pomo/po_file_spec.rb
33
41
  - spec/pomo/translation_spec.rb
34
42
  - spec/spec_helper.rb
43
+ - vendor/README.rdoc
44
+ - vendor/iconv.rb
45
+ - vendor/mofile.rb
35
46
  has_rdoc: true
36
47
  homepage: http://github.com/grosser/pomo
37
48
  post_install_message: