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