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,200 @@
|
|
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
|
+
#######################################################################
|
25
|
+
#
|
26
|
+
# Decoration Token Forms:
|
27
|
+
#
|
28
|
+
# [link: click here]
|
29
|
+
# or
|
30
|
+
# [link] click here [/link]
|
31
|
+
#
|
32
|
+
# Decoration Tokens Allow Nesting:
|
33
|
+
#
|
34
|
+
# [link: {count} {_messages}]
|
35
|
+
# [link: {count||message}]
|
36
|
+
# [link: {count||person, people}]
|
37
|
+
# [link: {user.name}]
|
38
|
+
#
|
39
|
+
#######################################################################
|
40
|
+
module Tr8n
|
41
|
+
module Tokens
|
42
|
+
class DecorationTokenizer
|
43
|
+
|
44
|
+
attr_reader :tokens, :fragments, :context, :text, :opts
|
45
|
+
|
46
|
+
RESERVED_TOKEN = 'tr8n'
|
47
|
+
|
48
|
+
RE_SHORT_TOKEN_START = '\[[\w]*:'
|
49
|
+
RE_SHORT_TOKEN_END = '\]'
|
50
|
+
RE_LONG_TOKEN_START = '\[[\w]*\]'
|
51
|
+
RE_LONG_TOKEN_END = '\[\/[\w]*\]'
|
52
|
+
RE_TEXT = '[^\[\]]+' #'[\w\s!.:{}\(\)\|,?]*'
|
53
|
+
|
54
|
+
def initialize(text, context = {}, opts = {})
|
55
|
+
@text = "[#{RESERVED_TOKEN}]#{text}[/#{RESERVED_TOKEN}]"
|
56
|
+
@context = context
|
57
|
+
@opts = opts
|
58
|
+
tokenize
|
59
|
+
end
|
60
|
+
|
61
|
+
def tokenize
|
62
|
+
re = [RE_SHORT_TOKEN_START,
|
63
|
+
RE_SHORT_TOKEN_END,
|
64
|
+
RE_LONG_TOKEN_START,
|
65
|
+
RE_LONG_TOKEN_END,
|
66
|
+
RE_TEXT].join('|')
|
67
|
+
@fragments = text.scan(/#{re}/)
|
68
|
+
@tokens = []
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse
|
72
|
+
return @text unless fragments
|
73
|
+
token = fragments.shift
|
74
|
+
|
75
|
+
if token.match(/#{RE_SHORT_TOKEN_START}/)
|
76
|
+
return parse_tree(token.gsub(/[\[:]/, ''), :short)
|
77
|
+
end
|
78
|
+
|
79
|
+
if token.match(/#{RE_LONG_TOKEN_START}/)
|
80
|
+
return parse_tree(token.gsub(/[\[\]]/, ''), :long)
|
81
|
+
end
|
82
|
+
|
83
|
+
token.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_tree(name, type = :short)
|
87
|
+
tree = [name]
|
88
|
+
@tokens << name unless (@tokens.include?(name) or name == RESERVED_TOKEN)
|
89
|
+
|
90
|
+
if type == :short
|
91
|
+
first = true
|
92
|
+
until fragments.first.nil? or fragments.first.match(/#{RE_SHORT_TOKEN_END}/)
|
93
|
+
value = parse
|
94
|
+
if first and value.is_a?(String)
|
95
|
+
value = value.lstrip
|
96
|
+
first = false
|
97
|
+
end
|
98
|
+
tree << value
|
99
|
+
end
|
100
|
+
elsif type == :long
|
101
|
+
until fragments.first.nil? or fragments.first.match(/#{RE_LONG_TOKEN_END}/)
|
102
|
+
tree << parse
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
fragments.shift
|
107
|
+
tree
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_decoration(token_name, token_value)
|
111
|
+
default_decoration = Tr8n.config.default_token_value(token_name, :decoration)
|
112
|
+
unless default_decoration
|
113
|
+
raise Tr8n::Exception.new("Invalid decoration token value #{token_name}")
|
114
|
+
end
|
115
|
+
|
116
|
+
default_decoration = default_decoration.clone
|
117
|
+
decoration_token_values = context[token_name.to_sym] || context[token_name.to_s] || []
|
118
|
+
|
119
|
+
if decoration_token_values.is_a?(Array)
|
120
|
+
params = [token_value, decoration_token_values].flatten
|
121
|
+
params.each_with_index do |param, index|
|
122
|
+
default_decoration.gsub!("{$#{index}}", param.to_s)
|
123
|
+
end
|
124
|
+
|
125
|
+
# clean all the rest of the {$num} params, if any
|
126
|
+
param_index = params.size
|
127
|
+
while default_decoration.index("{$#{param_index}}")
|
128
|
+
default_decoration.gsub!("{$#{param_index}}", "")
|
129
|
+
param_index += 1
|
130
|
+
end
|
131
|
+
|
132
|
+
return default_decoration
|
133
|
+
end
|
134
|
+
|
135
|
+
if decoration_token_values.is_a?(Hash)
|
136
|
+
default_decoration.gsub!("{$0}", token_value.to_s)
|
137
|
+
|
138
|
+
decoration_token_values.keys.each do |key|
|
139
|
+
default_decoration.gsub!("{$#{key}}", decoration_token_values[key].to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
return default_decoration
|
143
|
+
end
|
144
|
+
|
145
|
+
raise Tr8n::Exception.new("Don't know how to process decoration token value")
|
146
|
+
end
|
147
|
+
|
148
|
+
def allowed_token?(token)
|
149
|
+
return true if opts[:allowed_tokens].nil?
|
150
|
+
opts[:allowed_tokens].include?(token)
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply(token, value)
|
154
|
+
return value if token == RESERVED_TOKEN
|
155
|
+
return value unless allowed_token?(token)
|
156
|
+
|
157
|
+
method = context[token.to_sym] || context[token.to_s]
|
158
|
+
|
159
|
+
if method
|
160
|
+
if method.is_a?(Proc)
|
161
|
+
return method.call(value)
|
162
|
+
end
|
163
|
+
|
164
|
+
if method.is_a?(Array) or method.is_a?(Hash)
|
165
|
+
return default_decoration(token, value)
|
166
|
+
end
|
167
|
+
|
168
|
+
if method.is_a?(String)
|
169
|
+
return method.to_s.gsub("{$0}", value)
|
170
|
+
end
|
171
|
+
|
172
|
+
raise Tr8n::Exception.new("Invalid decoration token value")
|
173
|
+
end
|
174
|
+
|
175
|
+
if Tr8n.config.default_token_value(token, :decoration)
|
176
|
+
return default_decoration(token, value)
|
177
|
+
end
|
178
|
+
|
179
|
+
raise Tr8n::Exception.new("Missing decoration token value")
|
180
|
+
end
|
181
|
+
|
182
|
+
def evaluate(expr)
|
183
|
+
unless expr.is_a?(Array)
|
184
|
+
return expr
|
185
|
+
end
|
186
|
+
|
187
|
+
token = expr[0]
|
188
|
+
args = expr.drop(1)
|
189
|
+
value = args.map { |a| self.evaluate(a) }.join('')
|
190
|
+
|
191
|
+
apply(token, value)
|
192
|
+
end
|
193
|
+
|
194
|
+
def substitute
|
195
|
+
evaluate(parse)
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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
|
+
#######################################################################
|
25
|
+
#
|
26
|
+
# Hidden Token Forms:
|
27
|
+
#
|
28
|
+
# {_he_she}
|
29
|
+
# {_posted__items}
|
30
|
+
#
|
31
|
+
# '_' escaped as '/'
|
32
|
+
# '__' escaped as '__'
|
33
|
+
#
|
34
|
+
# Hidden tokens cannot have rules and are there for default language
|
35
|
+
# substitutions only
|
36
|
+
#
|
37
|
+
#######################################################################
|
38
|
+
|
39
|
+
class Tr8n::Tokens::Hidden < Tr8n::Tokens::Data
|
40
|
+
def self.expression
|
41
|
+
/(\{_[\w]+\})/
|
42
|
+
end
|
43
|
+
|
44
|
+
def allowed_in_translation?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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
|
+
#######################################################################
|
25
|
+
#
|
26
|
+
# Method Token Forms
|
27
|
+
#
|
28
|
+
# {user.name}
|
29
|
+
# {user.name:gender}
|
30
|
+
#
|
31
|
+
#######################################################################
|
32
|
+
|
33
|
+
class Tr8n::Tokens::Method < Tr8n::Tokens::Data
|
34
|
+
def self.expression
|
35
|
+
/(\{[^_:.][\w]*(\.[\w]+)(:[\w]+)*(::[\w]+)*\})/
|
36
|
+
end
|
37
|
+
|
38
|
+
def object_name
|
39
|
+
@object_name ||= short_name.split(".").first
|
40
|
+
end
|
41
|
+
|
42
|
+
def object_method_name
|
43
|
+
@object_method_name ||= short_name.split(".").last
|
44
|
+
end
|
45
|
+
|
46
|
+
def substitute(label, context, language, options = {})
|
47
|
+
object = hash_value(context, object_name)
|
48
|
+
raise Tr8n::Exception.new("Missing value for a token: #{full_name}") unless object
|
49
|
+
object_value = sanitize(object, object.send(object_method_name), options.merge(:sanitize_values => true), language)
|
50
|
+
label.gsub(full_name, object_value)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,191 @@
|
|
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
|
+
#######################################################################
|
25
|
+
#
|
26
|
+
# Transform Token Form
|
27
|
+
#
|
28
|
+
# {count:number || one: message, many: messages}
|
29
|
+
# {count:number || one: сообщение, few: сообщения, many: сообщений, other: много сообщений} in other case the number is not displayed#
|
30
|
+
#
|
31
|
+
# {count | message} - will not include {count}, resulting in "messages" with implied {count}
|
32
|
+
# {count | message, messages}
|
33
|
+
#
|
34
|
+
# {count:number | message, messages}
|
35
|
+
#
|
36
|
+
# {user:gender | he, she, he/she}
|
37
|
+
#
|
38
|
+
# {user:gender | male: he, female: she, other: he/she}
|
39
|
+
#
|
40
|
+
# {now:date | did, does, will do}
|
41
|
+
# {users:list | all male, all female, mixed genders}
|
42
|
+
#
|
43
|
+
# {count || message, messages} - will include count: "5 messages"
|
44
|
+
#
|
45
|
+
#######################################################################
|
46
|
+
|
47
|
+
class Tr8n::Tokens::Transform < Tr8n::Tokens::Data
|
48
|
+
attr_reader :pipe_separator, :piped_params
|
49
|
+
|
50
|
+
def self.expression
|
51
|
+
/(\{[^_:|][\w]*(:[\w]+)*(::[\w]+)*\s*\|\|?[^{^}]+\})/
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_elements
|
55
|
+
name_without_parens = @full_name[1..-2]
|
56
|
+
name_without_pipes = name_without_parens.split('|').first.strip
|
57
|
+
name_without_case_keys = name_without_pipes.split('::').first.strip
|
58
|
+
|
59
|
+
@short_name = name_without_pipes.split(':').first.strip
|
60
|
+
@case_keys = name_without_pipes.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
|
61
|
+
@context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
|
62
|
+
|
63
|
+
@pipe_separator = (full_name.index("||") ? "||" : "|")
|
64
|
+
@piped_params = name_without_parens.split(pipe_separator).last.split(",").collect{|param| param.strip}
|
65
|
+
end
|
66
|
+
|
67
|
+
def displayed_in_translation?
|
68
|
+
pipe_separator == "||"
|
69
|
+
end
|
70
|
+
|
71
|
+
def implied?
|
72
|
+
not displayed_in_translation?
|
73
|
+
end
|
74
|
+
|
75
|
+
# token: {count|| one: message, many: messages}
|
76
|
+
# results in: {"one": "message", "many": "messages"}
|
77
|
+
#
|
78
|
+
# token: {count|| message}
|
79
|
+
# transform: [{"one": "{$0}", "other": "{$0::plural}"}, {"one": "{$0}", "other": "{$1}"}]
|
80
|
+
# results in: {"one": "message", "other": "messages"}
|
81
|
+
#
|
82
|
+
# token: {count|| message, messages}
|
83
|
+
# transform: [{"one": "{$0}", "other": "{$0::plural}"}, {"one": "{$0}", "other": "{$1}"}]
|
84
|
+
# results in: {"one": "message", "other": "messages"}
|
85
|
+
#
|
86
|
+
# token: {user| Dorogoi, Dorogaya}
|
87
|
+
# transform: ["unsupported", {"male": "{$0}", "female": "{$1}", "other": "{$0}/{$1}"}]
|
88
|
+
# results in: {"male": "Dorogoi", "female": "Dorogaya", "other": "Dorogoi/Dorogaya"}
|
89
|
+
#
|
90
|
+
# token: {actors:|| likes, like}
|
91
|
+
# transform: ["unsupported", {"one": "{$0}", "other": "{$1}"}]
|
92
|
+
# results in: {"one": "likes", "other": "like"}
|
93
|
+
def generate_value_map(object, params, context)
|
94
|
+
values = {}
|
95
|
+
|
96
|
+
if params.first.index(':')
|
97
|
+
params.each do |p|
|
98
|
+
nv = p.split(':')
|
99
|
+
values[nv.first.strip] = nv.last.strip
|
100
|
+
end
|
101
|
+
return values
|
102
|
+
end
|
103
|
+
|
104
|
+
unless context.token_mapping
|
105
|
+
raise Tr8n::Exception.new("The token context #{context.keyword} does not support transformation for unnamed params: #{full_name}")
|
106
|
+
end
|
107
|
+
|
108
|
+
token_mapping = context.token_mapping
|
109
|
+
|
110
|
+
# "unsupported"
|
111
|
+
if token_mapping.is_a?(String)
|
112
|
+
raise Tr8n::Exception.new("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
113
|
+
end
|
114
|
+
|
115
|
+
# ["unsupported", "unsupported", {}]
|
116
|
+
if token_mapping.is_a?(Array)
|
117
|
+
if params.size > token_mapping.size
|
118
|
+
raise Tr8n::Exception.new("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
119
|
+
end
|
120
|
+
token_mapping = token_mapping[params.size-1]
|
121
|
+
if token_mapping.is_a?(String)
|
122
|
+
raise Tr8n::Exception.new("The token mapping #{token_mapping} does not support #{params.size} params: #{full_name}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# {}
|
127
|
+
token_mapping.each do |key, value|
|
128
|
+
values[key] = value
|
129
|
+
value.scan(/({\$\d(::\w+)*})/).each do |matches|
|
130
|
+
token = matches.first
|
131
|
+
parts = token[1..-2].split('::')
|
132
|
+
index = parts.first.gsub('$', '').to_i
|
133
|
+
|
134
|
+
if params.size < index
|
135
|
+
raise Tr8n::Exception.new("The index inside #{context.token_mapping} is out of bound: #{full_name}")
|
136
|
+
end
|
137
|
+
|
138
|
+
# apply settings cases
|
139
|
+
value = params[index]
|
140
|
+
if Tr8n.config.application.feature_enabled?(:language_cases)
|
141
|
+
parts[1..-1].each do |case_key|
|
142
|
+
lcase = context.language.language_case_by_keyword(case_key)
|
143
|
+
unless lcase
|
144
|
+
raise Tr8n::Exception.new("Language case #{case_key} for context #{context.keyword} is not defined: #{full_name}")
|
145
|
+
end
|
146
|
+
value = lcase.apply(value, object)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
values[key] = values[key].gsub(token, value)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
values
|
154
|
+
end
|
155
|
+
|
156
|
+
def substitute(label, context, language, options = {})
|
157
|
+
object = Tr8n::Tokens::Data.token_object(context, key)
|
158
|
+
|
159
|
+
unless object
|
160
|
+
raise Tr8n::Exception.new("Missing value for a token: #{full_name}")
|
161
|
+
end
|
162
|
+
|
163
|
+
if piped_params.empty?
|
164
|
+
raise Tr8n::Exception.new("Piped params may not be empty: #{full_name}")
|
165
|
+
end
|
166
|
+
|
167
|
+
language_context = context_for_language(language)
|
168
|
+
|
169
|
+
piped_values = generate_value_map(object, piped_params, language_context)
|
170
|
+
|
171
|
+
rule = language_context.find_matching_rule(object)
|
172
|
+
return label unless rule
|
173
|
+
|
174
|
+
value = piped_values[rule.keyword]
|
175
|
+
if value.nil? and language_context.fallback_rule
|
176
|
+
value = piped_values[language_context.fallback_rule.keyword]
|
177
|
+
end
|
178
|
+
|
179
|
+
return label unless value
|
180
|
+
|
181
|
+
substitution_value = []
|
182
|
+
if displayed_in_translation?
|
183
|
+
substitution_value << token_value(hash_value(context, key), options, language)
|
184
|
+
substitution_value << " "
|
185
|
+
end
|
186
|
+
substitution_value << value
|
187
|
+
|
188
|
+
label.gsub(full_name, substitution_value.join(""))
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
@@ -0,0 +1,104 @@
|
|
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 Tr8n::Translation < Tr8n::Base
|
25
|
+
belongs_to :translation_key, :language
|
26
|
+
attributes :locale, :label, :context, :precedence
|
27
|
+
|
28
|
+
def initialize(attrs = {})
|
29
|
+
super
|
30
|
+
|
31
|
+
if locale
|
32
|
+
self.language = self.translation_key.application.language(locale)
|
33
|
+
end
|
34
|
+
|
35
|
+
calculate_precedence
|
36
|
+
end
|
37
|
+
|
38
|
+
# switches to a new translation key
|
39
|
+
def set_translation_key(tkey)
|
40
|
+
self.translation_key = tkey
|
41
|
+
self.language = tkey.application.language(locale)
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_context_rules?
|
45
|
+
context and context.any?
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# the precedence is based on the number of fallback rules in the context.
|
50
|
+
# a fallback rule is indicated by the keyword "other"
|
51
|
+
# the more "others" are used the lower the precedence will be
|
52
|
+
#
|
53
|
+
# 0 indicates the highest precedence
|
54
|
+
#
|
55
|
+
def calculate_precedence
|
56
|
+
self.precedence = 0
|
57
|
+
return unless has_context_rules?
|
58
|
+
|
59
|
+
context.values.each do |rules|
|
60
|
+
rules.values.each do |rule_key|
|
61
|
+
self.precedence += 1 if rule_key == "other"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#{
|
67
|
+
# "count" => [{"type" => "number", "key" => "one"}],
|
68
|
+
# "user" => ["type" => "gender", "key" => "male"]
|
69
|
+
#}
|
70
|
+
#{
|
71
|
+
# "count" => [{"number":"one"}],
|
72
|
+
# "user" => [{"gender":"male"}]
|
73
|
+
#}
|
74
|
+
# checks if the translation is valid for the given tokens
|
75
|
+
def matches_rules?(token_values)
|
76
|
+
return true unless has_context_rules?
|
77
|
+
|
78
|
+
context.each do |token_name, rules|
|
79
|
+
token_object = Tr8n::Tokens::Data.token_object(token_values, token_name)
|
80
|
+
return false unless token_object
|
81
|
+
|
82
|
+
rules.each do |context_key, rule_key|
|
83
|
+
next if rule_key == "other"
|
84
|
+
|
85
|
+
context = language.context_by_keyword(context_key)
|
86
|
+
return false unless context
|
87
|
+
|
88
|
+
rule = context.find_matching_rule(token_object)
|
89
|
+
return false if rule.nil? or rule.keyword != rule_key
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
#######################################################################################################
|
97
|
+
## Cache Methods
|
98
|
+
#######################################################################################################
|
99
|
+
|
100
|
+
def to_cache_hash
|
101
|
+
to_hash(:label, :context)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|