grosser-pomo 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ A simple and extendable .mo and .po file parser/generator.
2
+ --mo file parser and writer are missing atm--
3
+
4
+ Advanteges over [original po-parser](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/poparser.rb):
5
+
6
+ - simple architecture + easy to extend/modify
7
+ - emtpy msgstr translations are read
8
+ - comments are included
9
+ - fuzzy can be set/unset
10
+ - multiple translations can be combined in a new po file(with comments and fuzzy and ...)
11
+ - po files can be written from any kind of input
12
+
13
+ Setup
14
+ =====
15
+ sudo gem install grosser-pomo -s http://gems.github.com/
16
+
17
+ ###Static interface
18
+ #parse po files
19
+ translations = Pomo::PoFile.parse(File.read('xxx.po')) + Pomo::PoFile.parse(File.read('yyy.po'))
20
+
21
+ #and use the data...
22
+ msgids = translations.reject{|t|t.plural? or t.fuzzy?}.map(&:msgid)
23
+
24
+ #or write a new po file (unique by msgid)...
25
+ File.open('xxx.po','w){|f|f.print(Pomo::PoFile.to_text(translations))}
26
+
27
+ ###Instance interface
28
+ p = PoMo::PoFile.new
29
+ p.add_translations_from_text(File.read('...'))
30
+ ...
31
+ p.translations
32
+ p.to_text
33
+
34
+ TODO
35
+ ====
36
+ - extracting of version/pluralisation_rule/plurals/translator... (from msgid "")
37
+ - mo writing/reading (this is the hardest part imo...)
38
+
39
+ Author
40
+ ======
41
+ [Michael Grosser](http://pragmatig.wordpress.com)
42
+ grosser.michael@gmail.com
43
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 4
3
+ :patch: 1
4
+ :major: 0
data/lib/pomo.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'pomo/po_file'
2
+ module Pomo
3
+ end
@@ -0,0 +1,117 @@
1
+ require 'pomo/translation'
2
+
3
+ module Pomo
4
+ class PoFile
5
+ def self.parse(text)
6
+ PoFile.new.add_translations_from_text(text)
7
+ end
8
+
9
+ def self.to_text(translations)
10
+ p = PoFile.new(:translations=>translations)
11
+ p.to_text
12
+ end
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
+ attr_reader :translations
21
+
22
+ def initialize(options = {})
23
+ @translations = options[:translations] || []
24
+ end
25
+
26
+ #the text is split into lines and then converted into logical translations
27
+ #each translation consists of comments(that come before a translation)
28
+ #and a msgid / msgstr
29
+ def add_translations_from_text(text)
30
+ start_new_translation
31
+ text.split(/$/).each_with_index do |line,index|
32
+ @line_number = index + 1
33
+ next if line.empty?
34
+ if method_call? line
35
+ parse_method_call line
36
+ elsif comment? line
37
+ add_comment line
38
+ else
39
+ add_string line
40
+ end
41
+ end
42
+ start_new_translation #instance_variable has to be overwritten or errors can occur on next add
43
+ translations
44
+ end
45
+
46
+ def to_text
47
+ self.class.unique_translations(translations).map {|translation|
48
+ comment = translation.comment.to_s.split(/\n|\r\n/).map{|line|"##{line}\n"}*''
49
+ msgid_and_msgstr = if translation.plural?
50
+ msgids =
51
+ %Q(msgid "#{translation.msgid[0]}"\n)+
52
+ %Q(msgid_plural "#{translation.msgid[1]}"\n)
53
+
54
+ msgstrs = []
55
+ translation.msgstr.each_with_index do |msgstr,index|
56
+ msgstrs << %Q(msgstr[#{index}] "#{msgstr}")
57
+ end
58
+
59
+ msgids + (msgstrs*"\n")
60
+ else
61
+ %Q(msgid "#{translation.msgid}"\n)+
62
+ %Q(msgstr "#{translation.msgstr}")
63
+ end
64
+
65
+ comment + msgid_and_msgstr
66
+ } * "\n\n"
67
+ end
68
+
69
+ private
70
+
71
+ #e.g. # fuzzy
72
+ def comment?(line)
73
+ line =~ /^\s*#/
74
+ end
75
+
76
+ def add_comment(line)
77
+ start_new_translation if translation_complete?
78
+ @current_translation.add_text(line.strip.sub('#','')+"\n",:to=>:comment)
79
+ end
80
+
81
+ #msgid "hello"
82
+ def method_call?(line)
83
+ line =~ /^\s*[a-z]/
84
+ end
85
+
86
+ #msgid "hello" -> method call msgid + add string "hello"
87
+ def parse_method_call(line)
88
+ method, string = line.match(/^\s*([a-z0-9_\[\]]+)(.*)/)[1..2]
89
+ raise "no method found" unless method
90
+
91
+ start_new_translation if method == 'msgid' and translation_complete?
92
+ @last_method = method.to_sym
93
+ add_string(string)
94
+ end
95
+
96
+ #"hello" -> hello
97
+ def add_string(string)
98
+ return if string.strip.empty?
99
+ raise "not string format: #{string.inspect} on line #{@line_number}" unless string.strip =~ /^['"](.*)['"]$/
100
+ @current_translation.add_text($1,:to=>@last_method)
101
+ end
102
+
103
+ def translation_complete?
104
+ return false unless @current_translation
105
+ @current_translation.complete?
106
+ end
107
+
108
+ def store_translation
109
+ @translations += [@current_translation] if @current_translation.complete?
110
+ end
111
+
112
+ def start_new_translation
113
+ store_translation if translation_complete?
114
+ @current_translation = Translation.new
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,44 @@
1
+ module Pomo
2
+ class Translation
3
+ FUZZY_REGEX = /^\s*fuzzy\s*$/
4
+ attr_accessor :msgid, :msgstr, :comment
5
+
6
+ def add_text(text,options)
7
+ to = options[:to]
8
+ if to.to_sym == :msgid_plural
9
+ self.msgid = [msgid] unless msgid.is_a? Array
10
+ msgid[1] = msgid[1].to_s + text
11
+ elsif to.to_s =~ /^msgstr\[(\d)\]$/
12
+ self.msgstr ||= []
13
+ msgstr[$1.to_i] = msgstr[$1.to_i].to_s + text
14
+ else
15
+ #simple form
16
+ send("#{to}=",send(to).to_s+text)
17
+ end
18
+ end
19
+
20
+ def to_hash
21
+ {:msgid=>msgid,:msgstr=>msgstr,:comment=>comment}.reject{|k,value|value.nil?}
22
+ end
23
+
24
+ def complete?
25
+ not msgid.to_s.strip.empty? and not msgstr.nil?
26
+ end
27
+
28
+ def fuzzy?
29
+ comment =~ FUZZY_REGEX
30
+ end
31
+
32
+ def fuzzy=(value)
33
+ if value and not fuzzy?
34
+ add_text "\nfuzzy", :to=>:comment
35
+ else
36
+ self.comment = comment.to_s.split(/$/).reject{|line|line=~FUZZY_REGEX}.join("\n")
37
+ end
38
+ end
39
+
40
+ def plural?
41
+ msgid.is_a? Array or msgstr.is_a? Array
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,110 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ include Pomo
4
+ describe Pomo::PoFile do
5
+ it "parses nothing" do
6
+ PoFile.parse("").should be_empty
7
+ end
8
+
9
+ it "parses a simple msgid and msgstr" do
10
+ t = PoFile.parse(%Q(msgid "xxx"\nmsgstr "yyy"))
11
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy'}
12
+ end
13
+
14
+ it "parses a simple msgid and msg with additional whitespace" do
15
+ t = PoFile.parse(%Q( msgid "xxx" \n msgstr "yyy" ))
16
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy'}
17
+ end
18
+
19
+ it "parses a multiline msgid/msgstr" do
20
+ t = PoFile.parse(%Q(msgid "xxx"\n"aaa"\n\n\nmsgstr ""\n"bbb"))
21
+ t[0].to_hash.should == {:msgid=>'xxxaaa',:msgstr=>'bbb'}
22
+ end
23
+
24
+ it "parses simple comments" do
25
+ t = PoFile.parse(%Q(#test\nmsgid "xxx"\nmsgstr "yyy"))
26
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy',:comment=>"test\n"}
27
+ end
28
+
29
+ it "parses comments above msgstr" do
30
+ t = PoFile.parse(%Q(#test\nmsgid "xxx"\n#another\nmsgstr "yyy"))
31
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy',:comment=>"test\nanother\n"}
32
+ end
33
+
34
+ describe "instance interface" do
35
+ it "adds two different translations" do
36
+ p = PoFile.new
37
+ p.add_translations_from_text(%Q(msgid "xxx"\nmsgstr "yyy"))
38
+ p.add_translations_from_text(%Q(msgid "aaa"\nmsgstr "yyy"))
39
+ p.translations[1].to_hash.should == {:msgid=>'aaa',:msgstr=>'yyy'}
40
+ end
41
+
42
+ it "can be initialized with translations" do
43
+ p = PoFile.new(:translations=>['xxx'])
44
+ p.translations[0].should == 'xxx'
45
+ end
46
+
47
+ it "can be converted to text" do
48
+ text = %Q(msgid "xxx"\nmsgstr "aaa")
49
+ p = PoFile.new
50
+ p.add_translations_from_text(text)
51
+ p.to_text.should == text
52
+ end
53
+
54
+ it "keeps uniqueness when converting to_text" do
55
+ text = %Q(msgid "xxx"\nmsgstr "aaa")
56
+ p = PoFile.new
57
+ p.add_translations_from_text(%Q(msgid "xxx"\nmsgstr "yyy"))
58
+ p.add_translations_from_text(text)
59
+ p.to_text.should == text
60
+ end
61
+ end
62
+
63
+ it "adds plural translations" do
64
+ t = PoFile.parse(%Q(msgid "singular"\nmsgid_plural "plural"\nmsgstr[0] "one"\nmsgstr[1] "many"))
65
+ t[0].to_hash.should == {:msgid=>['singular','plural'],:msgstr=>['one','many']}
66
+ end
67
+
68
+ it "does not fail on empty string" do
69
+ PoFile.parse(%Q(\n\n\n\n\n))
70
+ end
71
+
72
+ it "shows line number for invalid strings" do
73
+ begin
74
+ PoFile.parse(%Q(\n\n\n\n\nmsgstr "))
75
+ flunk
76
+ rescue Exception => e
77
+ e.to_s.should =~ /line 5/
78
+ end
79
+ end
80
+
81
+ describe :to_text do
82
+ it "is empty when not translations where added" do
83
+ PoFile.to_text([]).should == ""
84
+ end
85
+
86
+ it "preserves simple syntax" do
87
+ text = %Q(msgid "x"\nmsgstr "y")
88
+ PoFile.to_text(PoFile.parse(text)).should == text
89
+ end
90
+
91
+ it "adds comments" do
92
+ t = Pomo::Translation.new
93
+ t.msgid = 'a'
94
+ t.msgstr = 'b'
95
+ t.add_text("c\n",:to=>:comment)
96
+ t.add_text("d\n",:to=>:comment)
97
+ PoFile.to_text([t]).should == %Q(#c\n#d\nmsgid "a"\nmsgstr "b")
98
+ end
99
+
100
+ it "uses plural notation" do
101
+ text = %Q(#awesome\nmsgid "one"\nmsgid_plural "many"\nmsgstr[0] "1"\nmsgstr[1] "n")
102
+ PoFile.to_text(PoFile.parse(text)).should == text
103
+ end
104
+
105
+ it "only uses the latest of identicals msgids" do
106
+ text = %Q(msgid "one"\nmsgstr "1"\nmsgid "one"\nmsgstr "001")
107
+ PoFile.to_text(PoFile.parse(text)).should == %Q(msgid "one"\nmsgstr "001")
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,119 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ require 'pomo/translation'
4
+
5
+ describe Pomo::Translation do
6
+ describe :complete? do
7
+ it{should_not be_complete}
8
+
9
+ it "is complete if it has a msgid and a msgstr" do
10
+ subject.msgid="x"
11
+ subject.msgstr = "y"
12
+ should be_complete
13
+ end
14
+
15
+ it "is not complete if it has an complete msgid" do
16
+ subject.msgid=""
17
+ should_not be_complete
18
+ end
19
+
20
+ it "is not complete if it has a nil msgstr" do
21
+ subject.msgid="x"
22
+ should_not be_complete
23
+ end
24
+
25
+ it "is complete if it has an complete msgstr" do
26
+ subject.msgid = "x"
27
+ subject.msgstr = ""
28
+ should be_complete
29
+ end
30
+ end
31
+
32
+ describe :add_text do
33
+ it "adds a simple msgid" do
34
+ subject.add_text("x",:to=>:msgid)
35
+ subject.msgid.should == "x"
36
+ end
37
+
38
+ it "converts msgid to plural when msgid_plural is added" do
39
+ subject.add_text("x",:to=>:msgid)
40
+ subject.add_text("y",:to=>:msgid_plural)
41
+ subject.msgid.should == ["x",'y']
42
+ end
43
+
44
+ it "can add additional text to msgid plurals" do
45
+ subject.add_text("y",:to=>:msgid_plural)
46
+ subject.add_text("a",:to=>:msgid_plural)
47
+ subject.msgid.should == [nil,'ya']
48
+ end
49
+
50
+ it "can add plural msggstr" do
51
+ subject.add_text("x",:to=>'msgstr[0]')
52
+ subject.msgstr.should == ['x']
53
+ end
54
+
55
+ it "can add multiple plural msggstr" do
56
+ subject.add_text("x",:to=>'msgstr[0]')
57
+ subject.add_text("a",:to=>'msgstr[1]')
58
+ subject.add_text("y",:to=>'msgstr[1]')
59
+ subject.msgstr.should == ['x','ay']
60
+ end
61
+ end
62
+
63
+ describe :plural? do
64
+ it{should_not be_plural}
65
+
66
+ it "is plural if msgid is plural" do
67
+ subject.add_text("x",:to=>:msgid_plural)
68
+ should be_plural
69
+ end
70
+
71
+ it "is plural if msgstr is plural" do
72
+ subject.add_text("x",:to=>"msgstr[0]")
73
+ should be_plural
74
+ end
75
+
76
+ it "is not plural if simple strings where added" do
77
+ subject.msgid = "a"
78
+ subject.msgstr = "a"
79
+ should_not be_plural
80
+ end
81
+ end
82
+
83
+ describe :fuzzy? do
84
+ it{should_not be_fuzzy}
85
+
86
+ it "is fuzzy if a fuzzy comment was added" do
87
+ subject.add_text("fuzzy",:to=>:comment)
88
+ should be_fuzzy
89
+ end
90
+
91
+ it "can be made fuzzy by using fuzzy=" do
92
+ subject.fuzzy = true
93
+ should be_fuzzy
94
+ end
95
+
96
+ it "can be made unfuzzy by using fuzzy=" do
97
+ subject.fuzzy = false
98
+ should_not be_fuzzy
99
+ end
100
+
101
+ it "changes comment when made fuzzy through fuzzy=" do
102
+ subject.comment = "hello"
103
+ subject.fuzzy = true
104
+ subject.comment.should == "hello\nfuzzy"
105
+ end
106
+
107
+ it "changes empty comment when made fuzzy through fuzzy=" do
108
+ subject.fuzzy = true
109
+ subject.comment.should == "\nfuzzy"
110
+ end
111
+
112
+ it "preserves comments when making fuzzy/unfuzzy" do
113
+ subject.comment = "hello"
114
+ subject.fuzzy = true
115
+ subject.fuzzy = false
116
+ subject.comment.should == "hello"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,10 @@
1
+ # ---- requirements
2
+ $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__))
3
+ require 'pomo'
4
+
5
+ # ---- Helpers
6
+ def pending_it(text,&block)
7
+ it text do
8
+ pending(&block)
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grosser-pomo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-31 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: grosser.michael@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - README.markdown
27
+ - lib/pomo
28
+ - lib/pomo/po_file.rb
29
+ - lib/pomo/translation.rb
30
+ - lib/pomo.rb
31
+ - spec/pomo
32
+ - spec/pomo/po_file_spec.rb
33
+ - spec/pomo/translation_spec.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/grosser/pomo
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --inline-source
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.2.0
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: "Ruby/Gettext: A .po and .mo file parser/generator"
62
+ test_files: []
63
+