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,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
|