merb_global 0.0.4.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ require 'gettext'
2
+ require 'treetop'
3
+ require 'pathname'
4
+
5
+ # I'm not sure if it is the correct way of doing it.
6
+ # As far it seems to be simpler.
7
+ class Thread #:nodoc:
8
+ def gettext_context
9
+ @gettext_context ||= Merb::Global::MessageProviders::Gettext::GettextContext.new
10
+ end
11
+ end
12
+
13
+ module Merb
14
+ module Global
15
+ module MessageProviders
16
+ class Gettext #:nodoc: all
17
+ include Merb::Global::MessageProviders::Base
18
+ include Merb::Global::MessageProviders::Base::Importer
19
+ include Merb::Global::MessageProviders::Base::Exporter
20
+
21
+ def localize(singular, plural, opts)
22
+ context = Thread.current.gettext_context
23
+ context.set_locale opts[:lang], true
24
+ unless plural.nil?
25
+ context.ngettext singular, plural, opts[:n]
26
+ else
27
+ context.gettext singular
28
+ end
29
+ end
30
+
31
+ def support?(lang)
32
+ lang == 'en' ||
33
+ File.exist?(File.join(Merb::Global::MessageProviders.localedir, lang))
34
+ end
35
+
36
+ def create!
37
+ File.mkdirs Merb::Global::MessageProviders.localedir
38
+ end
39
+
40
+ def choose(except)
41
+ dir = Dir[Merb::Global::MessageProviders.localedir + '/*/']
42
+ dir.collect! {|p| File.basename p}
43
+ dir << 'en'
44
+ dir.reject! {|lang| except.include? lang}
45
+ dir.first
46
+ end
47
+
48
+ def import
49
+ Treetop.load(Pathname(__FILE__).dirname.expand_path.to_s +
50
+ '/gettext')
51
+ parser = Merb::Global::MessageProviders::GetTextParser.new
52
+ data = {}
53
+ Dir[Merb::Global::MessageProviders.localedir +
54
+ '/*.po'].each do |file|
55
+ lang_name = File.basename file, '.po'
56
+ lang_tree = nil
57
+ open file do |f|
58
+ lang_tree = parser.parse f.read
59
+ end
60
+ # Put the parsed file in data
61
+ data[lang_name] = lang_tree.to_hash
62
+ # Remove the metadata to futher managing
63
+ opts = data[lang_name].delete('')[nil].split("\n")
64
+ # Find the line about plural line
65
+ plural_line = nil
66
+ for l in opts
67
+ if l[0..."Plural-Forms:".length] == "Plural-Forms:"
68
+ plural_line = l
69
+ break
70
+ end
71
+ end
72
+ # Remove the "Plural-Forms:" from the beginning...
73
+ plural_line =
74
+ plural_line["Plural-Forms:".length...plural_line.length]
75
+ # and ; from end
76
+ plural_line = plural_line[0...plural_line.length-1]
77
+ # Split the line and build the hash
78
+ plural_line = plural_line.gsub(/[[:space:]]/, '').split(/[=;]/, 4)
79
+ # And change the plural and nplurals into :plural and :nplurals
80
+ plural_line[2] = :plural
81
+ plural_line[0] = :nplural
82
+ # Adn the nplural value into integer
83
+ plural_line[1] = plural_line[1].to_i
84
+ data[lang_name].merge! Hash[*plural_line]
85
+ end
86
+ data
87
+ end
88
+
89
+ def export(data)
90
+ data.each do |lang_name, lang|
91
+ lang_file = File.join(Merb::Global::MessageProviders.localedir,
92
+ lang_name + '.po')
93
+ open(lang_file, 'w') do |po|
94
+ po.puts <<EOF
95
+ msgid ""
96
+ msgstr ""
97
+ "Project-Id-Version: 0.0.1\\n"
98
+ "POT-Creation-Date: #{Time.now.strftime('%Y-%m-%d %H:%M%z')}\\n"
99
+ "PO-Revision-Date: #{Time.now.strftime('%Y-%m-%d %H:%M%z')}\\n"
100
+ "Last-Translator: <user@example.com>\\n"
101
+ "Language-Team: Language type\\n"
102
+ "MIME-Version: 1.0\\n"
103
+ "Content-Type: text/plain; charset=UTF-8\\n"
104
+ "Content-Transfer-Encoding: 8bit\\n"
105
+ "Plural-Forms: nplurals=#{lang[:nplurals]}; plural=#{lang[:plural]}\\n"
106
+ EOF
107
+
108
+ lang.each do |msgid, msgstr_hash|
109
+ po.puts ""
110
+ po.puts "msgid \"#{msgid}\""
111
+ if msgstr_hash[:plural]
112
+ po.puts "msgid_plural \"#{msgstr_hash[:plural]}\""
113
+ msgstr_hash.each do |msgstr_index, msgstr|
114
+ po.puts "msgstr[#{msgstr_index}] \"#{msgstr}\""
115
+ end
116
+ else
117
+ po.puts "msgstr \"#{msgstr_hash[nil]}\""
118
+ end
119
+ end
120
+ end
121
+ lang_dir = File.join(Merb::Global::MessageProviders.localedir,
122
+ lang, 'LC_MESSAGES')
123
+ FileUtils.mkdir_p lang_dir
124
+ domain = Merb::Global.config([:gettext, :domain], 'merbapp')
125
+ `msgfmt #{lang_file} -o #{lang_dir}/#{domain}.mo`
126
+ end
127
+ end
128
+
129
+ class GettextContext
130
+ include ::GetText
131
+ bindtextdomain Merb::Global.config([:gettext, :domain], 'merbapp'),
132
+ Merb::Global::MessageProviders.localedir
133
+ public :set_locale, :ngettext, :gettext
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,60 @@
1
+ module Merb
2
+ module Global
3
+ module MessageProviders
4
+ grammar GetText
5
+ rule po_file
6
+ entry* {
7
+ def to_hash
8
+ hash = {}
9
+ elements.each {|entry| hash.merge! entry.to_hash}
10
+ hash
11
+ end
12
+ }
13
+ end
14
+
15
+ rule entry
16
+ whitespaces*
17
+ "msgid" whitespaces msgid:strings
18
+ "msgstr" whitespaces msgstr:strings {
19
+ def to_hash
20
+ {msgid.to_string => {:plural => nil, nil => msgstr.to_string}}
21
+ end
22
+ }
23
+ /
24
+ whitespaces*
25
+ "msgid" whitespaces msgid:strings
26
+ "msgid_plural" whitespaces msgid_plural:strings
27
+ msgstrs:("msgstr[" number:[0-9]+ "]" whitespaces strings)+ {
28
+ def to_hash
29
+ hash = {:plural => msgid_plural.to_string}
30
+ msgstrs.elements.each do |msgstr|
31
+ hash[msgstr.number.text_value.to_i] = msgstr.strings.to_string
32
+ end
33
+ {msgid.to_string => hash}
34
+ end
35
+ }
36
+ end
37
+
38
+ rule strings
39
+ (string whitespaces?)+ {
40
+ def to_string
41
+ elements.collect {|element| element.string.to_string}.join
42
+ end
43
+ }
44
+ end
45
+
46
+ rule string
47
+ '"' content:((!'"' ('\"' / .))*) '"' {
48
+ def to_string
49
+ content.text_value.gsub(/\\n/, "\n")
50
+ end
51
+ }
52
+ end
53
+
54
+ rule whitespaces
55
+ (" " / "\t" / "\n" / ('#' (!"\n" .)* "\n") )+
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,10 +1,10 @@
1
1
  module Merb
