merb_global 0.0.4.2 → 0.0.5

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.
@@ -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