tr8n_core 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
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