nelumba-i18n 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ostatus.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "rake" # rakefile
8
+ gem "minitest", "4.7.0" # test framework (specified here for prior rubies)
9
+ gem "ansi" # minitest colors
10
+ gem "turn" # minitest output
11
+ gem "mocha" # stubs
12
+
13
+ gem "awesome_print"
14
+ gem "debugger"
15
+ end
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lotus-i18n (0.0.1)
5
+ i18n
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ansi (1.4.3)
11
+ awesome_print (1.1.0)
12
+ columnize (0.3.6)
13
+ debugger (1.6.0)
14
+ columnize (>= 0.3.1)
15
+ debugger-linecache (~> 1.2.0)
16
+ debugger-ruby_core_source (~> 1.2.1)
17
+ debugger-linecache (1.2.0)
18
+ debugger-ruby_core_source (1.2.3)
19
+ diff-lcs (1.1.3)
20
+ i18n (0.6.4)
21
+ metaclass (0.0.1)
22
+ minitest (4.7.0)
23
+ mocha (0.14.0)
24
+ metaclass (~> 0.0.1)
25
+ rake (10.1.0)
26
+ rspec (2.10.0)
27
+ rspec-core (~> 2.10.0)
28
+ rspec-expectations (~> 2.10.0)
29
+ rspec-mocks (~> 2.10.0)
30
+ rspec-core (2.10.1)
31
+ rspec-expectations (2.10.0)
32
+ diff-lcs (~> 1.1.3)
33
+ rspec-mocks (2.10.1)
34
+ turn (0.9.6)
35
+ ansi
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ ansi
42
+ awesome_print
43
+ debugger
44
+ lotus-i18n!
45
+ minitest (= 4.7.0)
46
+ mocha
47
+ rake
48
+ rspec (~> 2.10.0)
49
+ turn
data/LICENSE ADDED
@@ -0,0 +1,127 @@
1
+ nelumba-i18n
2
+
3
+ To the extent possible under law, the author(s) have dedicated all
4
+ copyright and related and neighboring rights to this software to the
5
+ public domain worldwide. This software is distributed without any warranty.
6
+
7
+ Creative Commons Legal Code
8
+
9
+ CC0 1.0 Universal
10
+
11
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
12
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
13
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
14
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
15
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
16
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
17
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
18
+ HEREUNDER.
19
+
20
+ Statement of Purpose
21
+
22
+ The laws of most jurisdictions throughout the world automatically confer
23
+ exclusive Copyright and Related Rights (defined below) upon the creator
24
+ and subsequent owner(s) (each and all, an "owner") of an original work of
25
+ authorship and/or a database (each, a "Work").
26
+
27
+ Certain owners wish to permanently relinquish those rights to a Work for
28
+ the purpose of contributing to a commons of creative, cultural and
29
+ scientific works ("Commons") that the public can reliably and without fear
30
+ of later claims of infringement build upon, modify, incorporate in other
31
+ works, reuse and redistribute as freely as possible in any form whatsoever
32
+ and for any purposes, including without limitation commercial purposes.
33
+ These owners may contribute to the Commons to promote the ideal of a free
34
+ culture and the further production of creative, cultural and scientific
35
+ works, or to gain reputation or greater distribution for their Work in
36
+ part through the use and efforts of others.
37
+
38
+ For these and/or other purposes and motivations, and without any
39
+ expectation of additional consideration or compensation, the person
40
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
41
+ is an owner of Copyright and Related Rights in the Work, voluntarily
42
+ elects to apply CC0 to the Work and publicly distribute the Work under its
43
+ terms, with knowledge of his or her Copyright and Related Rights in the
44
+ Work and the meaning and intended legal effect of CC0 on those rights.
45
+
46
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
47
+ protected by copyright and related or neighboring rights ("Copyright and
48
+ Related Rights"). Copyright and Related Rights include, but are not
49
+ limited to, the following:
50
+
51
+ i. the right to reproduce, adapt, distribute, perform, display,
52
+ communicate, and translate a Work;
53
+ ii. moral rights retained by the original author(s) and/or performer(s);
54
+ iii. publicity and privacy rights pertaining to a person's image or
55
+ likeness depicted in a Work;
56
+ iv. rights protecting against unfair competition in regards to a Work,
57
+ subject to the limitations in paragraph 4(a), below;
58
+ v. rights protecting the extraction, dissemination, use and reuse of data
59
+ in a Work;
60
+ vi. database rights (such as those arising under Directive 96/9/EC of the
61
+ European Parliament and of the Council of 11 March 1996 on the legal
62
+ protection of databases, and under any national implementation
63
+ thereof, including any amended or successor version of such
64
+ directive); and
65
+ vii. other similar, equivalent or corresponding rights throughout the
66
+ world based on applicable law or treaty, and any national
67
+ implementations thereof.
68
+
69
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
70
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
71
+ irrevocably and unconditionally waives, abandons, and surrenders all of
72
+ Affirmer's Copyright and Related Rights and associated claims and causes
73
+ of action, whether now known or unknown (including existing as well as
74
+ future claims and causes of action), in the Work (i) in all territories
75
+ worldwide, (ii) for the maximum duration provided by applicable law or
76
+ treaty (including future time extensions), (iii) in any current or future
77
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
78
+ including without limitation commercial, advertising or promotional
79
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
80
+ member of the public at large and to the detriment of Affirmer's heirs and
81
+ successors, fully intending that such Waiver shall not be subject to
82
+ revocation, rescission, cancellation, termination, or any other legal or
83
+ equitable action to disrupt the quiet enjoyment of the Work by the public
84
+ as contemplated by Affirmer's express Statement of Purpose.
85
+
86
+ 3. Public License Fallback. Should any part of the Waiver for any reason
87
+ be judged legally invalid or ineffective under applicable law, then the
88
+ Waiver shall be preserved to the maximum extent permitted taking into
89
+ account Affirmer's express Statement of Purpose. In addition, to the
90
+ extent the Waiver is so judged Affirmer hereby grants to each affected
91
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
92
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
93
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
94
+ maximum duration provided by applicable law or treaty (including future
95
+ time extensions), (iii) in any current or future medium and for any number
96
+ of copies, and (iv) for any purpose whatsoever, including without
97
+ limitation commercial, advertising or promotional purposes (the
98
+ "License"). The License shall be deemed effective as of the date CC0 was
99
+ applied by Affirmer to the Work. Should any part of the License for any
100
+ reason be judged legally invalid or ineffective under applicable law, such
101
+ partial invalidity or ineffectiveness shall not invalidate the remainder
102
+ of the License, and in such case Affirmer hereby affirms that he or she
103
+ will not (i) exercise any of his or her remaining Copyright and Related
104
+ Rights in the Work or (ii) assert any associated claims and causes of
105
+ action with respect to the Work, in either case contrary to Affirmer's
106
+ express Statement of Purpose.
107
+
108
+ 4. Limitations and Disclaimers.
109
+
110
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
111
+ surrendered, licensed or otherwise affected by this document.
112
+ b. Affirmer offers the Work as-is and makes no representations or
113
+ warranties of any kind concerning the Work, express, implied,
114
+ statutory or otherwise, including without limitation warranties of
115
+ title, merchantability, fitness for a particular purpose, non
116
+ infringement, or the absence of latent or other defects, accuracy, or
117
+ the present or absence of errors, whether or not discoverable, all to
118
+ the greatest extent permissible under applicable law.
119
+ c. Affirmer disclaims responsibility for clearing rights of other persons
120
+ that may apply to the Work or any use thereof, including without
121
+ limitation any person's Copyright and Related Rights in the Work.
122
+ Further, Affirmer disclaims responsibility for obtaining any necessary
123
+ consents, permissions or other rights required for any use of the
124
+ Work.
125
+ d. Affirmer understands and acknowledges that Creative Commons is not a
126
+ party to this document and has no duty or obligation with respect to
127
+ this CC0 or use of the Work.
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = "spec/**/*_spec.rb"
7
+ end
@@ -0,0 +1,11 @@
1
+ # The main module for all Nelumba related functionality.
2
+ module Nelumba
3
+ end
4
+
5
+ require 'nelumba-i18n/i18n'
6
+ require 'nelumba-i18n/locales'
7
+
8
+ # Add nelumba locales to the I18n load path
9
+ I18n.load_path += Dir[File.expand_path("../nelumba-i18n/locales/**/lexicon.{yml}", __FILE__)]
10
+
11
+ Dir[File.join(File.dirname(__FILE__), "nelumba-i18n", "locales", "*.rb")].each {|file| require file }
@@ -0,0 +1,166 @@
1
+ # The main module for all Nelumba related functionality.
2
+ module Nelumba
3
+ # The internationalization and localization module.
4
+ module I18n
5
+ require 'i18n'
6
+
7
+ # Translate the given token to the locale's translation.
8
+ def self.translate(*args)
9
+ ::I18n.t(*args)
10
+ end
11
+
12
+ # Translates the given verb for the current locale.
13
+ def self.verb(*args)
14
+ token = args.shift
15
+ self.translate("verb.#{token}", *args)
16
+ end
17
+
18
+ # Translates the given object as a singular for the current locale.
19
+ def self.single_object(*args)
20
+ token = args.shift
21
+ options = args.last if args.last.is_a? Hash
22
+ self.translate("object.singular_#{token}", *args)
23
+ end
24
+
25
+ # Translates the given object as a plural for the current locale.
26
+ def self.plural_object(*args)
27
+ token = args.shift
28
+ options = args.last if args.last.is_a? Hash
29
+ self.translate("object.plural_#{token}", *args)
30
+ end
31
+
32
+ # Translates the given object for the current locale.
33
+ def self.object(*args)
34
+ token = args.shift
35
+ self.translate("object.#{token}", *args)
36
+ end
37
+
38
+ # Translates the given pronoun for the current locale.
39
+ def self.pronoun(*args)
40
+ token = args.shift
41
+ self.translate("pronoun.#{token}", *args)
42
+ end
43
+
44
+ # Translates the given possessive pronoun for the current locale.
45
+ def self.possessive_pronoun(*args)
46
+ token = args.shift
47
+ self.translate("possessive_pronoun.#{token}", *args)
48
+ end
49
+
50
+ # Translates the given action for the current locale.
51
+ def self.action(*args)
52
+ token = args.shift
53
+ self.translate("action.#{token}", *args)
54
+ end
55
+
56
+ # Translates the given category for the current locale.
57
+ def self.category(*args)
58
+ token = args.shift
59
+ self.translate("category.#{token}", *args)
60
+ end
61
+
62
+ # Translates the given field for the current locale.
63
+ def self.field(*args)
64
+ token = args.shift
65
+ self.translate("field.#{token}", *args)
66
+ end
67
+
68
+ # Localize the given object for the current locale.
69
+ def self.localize(*args)
70
+ ::I18n.l(*args)
71
+ end
72
+
73
+ # Produces a sentence describing the given Nelumba::Activity.
74
+ def self.sentence(options = {})
75
+ options ||= {}
76
+
77
+ components = {}
78
+
79
+ if options[:actor].is_a? Symbol
80
+ components[:actor_pronoun] = options[:actor]
81
+ else
82
+ components[:actor] = options[:actor]
83
+ end
84
+
85
+ if options[:actors].is_a? Symbol
86
+ components[:actors_pronoun] = options[:actors]
87
+ else
88
+ components[:actors] = options[:actors]
89
+ end
90
+
91
+ if options[:object_owner].is_a? Symbol
92
+ components[:object_owner_pronoun] = options[:object_owner]
93
+ else
94
+ components[:object_owner] = options[:object_owner]
95
+ end
96
+
97
+ if options[:object_owners].is_a? Symbol
98
+ components[:object_owners_pronoun] = options[:object_owners]
99
+ else
100
+ components[:object_owners] = options[:object_owners]
101
+ end
102
+
103
+ if options[:target_owner].is_a? Symbol
104
+ components[:target_owner_pronoun] = options[:target_owner]
105
+ else
106
+ components[:target_owner] = options[:target_owner]
107
+ end
108
+
109
+ if options[:target_owners].is_a? Symbol
110
+ components[:target_owners_pronoun] = options[:target_owners]
111
+ else
112
+ components[:target_owners] = options[:target_owners]
113
+ end
114
+
115
+ components[:target] = options[:target]
116
+ components[:targets] = options[:targets]
117
+ components[:object] = options[:object]
118
+ components[:objects] = options[:objects]
119
+ components[:verb] = options[:verb]
120
+ components[:action] = options[:action]
121
+ components[:field] = options[:field]
122
+
123
+ components.keys.each do |key|
124
+ if components[key].nil?
125
+ components.delete key
126
+ end
127
+ end
128
+
129
+ rules = Nelumba::Locales.rules(options)
130
+ result = ""
131
+
132
+ rules.each do |hash|
133
+ if components.keys.select{|e| !hash["for"].include?(e.to_s)}.empty? &&
134
+ components.keys.count == hash["for"].count
135
+ if hash["match"] && hash["match"].count > 0
136
+ matches = hash["match"]
137
+
138
+ violations = matches.select do |rule|
139
+ if rule[0] && components[rule[0].intern]
140
+ !components[rule[0].intern].match(Regexp.new(rule[1]))
141
+ else
142
+ false
143
+ end
144
+ end
145
+
146
+ next unless violations.empty?
147
+ end
148
+
149
+ result = hash["do"]
150
+ components.each do |component, value|
151
+ if value.is_a? Symbol
152
+ result = result.gsub /[\.a-zA-Z_]*%#{Regexp.escape(component.to_s)}%[\.a-zA-Z_]*/ do |match|
153
+ self.translate(match.gsub("%#{component.to_s}%", value.to_s).strip, options)
154
+ end
155
+ else
156
+ result = result.gsub("%#{component.to_s}%", value.to_s)
157
+ end
158
+ end
159
+ break
160
+ end
161
+ end
162
+
163
+ result
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,122 @@
1
+ module Nelumba
2
+ # Modules that will generate sentences dependent on the locale.
3
+ module Locales
4
+ # Returns the metadata used to build grammatical structures for the given
5
+ # locale.
6
+ #
7
+ # options:
8
+ # :locale => The locale to use. Defaults to :en (English).
9
+ #
10
+ # Returns: an array of grammar rules. Each rule is a hash described below:
11
+ # "for" => gives an array of strings for components that must be available
12
+ # in order to generate this sentence.
13
+ # "do" => gives the string that serves as the grammatical structure.
14
+ # "match" => an array of patterns to match against component values. if
15
+ # the component does not match, then this rule is not used.
16
+ # For instance, a rule that looks for an 's' at the end of
17
+ # a word so as to decide between a rule that add "'s" or "'"
18
+ # to the end of a word to make it possessive. You would have
19
+ # a rule that matches that component against "s$". The patterns
20
+ # are regular expression strings.
21
+ def self.grammar(options = {})
22
+ filename = self.grammar_path(options)
23
+
24
+ @@grammar ||= {}
25
+ @@grammar[filename] ||= YAML.load_file(filename)[self.locale(options)]
26
+ @@grammar[filename]
27
+ end
28
+
29
+ # Returns the default locale for the system.
30
+ def self.default_locale
31
+ default = ::I18n.default_locale.to_s
32
+ end
33
+
34
+ # Returns the locale passed through options if that locale exists, otherwise
35
+ # it returns the default locale.
36
+ def self.locale(options = {})
37
+ options ||= {}
38
+
39
+ locale = options[:locale].to_s || ::I18n.locale.to_s
40
+
41
+ default = ::I18n.default_locale.to_s
42
+
43
+ # Use the system default locale and then English as fallback
44
+ filename = ::File.join(::File.dirname(__FILE__), 'locales', locale, 'grammar.yml')
45
+ unless ::File.exist? filename
46
+ locale = default
47
+ filename = ::File.join(::File.dirname(__FILE__), 'locales', locale, 'grammar.yml')
48
+ unless ::File.exist? filename
49
+ locale = "en"
50
+ end
51
+ end
52
+
53
+ locale
54
+ end
55
+
56
+ # Returns the path to the grammar descriptions for the given locale or the
57
+ # default locale if none is given or the given locale cannot be found.
58
+ def self.grammar_path(options = {})
59
+ ::File.join(::File.dirname(__FILE__), 'locales', self.locale(options), 'grammar.yml')
60
+ end
61
+
62
+ # Returns the full list of rules for the given locale.
63
+ def self.rules(options = {})
64
+ locale = self.locale(options)
65
+
66
+ @@rules ||= {}
67
+ return @@rules[locale] if @@rules[locale]
68
+
69
+ grammar = self.grammar(options)
70
+
71
+ rules = []
72
+ grammar["rules"].each do |rule|
73
+ if rule["match"] && rule["match"].count > 0 &&
74
+ !rule["match"][0].is_a?(Array)
75
+ rule["match"] = [rule["match"]]
76
+ end
77
+
78
+ if rule["do"].match /\$([^\$]+)\$/
79
+ patterns = rule["do"].split('$')
80
+ patterns << "" if patterns.length.even?
81
+ number_of_subrules = (patterns.length - 1)/2
82
+ subrules = patterns.select.each_with_index { |_,i| i.odd? }
83
+
84
+ subrules.map! do |subrule|
85
+ grammar["subrules"][subrule]
86
+ end
87
+
88
+ subrules = subrules[0].product(*subrules[1..-1])
89
+
90
+ subrules.each do |set|
91
+ new_rule = {}
92
+ new_rule["for"] = set.map{|subrule|(subrule["for"] || []).dup}.concat(rule["for"]||[]).flatten.uniq
93
+
94
+ new_rule["match"] = set.map do |subrule|
95
+ if subrule["match"] && subrule["match"].count > 0 &&
96
+ !subrule["match"][0].is_a?(Array)
97
+ subrule["match"] = [subrule["match"]]
98
+ end
99
+ (subrule["match"] || [[]]).dup
100
+ end.inject(&:concat).concat(rule["match"]||[[]]).uniq.select{|e|!e.empty?}
101
+
102
+ new_rule["do"] = ""
103
+ patterns.each_with_index do |pattern, i|
104
+ if i.even?
105
+ new_rule["do"] << pattern
106
+ else
107
+ new_rule["do"] << set[(i-1)/2]["do"]
108
+ end
109
+ end
110
+ rules << new_rule
111
+ end
112
+ else
113
+ rule["for"] ||= []
114
+ rule["match"] ||= [[]]
115
+ rules << rule
116
+ end
117
+ end
118
+
119
+ @@rules[locale] = rules
120
+ end
121
+ end
122
+ end