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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +69 -0
  4. data/Rakefile +9 -0
  5. data/config/config.yml +34 -0
  6. data/config/tokens/data.yml +45 -0
  7. data/config/tokens/decorations.yml +37 -0
  8. data/lib/tr8n/application.rb +320 -0
  9. data/lib/tr8n/base.rb +123 -0
  10. data/lib/tr8n/cache.rb +144 -0
  11. data/lib/tr8n/cache_adapters/cdb.rb +74 -0
  12. data/lib/tr8n/cache_adapters/file.rb +70 -0
  13. data/lib/tr8n/cache_adapters/memcache.rb +91 -0
  14. data/lib/tr8n/cache_adapters/redis.rb +94 -0
  15. data/lib/tr8n/component.rb +68 -0
  16. data/lib/tr8n/config.rb +291 -0
  17. data/lib/tr8n/decorators/base.rb +35 -0
  18. data/lib/tr8n/decorators/default.rb +30 -0
  19. data/lib/tr8n/decorators/html.rb +63 -0
  20. data/lib/tr8n/exception.rb +26 -0
  21. data/lib/tr8n/language.rb +250 -0
  22. data/lib/tr8n/language_case.rb +116 -0
  23. data/lib/tr8n/language_case_rule.rb +85 -0
  24. data/lib/tr8n/language_context.rb +115 -0
  25. data/lib/tr8n/language_context_rule.rb +62 -0
  26. data/lib/tr8n/logger.rb +83 -0
  27. data/lib/tr8n/rules_engine/evaluator.rb +156 -0
  28. data/lib/tr8n/rules_engine/parser.rb +83 -0
  29. data/lib/tr8n/source.rb +95 -0
  30. data/lib/tr8n/tokens/data.rb +410 -0
  31. data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
  32. data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
  33. data/lib/tr8n/tokens/hidden.rb +48 -0
  34. data/lib/tr8n/tokens/method.rb +52 -0
  35. data/lib/tr8n/tokens/transform.rb +191 -0
  36. data/lib/tr8n/translation.rb +104 -0
  37. data/lib/tr8n/translation_key.rb +205 -0
  38. data/lib/tr8n/translator.rb +62 -0
  39. data/lib/tr8n/utils.rb +124 -0
  40. data/lib/tr8n_core/ext/array.rb +74 -0
  41. data/lib/tr8n_core/ext/date.rb +63 -0
  42. data/lib/tr8n_core/ext/fixnum.rb +39 -0
  43. data/lib/tr8n_core/ext/hash.rb +126 -0
  44. data/lib/tr8n_core/ext/string.rb +44 -0
  45. data/lib/tr8n_core/ext/time.rb +71 -0
  46. data/lib/tr8n_core/generators/cache/base.rb +85 -0
  47. data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
  48. data/lib/tr8n_core/generators/cache/file.rb +69 -0
  49. data/lib/tr8n_core/modules/logger.rb +43 -0
  50. data/lib/tr8n_core/version.rb +27 -0
  51. data/lib/tr8n_core.rb +68 -0
  52. data/spec/application_spec.rb +228 -0
  53. data/spec/base_spec.rb +19 -0
  54. data/spec/config_spec.rb +16 -0
  55. data/spec/decorator_spec.rb +10 -0
  56. data/spec/decorators/base_spec.rb +14 -0
  57. data/spec/decorators/default_spec.rb +12 -0
  58. data/spec/decorators/html_spec.rb +50 -0
  59. data/spec/fixtures/application.json +112 -0
  60. data/spec/fixtures/languages/en-US.json +1424 -0
  61. data/spec/fixtures/languages/es.json +291 -0
  62. data/spec/fixtures/languages/ru.json +550 -0
  63. data/spec/fixtures/translations/ru/basic.json +56 -0
  64. data/spec/fixtures/translations/ru/counters.json +43 -0
  65. data/spec/fixtures/translations/ru/genders.json +171 -0
  66. data/spec/fixtures/translations/ru/last_names.txt +200 -0
  67. data/spec/fixtures/translations/ru/names.json +1 -0
  68. data/spec/fixtures/translations/ru/names.txt +458 -0
  69. data/spec/helper.rb +84 -0
  70. data/spec/language_case_rule_spec.rb +57 -0
  71. data/spec/language_case_spec.rb +58 -0
  72. data/spec/language_context_rule_spec.rb +73 -0
  73. data/spec/language_context_spec.rb +331 -0
  74. data/spec/language_spec.rb +16 -0
  75. data/spec/rules_engine/evaluator_spec.rb +148 -0
  76. data/spec/rules_engine/parser_spec.rb +29 -0
  77. data/spec/tokens/data_spec.rb +160 -0
  78. data/spec/tokens/data_tokenizer_spec.rb +29 -0
  79. data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
  80. data/spec/tokens/hidden_spec.rb +24 -0
  81. data/spec/tokens/method_spec.rb +84 -0
  82. data/spec/tokens/transform_spec.rb +50 -0
  83. data/spec/translation_key_spec.rb +96 -0
  84. data/spec/translation_spec.rb +24 -0
  85. data/spec/utils_spec.rb +64 -0
  86. 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