tr8n_core 4.0.1
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.
- 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
|