2
2
  module Global
3
- module Providers
3
+ module MessageProviders
4
4
  class Mock #:nodoc:
5
- include Merb::Global::Provider
6
-
7
- def translate_to(singular, plural, opts)
5
+ include Merb::Global::MessageProviders::Base
6
+
7
+ def localize(singular, plural, opts)
8
8
  opts[:n] > 1 ? plural : singular
9
9
  end
10
10
 
@@ -0,0 +1,105 @@
1
+ require 'sequel'
2
+ require 'merb_global/plural'
3
+
4
+ module Merb
5
+ module Global
6
+ module MessageProviders
7
+ class Sequel #:nodoc: all
8
+ include Merb::Global::MessageProviders::Base
9
+ include Merb::Global::MessageProviders::Base::Importer
10
+ include Merb::Global::MessageProviders::Base::Exporter
11
+
12
+ def localize(singular, plural, opts)
13
+ language = Language[:name => opts[:lang]] # I hope it's from MemCache
14
+ unless language.nil?
15
+ unless plural.nil?
16
+ n = Plural.which_form opts[:n], language[:plural]
17
+ translation = Translation[language.pk, singular, n]
18
+ else
19
+ translation = Translation[language.pk, singular, nil]
20
+ end
21
+ return translation[:msgstr] unless translation.nil?
22
+ end
23
+ return opts[:n] > 1 ? plural : singular # Fallback if not in database
24
+ end
25
+
26
+ def support?(lang)
27
+ Language.filter(:name => lang).count != 0
28
+ end
29
+
30
+ def create!
31
+ migration_exists = Dir[File.join(Merb.root, 'schema',
32
+ 'migrations', "*.rb")].detect do |f|
33
+ f =~ /translations\.rb/
34
+ end
35
+ if migration_exists
36
+ puts "\nThe Translation Migration File already exists\n\n"
37
+ else
38
+ sh %{merb-gen translations_migration}
39
+ end
40
+ end
41
+
42
+ def choose(except)
43
+ Language.filter(~{:name => except}).first[:name]
44
+ end
45
+
46
+ def import
47
+ data = {}
48
+ DB.transaction do
49
+ Language.each do |lang|
50
+ data[lang[:name]] = lang_hash = {
51
+ :plural => lang[:plural],
52
+ :nplural => lang[:nplural]
53
+ }
54
+ lang.translations.each do |translation|
55
+ lang_hash[translation[:msgid]] ||= {
56
+ :plural => translation[:msgid_plural]
57
+ }
58
+ lang_hash[translation[:msgid]][translation[:msgstr_index]] =
59
+ translation[:msgstr]
60
+ end
61
+ end
62
+ end
63
+ data
64
+ end
65
+
66
+ def export(data)
67
+ DB.transaction do
68
+ Language.delete_all
69
+ Translation.delete_all
70
+ data.each do |lang_name, lang|
71
+ lang_obj = Language.create(:name => lang_name,
72
+ :plural => lang[:plural],
73
+ :nplural => lang[:nplural]) or raise
74
+ lang_id = lang_obj[:id]
75
+ lang.each do |msgid, msgstrs|
76
+ if msgid.is_a? String
77
+ plural = msgstrs[:plural]
78
+ msgstrs.each do |index, msgstr|
79
+ if index.nil? or index.is_a? Fixnum
80
+ Translation.create(:language_id => lang_id,
81
+ :msgid => msgid,
82
+ :msgid_plural => plural,
83
+ :msgstr => msgstr,
84
+ :msgstr_index => index) or raise
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ class Language < ::Sequel::Model(:merb_global_languages)
94
+ has_many :translations,
95
+ :class => "Merb::Global::MessageProviders::Sequel::Translation",
96
+ :key => :language_id
97
+ end
98
+
99
+ class Translation < ::Sequel::Model(:merb_global_translations)
100
+ set_primary_key :language_id, :msgid, :msgstr_index
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,101 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ module Merb
5
+ module Global
6
+ module MessageProviders
7
+ class Yaml #:nodoc:
8
+ include Merb::Global::MessageProviders::Base
9
+ include Merb::Global::MessageProviders::Base::Importer
10
+ include Merb::Global::MessageProviders::Base::Exporter
11
+
12
+ def initialize
13
+ # Not synchronized - make GC do it's work (may be not optimal
14
+ # but I don't think that some problem will occure).
15
+ # Shouldn't it be sort of cache with some expiration limit?
16
+ @lang = Hash.new
17
+ end
18
+
19
+ def localize(singular, plural, opts)
20
+ unless @lang.include? opts[:lang]
21
+ file = File.join Merb::Global::MessageProviders.localedir,
22
+ opts[:lang] + '.yaml'
23
+ if File.exist? file
24
+ @lang[opts[:lang]] = YAML.load_file file
25
+ else
26
+ @lang[opts[:lang]] = nil
27
+ end
28
+ end
29
+
30
+ unless @lang[opts[:lang]].nil?
31
+ lang = @lang[opts[:lang]]
32
+ unless lang[singular].nil?
33
+ unless plural.nil?
34
+ n = Merb::Global::Plural.which_form opts[:n], lang[:plural]
35
+ return lang[singular][n] unless lang[singular][n].nil?
36
+ else
37
+ return lang[singular] unless lang[singular].nil?
38
+ end
39
+ end
40
+ end
41
+ return opts[:n] > 1 ? plural : singular
42
+ end
43
+
44
+ def support?(lang)
45
+ unless @lang.include? lang
46
+ file = File.join Merb::Global::MessageProviders.localedir,
47
+ lang + '.yaml'
48
+ @lang[lang] = YAML.load_file file if File.exist? file
49
+ end
50
+ not @lang[lang].nil?
51
+ end
52
+
53
+ def create!
54
+ FileUtils.mkdir_p Merb::Global::MessageProviders.localedir
55
+ end
56
+
57
+ def choose(except)
58
+ dir = Dir[Merb::Global::MessageProviders.localedir + '/*.yaml']
59
+ dir.collect! {|p| File.basename p, '.yaml'}
60
+ dir.reject! {|lang| except.include? lang}
61
+ dir.first
62
+ end
63
+
64
+ def import
65
+ data = {}
66
+ Dir[Merb::Global::MessageProviders.localedir +
67
+ '/*.yaml'].each do |file|
68
+ lang_name = File.basename file, '.yaml'
69
+ data[lang_name] = lang = YAML.load_file(file)
70
+ lang.each do |msgid, msgstr|
71
+ if msgstr.is_a? String and msgid.is_a? String
72
+ lang[msgid] = {nil => msgstr, :plural => nil}
73
+ end
74
+ end
75
+ end
76
+ data
77
+ end
78
+
79
+ def export(data)
80
+ File.unlink *Dir[Merb::Global::MessageProviders.localedir +
81
+ '/*.yaml']
82
+ data.each do |lang_name, lang_orig|
83
+ lang = {}
84
+ lang_orig.each do |msgid, msgstr_hash|
85
+ lang[msgid] = {}
86
+ msgstr_hash.each do |msgstr_index, msgstr|
87
+ if msgstr_index.nil?
88
+ lang[msgid] = msgstr
89
+ else
90
+ lang[msgid][msgstr_index] = msgstr
91
+ end
92
+ end
93
+ end
94
+ YAML.dump File.join(Merb::Global::MessageProviders.localedir,
95
+ lang_name + '.yaml')
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,175 @@
1
+ require 'merb_global/providers'
2
+
3
+ module Merb
4
+ module Global
5
+ module MessageProviders
6
+ include Providers
7
+ # call-seq:
8
+ # localedir => localdir
9
+ #
10
+ # Returns the directory where locales are stored for file-backended
11
+ # providers (such as gettext or yaml)
12
+ #
13
+ # ==== Returns
14
+ # localedir<String>>:: Directory where the locales are stored
15
+ def self.localedir
16
+ localedir =
17
+ if Merb::Global.config :flat
18
+ 'locale'
19
+ else
20
+ Merb::Global.config :localedir, File.join('app', 'locale')
21
+ end
22
+ File.join Merb.root, localedir
23
+ end
24
+ # call-seq:
25
+ # provider => provider
26
+ #
27
+ # Returns the provider of required type
28
+ #
29
+ # ==== Returns
30
+ # provider<Provider>:: Returns provider
31
+ def self.provider
32
+ @@provider ||= self[Merb::Global.config(:message_provider, 'gettext')]
33
+ end
34
+ # Merb-global is able to store the translations in different types of
35
+ # storage. An interface betwean merb-global and those storages are
36
+ # providers.
37
+ #
38
+ # Please note that it is not required to include this module - despite it
39
+ # is recomended both as a documentation part and the more customized
40
+ # error messages.
41
+ module Base
42
+ # call-seq:
43
+ # localize(singular, plural, opts) => translated
44
+ #
45
+ # Translate string using specific provider.
46
+ # It should be overloaded by the implementator.
47
+ #
48
+ # Do not use this method directly - use Merb::Global._ instead
49
+ #
50
+ # ==== Parameters
51
+ # singular<String>:: A string to translate
52
+ # plural<String>:: A plural form of string (nil if only singular)
53
+ # opts<Hash>:: An options hash (see below)
54
+ #
55
+ # ==== Options (opts)
56
+ # :lang<String>:: A language to translate on
57
+ # :n<Fixnum>:: A number of objects
58
+ #
59
+ # ==== Returns
60
+ # translated<String>:: A translated string
61
+ #
62
+ # ==== Raises
63
+ # NoMethodError:: Raised by default implementation. Should not be thrown.
64
+ def localize(singular, plural, opts)
65
+ raise NoMethodError.new 'method localize has not been implemented'
66
+ end
67
+ # call-seq:
68
+ # support?(lang) => supported
69
+ #
70
+ # Checks if the language is supported (i.e. if the translation exists).
71
+ #
72
+ # In normal merb app the language is checked automatically in controller
73
+ # so probably you don't have to use this method
74
+ #
75
+ # ==== Parameters
76
+ # lang<String>:: A code of language
77
+ #
78
+ # ==== Returns
79
+ # supported<Boolean>:: Is a program translated to this language
80
+ #
81
+ # ==== Raises
82
+ # NoMethodError:: Raised by default implementation.
83
+ # Should not be thrown.
84
+ def support?(lang)
85
+ raise NoMethodError.new('method support? has not been implemented')
86
+ end
87
+ # This method creates basic files and/or directory structures
88
+ # (for example it adds migration) needed for provider to work.
89
+ #
90
+ # It is called from Rakefile.
91
+ def create!
92
+ raise NoMethodError.new('method create! has not been implemented')
93
+ end
94
+ # This method choos an supported language except those form the list
95
+ # given. It may fallback to english if none language can be found
96
+ # which agree with those criteria
97
+ def choose(except)
98
+ raise NoMethodError.new('method choose has not been implemented')
99
+ end
100
+ ##
101
+ # Transfer data from importer into exporter
102
+ #
103
+ # ==== Parameters
104
+ # importer<Importer>:: The provider providing the information
105
+ # exporter<Exporter>:: The provider receiving the information
106
+ def self.transfer(importer, exporter)
107
+ exporter.export importer.import
108
+ end
109
+ ##
110
+ # Importer is a provider through which one can iterate.
111
+ # Therefore it is possible to import data from this source
112
+ module Importer
113
+ ##
114
+ # This method import the data into a specified format from source.
115
+ # The format is nearly dump of the current YAML format.
116
+ #
117
+ # ==== Returns
118
+ # data<~each>:: Data in the specified format.
119
+ #
120
+ # ==== Raises
121
+ # NoMethodError:: Raised by default implementation.
122
+ # Should not be thrown.
123
+ def import # TODO: Describe the format
124
+ raise NoMethodError.new('method import has not been implemented')
125
+ end
126
+ end
127
+ ##
128
+ # Some sources are not only read-only but one can write to them.
129
+ # The provider is exporter if it handles this sort of source.
130
+ module Exporter
131
+ ##
132
+ # The method export the data from specified format into destination
133
+ # The format is nearly dump of the current YAML format.
134
+ #
135
+ # ==== Parameters
136
+ # data<~each>:: Data in the specified format.
137
+ #
138
+ # ==== Raises
139
+ # NoMethodError:: Raised by default implementation.
140
+ # Should not be thrown.
141
+ def export(data) # TODO: Describe the format
142
+ raise NoMethodError.new('method import has not been implemented')
143
+ end
144
+ end
145
+ end
146
+ end
147
+ # Perform the registration
148
+ #
149
+ # ==== Parameters
150
+ # provider_name<~to_sym>:: Name under which it is registred
151
+ # opts<Array[Symbol]>:: Additional imformations
152
+ #
153
+ # ==== Options
154
+ # importer:: Can perform import
155
+ # exporter:: Can perform export
156
+ def self.MessageProvider(provider_name, *opts)
157
+ Module.new do
158
+ @@mg_message_provider_name = provider_name
159
+
160
+ include Merb::Global::MessageProviders::Base
161
+ if opts.include? :importer
162
+ include Merb::Global::MessageProviders::Base::Importer
163
+ end
164
+ if opts.include? :exporter
165
+ include Merb::Global::MessageProviders::Base::Exporter
166
+ end
167
+
168
+ def self.included(klass)
169
+ Merb::Global::MessageProviders.register @@mg_message_provider_name,
170
+ klass
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,35 @@
1
+ require 'inline'
2
+
3
+ module Merb
4
+ module Global
5
+ module NumericProviders
6
+ class Fork
7
+ include Merb::Global::DateProviders::Base
8
+
9
+ def localize(lang, number)
10
+ pipe_rd, pipe_wr = IO.pipe
11
+ pid = fork do
12
+ pipe_rd.close
13
+ setlocale(lang)
14
+ pipe_wr.write(number)
15
+ pipe_wr.flush
16
+ end
17
+ pipe_wr.close
18
+ Process.wait(pid)
19
+ pipe_rd.read
20
+ end
21
+
22
+ inline do |builder|
23
+ builder.include '<locale.h>'
24
+ builder.c <<C
25
+ void set_locale(const char *locale)
26
+ {
27
+ setlocale(LC_ALL, locale);
28
+ }
29
+ C
30
+ end
31
+ private :set_locale
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ require 'merb_global/providers'
2
+
3
+ module Merb
4
+ module Global
5
+ module NumericProviders
6
+ include Providers
7
+ # call-seq:
8
+ # provider => provider
9
+ #
10
+ # Returns the provider of required type
11
+ #
12
+ # ==== Returns
13
+ # provider<Provider>:: Returns provider
14
+ def self.provider
15
+ @@provider ||= self[Merb::Global.config(:numeric_provider, 'fork')]
16
+ end
17
+ # Merb-global is able to handle localization in different ways.
18
+ # Providers are the interface.
19
+ #
20
+ # Please note that it is not required to include this module - despite it
21
+ # is recomended both as a documentation part and the more customized
22
+ # error messages.
23
+ module Base
24
+ ##
25
+ #
26
+ # Localize date using format as in strftime
27
+ def localize(lang, number)
28
+ raise NoMethodError.new('method localize has not been implemented')
29
+ end
30
+ end
31
+ end
32
+ # Perform the registration
33
+ #
34
+ # ==== Parameters
35
+ # name<~to_sym>:: Name under which it is registred
36
+ def self.NumericProvider(provider_name)
37
+ Module.new do
38
+ @@rb_numeric_provider_name = provider_name
39
+ include Merb::Global::NumericProviders::Base
40
+
41
+ def self.included(klass)
42
+ Merb::Global::NumericProviders.register @@rb_numeric_provider_name,
43
+ klass
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end