tr8n_core 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +69 -0
- data/Rakefile +9 -0
- data/config/config.yml +34 -0
- data/config/tokens/data.yml +45 -0
- data/config/tokens/decorations.yml +37 -0
- data/lib/tr8n/application.rb +320 -0
- data/lib/tr8n/base.rb +123 -0
- data/lib/tr8n/cache.rb +144 -0
- data/lib/tr8n/cache_adapters/cdb.rb +74 -0
- data/lib/tr8n/cache_adapters/file.rb +70 -0
- data/lib/tr8n/cache_adapters/memcache.rb +91 -0
- data/lib/tr8n/cache_adapters/redis.rb +94 -0
- data/lib/tr8n/component.rb +68 -0
- data/lib/tr8n/config.rb +291 -0
- data/lib/tr8n/decorators/base.rb +35 -0
- data/lib/tr8n/decorators/default.rb +30 -0
- data/lib/tr8n/decorators/html.rb +63 -0
- data/lib/tr8n/exception.rb +26 -0
- data/lib/tr8n/language.rb +250 -0
- data/lib/tr8n/language_case.rb +116 -0
- data/lib/tr8n/language_case_rule.rb +85 -0
- data/lib/tr8n/language_context.rb +115 -0
- data/lib/tr8n/language_context_rule.rb +62 -0
- data/lib/tr8n/logger.rb +83 -0
- data/lib/tr8n/rules_engine/evaluator.rb +156 -0
- data/lib/tr8n/rules_engine/parser.rb +83 -0
- data/lib/tr8n/source.rb +95 -0
- data/lib/tr8n/tokens/data.rb +410 -0
- data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
- data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
- data/lib/tr8n/tokens/hidden.rb +48 -0
- data/lib/tr8n/tokens/method.rb +52 -0
- data/lib/tr8n/tokens/transform.rb +191 -0
- data/lib/tr8n/translation.rb +104 -0
- data/lib/tr8n/translation_key.rb +205 -0
- data/lib/tr8n/translator.rb +62 -0
- data/lib/tr8n/utils.rb +124 -0
- data/lib/tr8n_core/ext/array.rb +74 -0
- data/lib/tr8n_core/ext/date.rb +63 -0
- data/lib/tr8n_core/ext/fixnum.rb +39 -0
- data/lib/tr8n_core/ext/hash.rb +126 -0
- data/lib/tr8n_core/ext/string.rb +44 -0
- data/lib/tr8n_core/ext/time.rb +71 -0
- data/lib/tr8n_core/generators/cache/base.rb +85 -0
- data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
- data/lib/tr8n_core/generators/cache/file.rb +69 -0
- data/lib/tr8n_core/modules/logger.rb +43 -0
- data/lib/tr8n_core/version.rb +27 -0
- data/lib/tr8n_core.rb +68 -0
- data/spec/application_spec.rb +228 -0
- data/spec/base_spec.rb +19 -0
- data/spec/config_spec.rb +16 -0
- data/spec/decorator_spec.rb +10 -0
- data/spec/decorators/base_spec.rb +14 -0
- data/spec/decorators/default_spec.rb +12 -0
- data/spec/decorators/html_spec.rb +50 -0
- data/spec/fixtures/application.json +112 -0
- data/spec/fixtures/languages/en-US.json +1424 -0
- data/spec/fixtures/languages/es.json +291 -0
- data/spec/fixtures/languages/ru.json +550 -0
- data/spec/fixtures/translations/ru/basic.json +56 -0
- data/spec/fixtures/translations/ru/counters.json +43 -0
- data/spec/fixtures/translations/ru/genders.json +171 -0
- data/spec/fixtures/translations/ru/last_names.txt +200 -0
- data/spec/fixtures/translations/ru/names.json +1 -0
- data/spec/fixtures/translations/ru/names.txt +458 -0
- data/spec/helper.rb +84 -0
- data/spec/language_case_rule_spec.rb +57 -0
- data/spec/language_case_spec.rb +58 -0
- data/spec/language_context_rule_spec.rb +73 -0
- data/spec/language_context_spec.rb +331 -0
- data/spec/language_spec.rb +16 -0
- data/spec/rules_engine/evaluator_spec.rb +148 -0
- data/spec/rules_engine/parser_spec.rb +29 -0
- data/spec/tokens/data_spec.rb +160 -0
- data/spec/tokens/data_tokenizer_spec.rb +29 -0
- data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
- data/spec/tokens/hidden_spec.rb +24 -0
- data/spec/tokens/method_spec.rb +84 -0
- data/spec/tokens/transform_spec.rb +50 -0
- data/spec/translation_key_spec.rb +96 -0
- data/spec/translation_spec.rb +24 -0
- data/spec/utils_spec.rb +64 -0
- metadata +176 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'digest/md5'
|
25
|
+
|
26
|
+
class Tr8n::TranslationKey < Tr8n::Base
|
27
|
+
belongs_to :application, :language
|
28
|
+
attributes :id, :key, :label, :description, :locale, :level, :locked
|
29
|
+
has_many :translations # hashed by language
|
30
|
+
|
31
|
+
def initialize(attrs = {})
|
32
|
+
super
|
33
|
+
|
34
|
+
self.attributes[:key] ||= self.class.generate_key(label, description)
|
35
|
+
self.attributes[:locale] ||= Tr8n.config.block_options[:locale] || application.default_locale
|
36
|
+
self.attributes[:language] ||= application.language(locale)
|
37
|
+
self.attributes[:translations] = {}
|
38
|
+
|
39
|
+
if hash_value(attrs, :translations)
|
40
|
+
hash_value(attrs, :translations).each do |locale, translations|
|
41
|
+
language = application.language(locale)
|
42
|
+
|
43
|
+
self.attributes[:translations][locale] ||= []
|
44
|
+
|
45
|
+
translations.each do |translation_hash|
|
46
|
+
translation = Tr8n::Translation.new(translation_hash.merge(:translation_key => self, :locale => language.locale))
|
47
|
+
self.attributes[:translations][locale] << translation
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.generate_key(label, desc = "")
|
54
|
+
"#{Digest::MD5.hexdigest("#{label};;;#{desc}")}~"[0..-2].to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_translations_for_language?(language)
|
58
|
+
translations and translations[language.locale] and translations[language.locale].any?
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_translations(language, options = {})
|
62
|
+
return self if self.id and has_translations_for_language?(language)
|
63
|
+
|
64
|
+
if options[:dry] or Tr8n.config.block_options[:dry]
|
65
|
+
return application.cache_translation_key(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
tkey = application.post("translation_key/translations",
|
69
|
+
{:key => key, :label => label, :description => description, :locale => language.locale},
|
70
|
+
{:class => Tr8n::TranslationKey, :attributes => {:application => application}})
|
71
|
+
|
72
|
+
application.cache_translation_key(tkey)
|
73
|
+
end
|
74
|
+
|
75
|
+
# switches to a new application
|
76
|
+
def set_application(app)
|
77
|
+
self.application = app
|
78
|
+
translations.values.each do |locale_translations|
|
79
|
+
locale_translations.each do |t|
|
80
|
+
t.set_translation_key(self)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_language_translations(language, translations)
|
87
|
+
translations.each do |translation|
|
88
|
+
translation.locale = language.locale
|
89
|
+
translation.set_translation_key(self)
|
90
|
+
end
|
91
|
+
self.translations[language.locale] = translations
|
92
|
+
end
|
93
|
+
|
94
|
+
###############################################################
|
95
|
+
## Translation Methods
|
96
|
+
###############################################################
|
97
|
+
|
98
|
+
def translations_for_language(language)
|
99
|
+
return [] unless self.translations
|
100
|
+
self.translations[language.locale] || []
|
101
|
+
end
|
102
|
+
|
103
|
+
def find_first_valid_translation(language, token_values)
|
104
|
+
translations = translations_for_language(language)
|
105
|
+
|
106
|
+
translations.sort! { |x,y| x.precedence <=> y.precedence }
|
107
|
+
|
108
|
+
translations.each do |translation|
|
109
|
+
return translation if translation.matches_rules?(token_values)
|
110
|
+
end
|
111
|
+
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def translate(language, token_values = {}, options = {})
|
116
|
+
if Tr8n.config.disabled? or language.locale == self.attributes[:locale]
|
117
|
+
return substitute_tokens(label, token_values, language, options.merge(:fallback => false))
|
118
|
+
end
|
119
|
+
|
120
|
+
translation = find_first_valid_translation(language, token_values)
|
121
|
+
decorator = Tr8n::Decorators::Base.decorator
|
122
|
+
|
123
|
+
if translation
|
124
|
+
translated_label = substitute_tokens(translation.label, token_values, translation.language, options)
|
125
|
+
return decorator.decorate(translated_label, translation.language, language, self, options)
|
126
|
+
end
|
127
|
+
|
128
|
+
#Tr8n.logger.info("Translating: #{label}")
|
129
|
+
|
130
|
+
translated_label = substitute_tokens(label, token_values, self.attributes[:language], options)
|
131
|
+
decorator.decorate(translated_label, self.attributes[:language], language, self, options)
|
132
|
+
end
|
133
|
+
|
134
|
+
###############################################################
|
135
|
+
## Token Substitution Methods
|
136
|
+
###############################################################
|
137
|
+
|
138
|
+
# Returns an array of decoration tokens from the translation key
|
139
|
+
def decoration_tokens
|
140
|
+
@decoration_tokens ||= begin
|
141
|
+
dt = Tr8n::Tokens::DecorationTokenizer.new(label)
|
142
|
+
dt.parse
|
143
|
+
dt.tokens
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns an array of data tokens from the translation key
|
148
|
+
def data_tokens
|
149
|
+
@data_tokens ||= begin
|
150
|
+
dt = Tr8n::Tokens::DataTokenizer.new(label)
|
151
|
+
dt.tokens
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def data_tokens_names_map
|
156
|
+
@data_tokens_names_map ||= begin
|
157
|
+
map = {}
|
158
|
+
data_tokens.each do |token|
|
159
|
+
map[token.name] = token
|
160
|
+
end
|
161
|
+
map
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# if the translations engine is disabled
|
166
|
+
def self.substitute_tokens(label, token_values, language, options = {})
|
167
|
+
return label.to_s if options[:skip_substitution]
|
168
|
+
Tr8n::TranslationKey.new(:label => label.to_s).substitute_tokens(label.to_s, token_values, language, options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def substitute_tokens(translated_label, token_values, language, options = {})
|
172
|
+
if translated_label.index('{')
|
173
|
+
dt = Tr8n::Tokens::DataTokenizer.new(translated_label, token_values, :allowed_tokens => data_tokens_names_map)
|
174
|
+
translated_label = dt.substitute(language, options)
|
175
|
+
end
|
176
|
+
|
177
|
+
return translated_label unless translated_label.index('[')
|
178
|
+
dt = Tr8n::Tokens::DecorationTokenizer.new(translated_label, token_values, :allowed_tokens => decoration_tokens)
|
179
|
+
dt.substitute
|
180
|
+
end
|
181
|
+
|
182
|
+
#######################################################################################################
|
183
|
+
## Cache Methods
|
184
|
+
#######################################################################################################
|
185
|
+
|
186
|
+
def self.cache_prefix
|
187
|
+
't@'
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.cache_key(label, description, locale)
|
191
|
+
"#{cache_prefix}_[#{locale}]_[#{generate_key(label, description)}]";
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_cache_hash
|
195
|
+
hash = to_hash(:id, :key, :label, :description, :locale, :level)
|
196
|
+
if translations and translations.any?
|
197
|
+
hash["translations"] = {}
|
198
|
+
translations.each do |locale, locale_translations|
|
199
|
+
hash["translations"][locale] = locale_translations.collect{|t| t.to_cache_hash}
|
200
|
+
end
|
201
|
+
end
|
202
|
+
hash
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'digest/md5'
|
25
|
+
|
26
|
+
class Tr8n::Translator < Tr8n::Base
|
27
|
+
belongs_to :application
|
28
|
+
attributes :id, :name, :email, :gender, :mugshot, :link, :inline, :features
|
29
|
+
attributes :voting_power, :rank, :level, :locale, :manager, :code, :access_token
|
30
|
+
|
31
|
+
def self.authorize(application, username, password, options = {})
|
32
|
+
data = application.get('oauth/request_token', {:grant_type => :password, :username => username, :password => password})
|
33
|
+
init(application, data['access_token'])
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.init(application, access_token)
|
37
|
+
application.get('translator', {:access_token => access_token}, {:class => Tr8n::Translator, :attributes => {
|
38
|
+
:application => application,
|
39
|
+
:access_token => access_token
|
40
|
+
}})
|
41
|
+
end
|
42
|
+
|
43
|
+
def applications
|
44
|
+
application.get("translator/applications", {:access_token => access_token}, {:class => Tr8n::Application})
|
45
|
+
end
|
46
|
+
|
47
|
+
def translations
|
48
|
+
application.get("translator/translations", {:access_token => access_token}, {:class => Tr8n::Application})
|
49
|
+
end
|
50
|
+
|
51
|
+
def feature_enabled?(key)
|
52
|
+
return false unless features
|
53
|
+
hash_value(features, key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def mugshot_url
|
57
|
+
return nil unless email
|
58
|
+
gravatar_id = Digest::MD5.hexdigest(email.downcase)
|
59
|
+
"http://gravatar.com/avatar/#{gravatar_id}.png?s=48"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/tr8n/utils.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'yaml'
|
25
|
+
require 'base64'
|
26
|
+
require 'openssl'
|
27
|
+
require 'json'
|
28
|
+
require 'uri'
|
29
|
+
|
30
|
+
module Tr8n
|
31
|
+
class Utils
|
32
|
+
def self.normalize_tr_params(label, description, tokens, options)
|
33
|
+
return label if label.is_a?(Hash)
|
34
|
+
|
35
|
+
if description.is_a?(Hash)
|
36
|
+
return {
|
37
|
+
:label => label,
|
38
|
+
:description => nil,
|
39
|
+
:tokens => description,
|
40
|
+
:options => tokens
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
{
|
45
|
+
:label => label,
|
46
|
+
:description => description,
|
47
|
+
:tokens => tokens,
|
48
|
+
:options => options
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.guid
|
53
|
+
(0..16).to_a.map{|a| rand(16).to_s(16)}.join
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.split_by_sentence(text)
|
57
|
+
sentence_regex = /[^.!?\s][^.!?]*(?:[.!?](?![\'"]?\s|$)[^.!?]*)*[.!?]?[\'"]?(?=\s|$)/
|
58
|
+
|
59
|
+
sentences = []
|
60
|
+
text.scan(sentence_regex).each do |s|
|
61
|
+
sentences << s
|
62
|
+
end
|
63
|
+
|
64
|
+
sentences
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.load_json(file_path, env = nil)
|
68
|
+
json = JSON.parse(File.read(file_path))
|
69
|
+
return json if env.nil?
|
70
|
+
return yml['defaults'] if env == 'defaults'
|
71
|
+
yml['defaults'].rmerge(yml[env] || {})
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.load_yaml(file_path, env = nil)
|
75
|
+
yaml = YAML.load_file(file_path)
|
76
|
+
return yaml if env.nil?
|
77
|
+
return yaml['defaults'] if env == 'defaults'
|
78
|
+
yaml['defaults'].rmerge(yaml[env] || {})
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.sign_and_encode_params(params, secret)
|
82
|
+
payload = params.merge(:algorithm => 'HMAC-SHA256', :ts => Time.now.to_i).to_json
|
83
|
+
payload = Base64.encode64(payload)
|
84
|
+
|
85
|
+
sig = OpenSSL::HMAC.digest('sha256', secret, payload)
|
86
|
+
encoded_sig = Base64.encode64(sig)
|
87
|
+
|
88
|
+
URI::encode(Base64.encode64("#{encoded_sig}.#{payload}"))
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.decode_and_verify_params(signed_request, secret)
|
92
|
+
signed_request = URI::decode(signed_request)
|
93
|
+
signed_request = Base64.decode64(signed_request)
|
94
|
+
|
95
|
+
encoded_sig, payload = signed_request.split('.', 2)
|
96
|
+
expected_sig = OpenSSL::HMAC.digest('sha256', secret, payload)
|
97
|
+
expected_sig = Base64.encode64(expected_sig)
|
98
|
+
if expected_sig != encoded_sig
|
99
|
+
raise Tr8n::Exception.new("Bad signature")
|
100
|
+
end
|
101
|
+
|
102
|
+
JSON.parse(Base64.decode64(payload))
|
103
|
+
end
|
104
|
+
|
105
|
+
######################################################################
|
106
|
+
# Author: Iain Hecker
|
107
|
+
# reference: http://github.com/iain/http_accept_language
|
108
|
+
######################################################################
|
109
|
+
def self.browser_accepted_locales(request)
|
110
|
+
request.env['HTTP_ACCEPT_LANGUAGE'].split(/\s*,\s*/).collect do |l|
|
111
|
+
l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
|
112
|
+
l.split(';q=')
|
113
|
+
end.sort do |x,y|
|
114
|
+
raise Tr8n::Exception.new("Not correctly formatted") unless x.first =~ /^[a-z\-]+$/i
|
115
|
+
y.last.to_f <=> x.last.to_f
|
116
|
+
end.collect do |l|
|
117
|
+
l.first.downcase.gsub(/-[a-z]+$/i) { |x| x.upcase }
|
118
|
+
end
|
119
|
+
rescue
|
120
|
+
[]
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Array
|
25
|
+
|
26
|
+
# translates an array of options for a select tag
|
27
|
+
def tro(description = "", options = {}, language = Tr8n.config.current_language)
|
28
|
+
return [] if empty?
|
29
|
+
|
30
|
+
collect do |opt|
|
31
|
+
if opt.is_a?(Array) and opt.first.is_a?(String)
|
32
|
+
[opt.first.trl(description, {}, options, language), opt.last]
|
33
|
+
elsif opt.is_a?(String)
|
34
|
+
[opt.trl(description, {}, options, language), opt]
|
35
|
+
else
|
36
|
+
opt
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# translate array values
|
42
|
+
def trl(description = "", options = {}, language = Tr8n.config.current_language)
|
43
|
+
return [] if empty?
|
44
|
+
|
45
|
+
collect do |opt|
|
46
|
+
if opt.is_a?(String)
|
47
|
+
opt.trl(description, {}, options, language)
|
48
|
+
else
|
49
|
+
opt
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# creates a sentence with tr "and" joiner
|
55
|
+
def tr_sentence(options = {}, language = Tr8n.config.current_language)
|
56
|
+
return "" if empty?
|
57
|
+
return first if size == 1
|
58
|
+
|
59
|
+
result = "#{self[0..-2].join(", ")}"
|
60
|
+
result << " " << "and".translate("List elements joiner", {}, options, language) << " "
|
61
|
+
result << self.last
|
62
|
+
end
|
63
|
+
|
64
|
+
def tr8n_translated
|
65
|
+
return self if frozen?
|
66
|
+
@tr8n_translated = true
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def tr8n_translated?
|
71
|
+
@tr8n_translated
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Date
|
25
|
+
|
26
|
+
def translate(format = :default, language = Tr8n.config.current_language, options = {})
|
27
|
+
label = (format.is_a?(String) ? format.clone : Tr8n.config.default_date_formats[format].clone)
|
28
|
+
symbols = label.scan(/(%\w)/).flatten.uniq
|
29
|
+
|
30
|
+
selected_tokens = []
|
31
|
+
symbols.each do |symbol|
|
32
|
+
token = Tr8n.config.strftime_symbol_to_token(symbol)
|
33
|
+
next unless token
|
34
|
+
selected_tokens << token
|
35
|
+
label.gsub!(symbol, token)
|
36
|
+
end
|
37
|
+
|
38
|
+
tokens = {}
|
39
|
+
selected_tokens.each do |token|
|
40
|
+
case token
|
41
|
+
when "{days}" then tokens[:days] = options[:with_leading_zero] ? day.with_leading_zero : day.to_s
|
42
|
+
when "{year_days}" then tokens[:year_days] = options[:with_leading_zero] ? yday.with_leading_zero : yday.to_s
|
43
|
+
when "{months}" then tokens[:months] = options[:with_leading_zero] ? month.with_leading_zero : month.to_s
|
44
|
+
when "{week_num}" then tokens[:week_num] = wday
|
45
|
+
when "{week_days}" then tokens[:week_days] = strftime("%w")
|
46
|
+
when "{short_years}" then tokens[:short_years] = strftime("%y")
|
47
|
+
when "{years}" then tokens[:years] = year
|
48
|
+
when "{short_week_day_name}" then tokens[:short_week_day_name] = language.tr(Tr8n.config.default_abbr_day_names[wday], "Short name for a day of a week", {}, options)
|
49
|
+
when "{week_day_name}" then tokens[:week_day_name] = language.tr(Tr8n.config.default_day_names[wday], "Day of a week", {}, options)
|
50
|
+
when "{short_month_name}" then tokens[:short_month_name] = language.tr(Tr8n.config.default_abbr_month_names[month - 1], "Short month name", {}, options)
|
51
|
+
when "{month_name}" then tokens[:month_name] = language.tr(Tr8n.config.default_month_names[month - 1], "Month name", {}, options)
|
52
|
+
when "{day_of_month}" then tokens[:day_of_month] = strftime("%e")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
language.tr(label, nil, tokens, options)
|
57
|
+
end
|
58
|
+
alias :tr :translate
|
59
|
+
|
60
|
+
def trl(format = :default, language = Tr8n.config.current_language, options = {})
|
61
|
+
tr(format, language, options.merge!(:skip_decorations => true))
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Fixnum
|
25
|
+
|
26
|
+
def with_leading_zero
|
27
|
+
(to_i < 10 ? "0#{to_s}" : to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def translate(desc = "", tokens = {}, options = {}, language = Tr8n.config.current_language)
|
31
|
+
to_s.translate(desc, tokens, options, language)
|
32
|
+
end
|
33
|
+
alias tr translate
|
34
|
+
|
35
|
+
def trl(desc = "", tokens = {}, options = {}, language = Tr8n.config.current_language)
|
36
|
+
to_s.trl(desc, tokens, options, language)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 Michael Berkovich, tr8nhub.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
class Hash
|
25
|
+
|
26
|
+
# Return all combinations of a hash.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
# {
|
30
|
+
# :a => [1, 2]
|
31
|
+
# :b => [1, 2]
|
32
|
+
# }.combinations #=> [{:a=>1, :b=>1}, {:a=>1, :b=>2}, {:a=>2, :b=>1}, {:a=>2, :b=>2}]
|
33
|
+
#
|
34
|
+
def combinations
|
35
|
+
return [{}] if empty?
|
36
|
+
|
37
|
+
copy = dup
|
38
|
+
values = copy.delete(key = keys.first)
|
39
|
+
|
40
|
+
result = []
|
41
|
+
copy.combinations.each do |tail|
|
42
|
+
values.each do |value|
|
43
|
+
result << tail.merge(key=>value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
result
|
48
|
+
end
|
49
|
+
|
50
|
+
def tr8n_translated
|
51
|
+
return self if frozen?
|
52
|
+
@tr8n_translated = true
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def tr8n_translated?
|
57
|
+
@tr8n_translated
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# = Hash Recursive Merge
|
62
|
+
#
|
63
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
64
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
65
|
+
#
|
66
|
+
# Category:: Ruby
|
67
|
+
# Package:: Hash
|
68
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
69
|
+
# Copyright:: 2007-2008 The Authors
|
70
|
+
# License:: MIT License
|
71
|
+
# Link:: http://www.simonecarletti.com/
|
72
|
+
# Source:: http://gist.github.com/gists/6391/
|
73
|
+
|
74
|
+
#
|
75
|
+
# Recursive version of Hash#merge!
|
76
|
+
#
|
77
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
78
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
79
|
+
#
|
80
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
81
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
82
|
+
# it merges and returns the values from both arrays.
|
83
|
+
#
|
84
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
85
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
86
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
87
|
+
#
|
88
|
+
# Simply using Hash#merge! would return
|
89
|
+
#
|
90
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
91
|
+
#
|
92
|
+
def rmerge!(other_hash)
|
93
|
+
merge!(other_hash) do |key, oldval, newval|
|
94
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Recursive version of Hash#merge
|
100
|
+
#
|
101
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
102
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
103
|
+
# it merges and returns the values from both arrays.
|
104
|
+
#
|
105
|
+
# Compared with Hash#merge, this method provides a different approch
|
106
|
+
# for merging nasted hashes.
|
107
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
108
|
+
# includes the same key, the value is merged instead replaced with
|
109
|
+
# +other_hash+ value.
|
110
|
+
#
|
111
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
112
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
113
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
114
|
+
#
|
115
|
+
# Simply using Hash#merge would return
|
116
|
+
#
|
117
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
118
|
+
#
|
119
|
+
def rmerge(other_hash)
|
120
|
+
r = {}
|
121
|
+
merge(other_hash) do |key, oldval, newval|
|
122
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|