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,291 @@
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
+ module Tr8n
25
+
26
+ def self.config
27
+ @config ||= Tr8n::Config.new
28
+ end
29
+
30
+ # config class can be set
31
+ def self.config=(config)
32
+ @config = config
33
+ end
34
+
35
+ # Acts as a global singleton that holds all Tr8n configuration
36
+ # The class can be extended with a different implementation, as long as the interface is supported
37
+ class Config < Tr8n::Base
38
+ thread_safe_attributes :application, :current_user, :current_language, :current_translator, :current_source, :current_component
39
+ thread_safe_attributes :block_options
40
+
41
+ def root
42
+ @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
43
+ end
44
+
45
+ def env
46
+ 'defaults'
47
+ end
48
+
49
+ def defaults
50
+ @defaults ||= Tr8n::Utils.load_yaml("#{root}/config/config.yml", env)
51
+ end
52
+
53
+ def enabled?
54
+ hash_value(defaults, "enabled")
55
+ end
56
+
57
+ def disabled?
58
+ not enabled?
59
+ end
60
+
61
+ #########################################################
62
+ ## Application
63
+ #########################################################
64
+
65
+ def host
66
+ hash_value(defaults, "application.host")
67
+ end
68
+
69
+ def app_key
70
+ hash_value(defaults, "application.key")
71
+ end
72
+
73
+ def app_secret
74
+ hash_value(defaults, "application.secret")
75
+ end
76
+
77
+ def init_application
78
+ Tr8n::Application.init(host, app_key, app_secret)
79
+ self.current_source = "/tr8n/core"
80
+ end
81
+
82
+ def default_locale
83
+ return application.default_locale if application
84
+ hash_value(defaults, "default_locale")
85
+ end
86
+
87
+ def default_language
88
+ return Tr8n::Language.new(:locale => default_locale, :default => true) if disabled?
89
+ application.language(default_locale)
90
+ end
91
+
92
+ def default_level
93
+ return application.default_level if application
94
+ hash_value(defaults, "default_level")
95
+ end
96
+
97
+ def reset
98
+ self.application = nil
99
+ self.current_user = nil
100
+ self.current_language = nil
101
+ self.current_translator = nil
102
+ self.current_source = nil
103
+ self.current_component = nil
104
+ self.block_options = nil
105
+ end
106
+
107
+ #########################################################
108
+ ## Logger
109
+ #########################################################
110
+
111
+ def logger_enabled?
112
+ hash_value(defaults, "logger.enabled")
113
+ end
114
+
115
+ def log_path
116
+ ".#{hash_value(defaults, "logger.path")}"
117
+ end
118
+
119
+ #########################################################
120
+ ## Cache
121
+ #########################################################
122
+
123
+ def cache_enabled?
124
+ hash_value(defaults, "cache.enabled")
125
+ end
126
+
127
+ def cache_path
128
+ "#{root}#{hash_value(defaults, "cache.path")}"
129
+ end
130
+
131
+ def cache_adapter
132
+ hash_value(defaults, "cache.adapter")
133
+ end
134
+
135
+ def cache_version
136
+ hash_value(defaults, "cache.version")
137
+ end
138
+
139
+ def cache_host
140
+ hash_value(defaults, "cache.host")
141
+ end
142
+
143
+ def cache_timeout
144
+ hash_value(defaults, 'cache.timeout')
145
+ end
146
+
147
+ #########################################################
148
+ ## Rules Engine
149
+ #########################################################
150
+
151
+ def context_rules
152
+ @context_rules ||= {
153
+ "number" => {
154
+ "variables" => {
155
+ }
156
+ },
157
+ "gender" => {
158
+ "variables" => {
159
+ "@gender" => "gender",
160
+ }
161
+ },
162
+ "genders" => {
163
+ "variables" => {
164
+ "@genders" => lambda{|list| list.collect do |u|
165
+ u.is_a?(Hash) ? (u["gender"] || u[:gender]) : u.gender
166
+ end
167
+ },
168
+ "@size" => lambda{|list| list.size}
169
+ }
170
+ },
171
+ "date" => {
172
+ "variables" => {
173
+ }
174
+ },
175
+ "time" => {
176
+ "variables" => {
177
+ }
178
+ },
179
+ "list" => {
180
+ "variables" => {
181
+ "@count" => lambda{|list| list.size}
182
+ }
183
+ },
184
+ }
185
+ end
186
+
187
+ def decorator_class
188
+ Tr8n::Decorators::Default
189
+ end
190
+
191
+ def default_data_tokens
192
+ @default_data_tokens ||= Tr8n::Utils.load_yaml("#{root}/config/tokens/data.yml")
193
+ end
194
+
195
+ def default_decoration_tokens
196
+ @default_decoration_tokens ||= Tr8n::Utils.load_yaml("#{root}/config/tokens/decorations.yml")
197
+ end
198
+
199
+ def default_token_value(token_name, type = :data, format = :html)
200
+ return default_data_tokens[format.to_s][token_name.to_s] if type.to_sym == :data
201
+ return default_decoration_tokens[format.to_s][token_name.to_s] if type.to_sym == :decoration
202
+ end
203
+
204
+ #########################################################
205
+ ## Block Options
206
+ #########################################################
207
+
208
+ def block_options
209
+ (Thread.current[:block_options] ||= []).last || {}
210
+ end
211
+
212
+ def with_block_options(opts)
213
+ Thread.current[:block_options] ||= []
214
+ Thread.current[:block_options].push(opts)
215
+ if block_given?
216
+ ret = yield
217
+ end
218
+ Thread.current[:block_options].pop
219
+ ret
220
+ end
221
+
222
+ def current_source_from_block_options
223
+ arr = Thread.current[:block_options] || []
224
+ arr.reverse.each do |opts|
225
+ return application.source_by_key(opts[:source]) unless opts[:source].blank?
226
+ end
227
+ nil
228
+ end
229
+
230
+ def current_component_from_block_options
231
+ arr = Thread.current[:block_options] || []
232
+ arr.reverse.each do |opts|
233
+ return application.component_by_key(opts[:component]) unless opts[:component].blank?
234
+ end
235
+ Tr8n.config.current_component
236
+ end
237
+
238
+ #########################################################
239
+ ## Localization
240
+ #########################################################
241
+
242
+ def localization
243
+ hash_value(defaults, "localization")
244
+ end
245
+
246
+ def strftime_symbol_to_token(symbol)
247
+ {
248
+ "%a" => "{short_week_day_name}",
249
+ "%A" => "{week_day_name}",
250
+ "%b" => "{short_month_name}",
251
+ "%B" => "{month_name}",
252
+ "%p" => "{am_pm}",
253
+ "%d" => "{days}",
254
+ "%e" => "{day_of_month}",
255
+ "%j" => "{year_days}",
256
+ "%m" => "{months}",
257
+ "%W" => "{week_num}",
258
+ "%w" => "{week_days}",
259
+ "%y" => "{short_years}",
260
+ "%Y" => "{years}",
261
+ "%l" => "{trimed_hour}",
262
+ "%H" => "{full_hours}",
263
+ "%I" => "{short_hours}",
264
+ "%M" => "{minutes}",
265
+ "%S" => "{seconds}",
266
+ "%s" => "{since_epoch}"
267
+ }[symbol]
268
+ end
269
+
270
+ def default_day_names
271
+ hash_value(defaults, "localization.default_day_names")
272
+ end
273
+
274
+ def default_abbr_day_names
275
+ hash_value(defaults, "localization.default_abbr_day_names")
276
+ end
277
+
278
+ def default_month_names
279
+ hash_value(defaults, "localization.default_month_names")
280
+ end
281
+
282
+ def default_abbr_month_names
283
+ hash_value(defaults, "localization.default_abbr_month_names")
284
+ end
285
+
286
+ def default_date_formats
287
+ hash_value(defaults, "localization.custom_date_formats")
288
+ end
289
+
290
+ end
291
+ end
@@ -0,0 +1,35 @@
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::Decorators::Base < Tr8n::Base
25
+
26
+ def self.decorator
27
+ # can alternate based on the format in the config
28
+ Tr8n.config.decorator_class.new
29
+ end
30
+
31
+ def decorate(translated_label, translation_language, target_language, translation_key, options = {})
32
+ raise Tr8n::Exception.new("Must be implemented by the extending class")
33
+ end
34
+
35
+ end
@@ -0,0 +1,30 @@
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::Decorators::Default < Tr8n::Decorators::Base
25
+
26
+ def decorate(translated_label, translation_language, target_language, translation_key, options = {})
27
+ translated_label
28
+ end
29
+
30
+ end
@@ -0,0 +1,63 @@
1
+ #--
2
+ # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ class Tr8n::Decorators::Html < Tr8n::Decorators::Base
25
+
26
+ def decorate(translated_label, translation_language, target_language, translation_key, options = {})
27
+ # skip decoration if instructed so
28
+ return translated_label if options[:skip_decorations]
29
+ # if translation key language is the same as target language - skip decorations
30
+ return translated_label if translation_key.language == target_language
31
+ return translated_label unless Tr8n.config.current_translator and Tr8n.config.current_translator.inline?
32
+ return translated_label if translation_key.locked? and not Tr8n.config.current_translator.manager?
33
+
34
+ element = 'span'
35
+ if options[:use_div]
36
+ element = 'div'
37
+ end
38
+
39
+ if translation_key.id.nil?
40
+ return "<#{element} class='tr8n_pending'>#{translated_label}</#{element}>"
41
+ end
42
+
43
+ classes = ['tr8n_translatable']
44
+
45
+ if translation_key.locked?
46
+ # must be a manager and enabling locking feature
47
+ return translated_label unless Tr8n.config.current_translator.feature_enabled?(:show_locked_keys)
48
+ classes << 'tr8n_locked'
49
+ elsif translation_language == translation_key.language
50
+ classes << 'tr8n_not_translated'
51
+ elsif translation_language == target_language
52
+ classes << 'tr8n_translated'
53
+ else
54
+ classes << 'tr8n_fallback'
55
+ end
56
+
57
+ html = "<#{element} class='#{classes.join(' ')}' data-translation_key_id='#{translation_key.id}'>"
58
+ html << translated_label
59
+ html << "</#{element}>"
60
+ html
61
+ end
62
+
63
+ end
@@ -0,0 +1,26 @@
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::Exception < StandardError
25
+
26
+ end
@@ -0,0 +1,250 @@
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::Language < Tr8n::Base
25
+ belongs_to :application
26
+ attributes :locale, :name, :english_name, :native_name, :right_to_left, :flag_url
27
+ has_many :contexts, :cases
28
+
29
+ def initialize(attrs = {})
30
+ super
31
+
32
+ self.attributes[:contexts] = {}
33
+ if hash_value(attrs, :contexts)
34
+ hash_value(attrs, :contexts).each do |key, context|
35
+ self.attributes[:contexts][key] = Tr8n::LanguageContext.new(context.merge(:language => self))
36
+ end
37
+ end
38
+
39
+ self.attributes[:cases] = {}
40
+ if hash_value(attrs, :cases)
41
+ hash_value(attrs, :cases).each do |key, lcase|
42
+ self.attributes[:cases][key] = Tr8n::LanguageCase.new(lcase.merge(:language => self))
43
+ end
44
+ end
45
+ end
46
+
47
+ def context_by_keyword(keyword)
48
+ hash_value(contexts, keyword)
49
+ end
50
+
51
+ def context_by_token_name(token_name)
52
+ contexts.values.detect{|ctx| ctx.applies_to_token?(token_name)}
53
+ end
54
+
55
+ def language_case_by_keyword(keyword)
56
+ cases[keyword]
57
+ end
58
+
59
+ def has_definition?
60
+ contexts.any?
61
+ end
62
+
63
+ def default?
64
+ return true unless application
65
+ application.default_locale == locale
66
+ end
67
+
68
+ def dir
69
+ right_to_left? ? "rtl" : "ltr"
70
+ end
71
+
72
+ def align(dest)
73
+ return dest unless right_to_left?
74
+ dest.to_s == 'left' ? 'right' : 'left'
75
+ end
76
+
77
+ def full_name
78
+ return english_name if english_name == native_name
79
+ "#{english_name} - #{native_name}"
80
+ end
81
+
82
+ def current_source(options)
83
+ options[:source] || Tr8n.config.block_options[:source] || Tr8n.config.current_source
84
+ end
85
+
86
+ #######################################################################################################
87
+ # Translation Methods
88
+ #
89
+ # Note - when inline translation mode is enable, cache will not be used and translators will
90
+ # always hit the live service to get the most recent translations
91
+ #
92
+ # Some cache adapters cache by source, others by key. Some are read-only, some are built on the fly.
93
+ #
94
+ # There are three ways to call the tr method
95
+ #
96
+ # tr(label, description = "", tokens = {}, options = {})
97
+ # or
98
+ # tr(label, tokens = {}, options = {})
99
+ # or
100
+ # tr(:label => label, :description => "", :tokens => {}, :options => {})
101
+ ########################################################################################################
102
+
103
+ def translate(label, description = nil, tokens = {}, options = {})
104
+ return label if label.tr8n_translated?
105
+
106
+ params = Tr8n::Utils.normalize_tr_params(label, description, tokens, options)
107
+ key_locale = hash_value(params[:options], :locale) || hash_value(Tr8n.config.block_options, :locale) || Tr8n.config.default_locale
108
+ key_level = hash_value(params[:options], :level) || hash_value(Tr8n.config.block_options, :level) || Tr8n.config.default_level
109
+
110
+ temp_key = Tr8n::TranslationKey.new({
111
+ :application => application,
112
+ :label => params[:label],
113
+ :description => params[:description],
114
+ :locale => key_locale,
115
+ :level => key_level,
116
+ :translations => []
117
+ })
118
+
119
+ #Tr8n.logger.info("Translating " + params[:label] + " from: " + key_locale.inspect + " to " + locale.inspect)
120
+
121
+ params[:tokens].merge!(:viewing_user => Tr8n.config.current_user)
122
+
123
+ if Tr8n.config.disabled? or locale == key_locale
124
+ return temp_key.substitute_tokens(params[:label], params[:tokens], self, params[:options]).tr8n_translated
125
+ end
126
+
127
+ translation_key = application.translation_key(temp_key.key)
128
+ if translation_key
129
+ return translation_key.translate(self, params[:tokens], params[:options])
130
+ end
131
+
132
+ # no cache or translator is using inline mode - use service, otherwise load from cache
133
+ if not Tr8n.config.cache_enabled? or (Tr8n.config.current_translator and Tr8n.config.current_translator.inline?)
134
+ return translate_from_service(temp_key, params[:tokens], params[:options]).tr8n_translated
135
+ end
136
+
137
+ translate_from_cache(temp_key, params[:tokens], params[:options]).tr8n_translated
138
+ rescue Exception => ex
139
+ Tr8n.logger.error("Failed to translate: #{params[:label]} : #{ex.message}")
140
+ Tr8n.logger.error(ex.backtrace)
141
+ params[:label]
142
+ end
143
+ alias :tr :translate
144
+
145
+ def translate_from_cache(translation_key, tokens, options)
146
+ # In most scenarios translations should be cached by source
147
+ if Tr8n.cache.cached_by_source?
148
+ source_key = current_source(options)
149
+ cache_key = Tr8n::Source.cache_key(source_key, locale);
150
+ source = Tr8n.cache.fetch(cache_key)
151
+
152
+ # if it came from cache, it will be full of translation keys with translations for the locale
153
+ if source
154
+ translation_keys = source.translation_keys
155
+ elsif Tr8n.cache.read_only?
156
+ translation_keys = {}
157
+ else
158
+ # get the source info from the application
159
+ source = application.source(source_key)
160
+ translation_keys = source.fetch_translations_for_language(self, options)
161
+ Tr8n.cache.store(cache_key, source)
162
+ end
163
+
164
+ if translation_keys[translation_key.key]
165
+ translation_key = translation_keys[translation_key.key]
166
+ return translation_key.translate(self, tokens, options)
167
+ end
168
+
169
+ translation_key.translations = {locale => []}
170
+ application.cache_translation_key(translation_key)
171
+ return translation_key.translate(self, tokens, options)
172
+ end
173
+
174
+ # CDB allows for caching by key
175
+ cache_key = Tr8n::TranslationKey.cache_key(translation_key.label, translation_key.description, locale)
176
+ translations = Tr8n.cache.fetch(cache_key)
177
+
178
+ if translations.nil?
179
+ if Tr8n.cache.read_only?
180
+ translation_key.translations = {locale => []}
181
+ application.cache_translation_key(translation_key)
182
+ return translation_key.translate(self, tokens, options)
183
+ end
184
+
185
+ translation_key = translation_key.fetch_translations(self, options)
186
+ Tr8n.cache.store(cache_key, translation_key.translations(self))
187
+ return translation_key.translate(self, tokens, options)
188
+ end
189
+
190
+ unless translations.is_a?(Array)
191
+ translations = [translations]
192
+ end
193
+
194
+ translation_key.translations = {locale => translations}
195
+ application.cache_translation_key(translation_key)
196
+ translation_key.translate(self, tokens, options)
197
+ end
198
+
199
+ def translate_from_service(translation_key, tokens, options)
200
+ source_key = current_source(options)
201
+
202
+ if source_key
203
+ source = application.source(source_key)
204
+ source_translation_keys = source.fetch_translations_for_language(self, options)
205
+ if source_translation_keys[translation_key.key]
206
+ translation_key = source_translation_keys[translation_key.key]
207
+ else
208
+ application.register_missing_key(translation_key, source)
209
+ end
210
+
211
+ return translation_key.translate(self, tokens, options)
212
+ end
213
+
214
+ # all translations are cached in memory as the second level cache
215
+ memory_cached_translation_key = application.translation_key(translation_key.key)
216
+ if memory_cached_translation_key
217
+ translation_key = memory_cached_translation_key
218
+ else
219
+ translation_key = translation_key.fetch_translations(self, options)
220
+ end
221
+
222
+ translation_key.translate(self, tokens, options)
223
+ end
224
+
225
+ #######################################################################################################
226
+ ## Cache Methods
227
+ #######################################################################################################
228
+
229
+ def self.cache_prefix
230
+ 'l@'
231
+ end
232
+
233
+ def self.cache_key(locale)
234
+ "#{cache_prefix}_[#{locale}]"
235
+ end
236
+
237
+ def to_cache_hash
238
+ hash = to_hash(:locale, :name, :english_name, :native_name, :right_to_left, :flag_url)
239
+ hash[:contexts] = {}
240
+ contexts.each do |name, value|
241
+ hash[:contexts][name] = value.to_cache_hash
242
+ end
243
+ hash[:cases] = {}
244
+ cases.each do |name, value|
245
+ hash[:cases][name] = value.to_cache_hash
246
+ end
247
+ hash
248
+ end
249
+
250
+ end