tr8n_core 4.0.4 → 4.0.5

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +44 -6
  4. data/lib/tr8n/api_client.rb +109 -0
  5. data/lib/tr8n/application.rb +14 -96
  6. data/lib/tr8n/base.rb +7 -2
  7. data/lib/tr8n/cache.rb +2 -1
  8. data/lib/tr8n/cache_adapters/cdb.rb +2 -1
  9. data/lib/tr8n/cache_adapters/file.rb +2 -1
  10. data/lib/tr8n/cache_adapters/memcache.rb +2 -1
  11. data/lib/tr8n/cache_adapters/redis.rb +2 -1
  12. data/lib/tr8n/component.rb +2 -1
  13. data/lib/tr8n/config.rb +13 -3
  14. data/lib/tr8n/decorators/base.rb +2 -1
  15. data/lib/tr8n/decorators/default.rb +2 -1
  16. data/lib/tr8n/decorators/html.rb +2 -1
  17. data/lib/tr8n/exception.rb +2 -1
  18. data/lib/tr8n/language.rb +9 -3
  19. data/lib/tr8n/language_case.rb +5 -1
  20. data/lib/tr8n/language_case_rule.rb +2 -1
  21. data/lib/tr8n/language_context.rb +4 -1
  22. data/lib/tr8n/language_context_rule.rb +2 -1
  23. data/lib/tr8n/logger.rb +3 -2
  24. data/lib/tr8n/rules_engine/evaluator.rb +2 -1
  25. data/lib/tr8n/rules_engine/parser.rb +2 -1
  26. data/lib/tr8n/session.rb +21 -7
  27. data/lib/tr8n/source.rb +3 -2
  28. data/lib/tr8n/tokens/data.rb +319 -325
  29. data/lib/tr8n/tokens/data_tokenizer.rb +6 -1
  30. data/lib/tr8n/tokens/decoration_tokenizer.rb +15 -5
  31. data/lib/tr8n/tokens/hidden.rb +2 -1
  32. data/lib/tr8n/tokens/method.rb +4 -3
  33. data/lib/tr8n/tokens/transform.rb +22 -10
  34. data/lib/tr8n/translation.rb +6 -8
  35. data/lib/tr8n/translation_key.rb +11 -9
  36. data/lib/tr8n/translator.rb +2 -1
  37. data/lib/tr8n/utils.rb +6 -1
  38. data/lib/tr8n_core.rb +1 -1
  39. data/lib/tr8n_core/ext/array.rb +1 -1
  40. data/lib/tr8n_core/ext/date.rb +1 -1
  41. data/lib/tr8n_core/ext/fixnum.rb +1 -1
  42. data/lib/tr8n_core/ext/hash.rb +1 -1
  43. data/lib/tr8n_core/ext/string.rb +1 -1
  44. data/lib/tr8n_core/ext/time.rb +1 -1
  45. data/lib/tr8n_core/generators/cache/base.rb +1 -1
  46. data/lib/tr8n_core/generators/cache/cdb.rb +1 -1
  47. data/lib/tr8n_core/generators/cache/file.rb +1 -1
  48. data/lib/tr8n_core/languages/en-US.json +1362 -0
  49. data/lib/tr8n_core/version.rb +2 -2
  50. data/spec/language_case_spec.rb +3 -3
  51. data/spec/tokens/data_spec.rb +38 -84
  52. metadata +5 -3
@@ -1,7 +1,6 @@
1
- #!ruby19
2
- # encoding: utf-8
1
+ # encoding: UTF-8
3
2
  #--
4
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
5
4
  #
6
5
  # Permission is hereby granted, free of charge, to any person obtaining
7
6
  # a copy of this software and associated documentation files (the
@@ -286,6 +285,17 @@ module Tr8n
286
285
  # @default_level
287
286
  #end
288
287
 
288
+ def default_language
289
+ @default_language ||= begin
290
+ file = File.expand_path(File.join(File.dirname(__FILE__), '..', 'tr8n_core', 'languages', "#{Tr8n.config.default_locale}.json"))
291
+ Tr8n::Language.new(JSON.parse(File.read(file)))
292
+ end
293
+ end
294
+
295
+ def default_application
296
+ @default_application ||= Tr8n::Application.new(:host => Tr8n::ApiClient::API_HOST)
297
+ end
298
+
289
299
  #########################################################
290
300
  ## Decorations
291
301
  #########################################################
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -26,7 +27,12 @@ class Tr8n::Language < Tr8n::Base
26
27
  attributes :locale, :name, :english_name, :native_name, :right_to_left, :flag_url
27
28
  has_many :contexts, :cases
28
29
 
29
- def initialize(attrs = {})
30
+ def fetch
31
+ update_attributes(application.api_client.get("language", {:locale => locale}))
32
+ self
33
+ end
34
+
35
+ def update_attributes(attrs)
30
36
  super
31
37
 
32
38
  self.attributes[:contexts] = {}
@@ -52,7 +58,7 @@ class Tr8n::Language < Tr8n::Base
52
58
  contexts.values.detect{|ctx| ctx.applies_to_token?(token_name)}
53
59
  end
54
60
 
55
- def language_case_by_keyword(keyword)
61
+ def case_by_keyword(keyword)
56
62
  cases[keyword]
57
63
  end
58
64
 
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -49,6 +50,9 @@ class Tr8n::LanguageCase < Tr8n::Base
49
50
 
50
51
  def apply(value, object = nil, options = {})
51
52
  value = value.to_s
53
+
54
+ options = options.merge(:skip_decorations => true) if value.index('not_translated')
55
+
52
56
  html_tokens = value.scan(TR8N_HTML_TAGS_REGEX).uniq
53
57
  sanitized_value = value.gsub(TR8N_HTML_TAGS_REGEX, "")
54
58
 
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -78,6 +79,8 @@ class Tr8n::LanguageContext < Tr8n::Base
78
79
  else
79
80
  vars[key] = object.send(method)
80
81
  end
82
+ elsif obj.is_a?(String)
83
+ vars[key] = obj
81
84
  else
82
85
  vars[key] = obj.send(method)
83
86
  end
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -55,7 +56,7 @@ module Tr8n
55
56
 
56
57
  def trace_api_call(path, params)
57
58
  [:client_secret, :access_token].each do |param|
58
- params = params.merge(param => "##filtered##")
59
+ params = params.merge(param => "##filtered##") if params[param]
59
60
  end
60
61
  debug("api: [/#{path}] #{params.inspect}")
61
62
  stack.push(caller)
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -27,20 +28,26 @@ module Tr8n
27
28
  Thread.current[:session] ||= Tr8n::Session.new
28
29
  end
29
30
 
30
- # Acts as a global singleton that holds all Tr8n configuration
31
- # The class can be extended with a different implementation, as long as the interface is supported
32
31
  class Session
33
32
  # Session Attributes - Move to Session
34
33
  attr_accessor :application, :current_user, :current_language, :current_translator,
35
34
  :current_source, :current_component, :block_options
36
35
 
37
- def init
38
- return unless Tr8n.config.application # not configured
36
+ def init(key = nil, secret = nil, host = nil)
37
+ key ||= Tr8n.config.application[:key]
38
+ secret ||= Tr8n.config.application[:secret]
39
+ host ||= Tr8n.config.application[:host]
39
40
 
40
- unless @application
41
- Tr8n::Application.init(Tr8n.config.application[:key], Tr8n.config.application[:secret], Tr8n.config.application[:host])
41
+ Tr8n.cache.reset_version
42
+
43
+ self.application = Tr8n.cache.fetch(Tr8n::Application.cache_key(key)) do
44
+ Tr8n.logger.info("Initializing application...")
45
+ Tr8n::Application.new(:host => host, :key => key, :secret => secret).fetch
42
46
  end
47
+
43
48
  self.current_source = "/tr8n/core"
49
+
50
+ self.application
44
51
  end
45
52
 
46
53
  def reset
@@ -53,6 +60,13 @@ module Tr8n
53
60
  self.block_options= nil
54
61
  end
55
62
 
63
+ def current_language
64
+ @current_language ||= Tr8n.config.default_language
65
+ end
66
+
67
+ def application
68
+ @application ||= Tr8n::Application.new(:host => Tr8n::ApiClient::API_HOST)
69
+ end
56
70
  #########################################################
57
71
  ## Block Options
58
72
  #########################################################
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2014 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -56,7 +57,7 @@ class Tr8n::Source < Tr8n::Base
56
57
  def fetch_translations_for_language(language, options = {})
57
58
  return translation_keys if translation_keys
58
59
 
59
- keys_with_translations = application.get("source/translations",
60
+ keys_with_translations = application.api_client.get("source/translations",
60
61
  {:source => source, :locale => language.locale},
61
62
  {:class => Tr8n::TranslationKey, :attributes => {:application => application}})
62
63
 
@@ -1,5 +1,6 @@
1
+ # encoding: UTF-8
1
2
  #--
2
- # Copyright (c) 2013 Michael Berkovich, tr8nhub.com
3
+ # Copyright (c) 2014 Michael Berkovich, TranslationExchange.com
3
4
  #
4
5
  # Permission is hereby granted, free of charge, to any person obtaining
5
6
  # a copy of this software and associated documentation files (the
@@ -21,390 +22,383 @@
21
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23
  #++
23
24
 
24
- #######################################################################
25
- #
26
- # Data Token Forms:
27
- #
28
- # {count}
29
- # {count:number}
30
- # {user:gender}
31
- # {today:date}
32
- # {user_list:list}
33
- # {long_token_name}
34
- # {user1}
35
- # {user1:user}
36
- # {user1:user::pos}
37
- #
38
- # Data tokens can be associated with any rules through the :dependency
39
- # notation or using the naming convention of the token suffix, defined
40
- # in the tr8n configuration file
41
- #
42
- #######################################################################
43
-
44
- class Tr8n::Tokens::Data < Tr8n::Base
45
- attr_reader :label, :full_name, :short_name, :case_keys, :context_keys
46
-
47
- def self.expression
48
- /(\{[^_:][\w]*(:[\w]+)*(::[\w]+)*\})/
49
- end
50
-
51
- def self.parse(label, opts = {})
52
- tokens = []
53
- label.scan(expression).uniq.each do |token_array|
54
- tokens << self.new(label, token_array.first)
55
- end
56
- tokens
57
- end
58
-
59
- def initialize(label, token)
60
- @label = label
61
- @full_name = token
62
- parse_elements
63
- end
25
+ module Tr8n
26
+ module Tokens
27
+ class Data
64
28
 
65
- def parse_elements
66
- name_without_parens = self.full_name[1..-2]
67
- name_without_case_keys = name_without_parens.split('::').first.strip
29
+ attr_reader :label, :full_name, :short_name, :case_keys, :context_keys
68
30
 
69
- @short_name = name_without_parens.split(':').first.strip
70
- @case_keys = name_without_parens.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
71
- @context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
72
- end
73
-
74
- def name(opts = {})
75
- val = short_name
76
- val = "#{val}:#{context_keys.join(':')}" if opts[:context_keys] and context_keys.any?
77
- val = "#{val}::#{case_keys.join('::')}" if opts[:case_keys] and case_keys.any?
78
- val = "{#{val}}" if opts[:parens]
79
- val
80
- end
31
+ def self.expression
32
+ /(\{[^_:][\w]*(:[\w]+)*(::[\w]+)*\})/
33
+ end
81
34
 
82
- def key
83
- short_name.to_sym
84
- end
35
+ def self.parse(label, opts = {})
36
+ tokens = []
37
+ label.scan(expression).uniq.each do |token_array|
38
+ tokens << self.new(label, token_array.first)
39
+ end
40
+ tokens
41
+ end
85
42
 
86
- # used by the translator submit dialog
87
- def name_for_case_keys(keys)
88
- keys = [keys] unless keys.is_a?(Array)
89
- "#{name}::#{keys.join('::')}"
90
- end
43
+ def initialize(label, token)
44
+ @label = label
45
+ @full_name = token
46
+ parse_elements
47
+ end
91
48
 
92
- def sanitize(object, value, options, language)
93
- value = "#{value.to_s}" unless value.is_a?(String)
49
+ def parse_elements
50
+ name_without_parens = @full_name[1..-2]
51
+ name_without_case_keys = name_without_parens.split('::').first.strip
94
52
 
95
- unless Tr8n.session.block_options[:skip_html_escaping]
96
- if options[:sanitize_values]
97
- value = ERB::Util.html_escape(value)
53
+ @short_name = name_without_parens.split(':').first.strip
54
+ @case_keys = name_without_parens.scan(/(::\w+)/).flatten.uniq.collect{|c| c.gsub('::', '')}
55
+ @context_keys = name_without_case_keys.scan(/(:\w+)/).flatten.uniq.collect{|c| c.gsub(':', '')}
98
56
  end
99
- end
100
57
 
101
- if Tr8n.session.application and not Tr8n.session.application.feature_enabled?(:language_cases)
102
- return value
103
- end
58
+ def name(opts = {})
59
+ val = short_name
60
+ val = "#{val}:#{context_keys.join(':')}" if opts[:context_keys] and context_keys.any?
61
+ val = "#{val}::#{case_keys.join('::')}" if opts[:case_keys] and case_keys.any?
62
+ val = "{#{val}}" if opts[:parens]
63
+ val
64
+ end
104
65
 
105
- case_keys.each do |key|
106
- value = apply_case(key, value, object, options, language)
107
- end
66
+ def key
67
+ short_name.to_sym
68
+ end
108
69
 
109
- value
110
- end
70
+ # used by the translator submit dialog
71
+ def name_for_case_keys(keys)
72
+ keys = [keys] unless keys.is_a?(Array)
73
+ "#{name}::#{keys.join('::')}"
74
+ end
111
75
 
112
- def context_for_language(language, opts = {})
113
- if context_keys.any?
114
- ctx = language.context_by_keyword(context_keys.first)
115
- else
116
- ctx = language.context_by_token_name(short_name)
117
- end
76
+ def context_for_language(language, opts = {})
77
+ if context_keys.any?
78
+ ctx = language.context_by_keyword(context_keys.first)
79
+ else
80
+ ctx = language.context_by_token_name(short_name)
81
+ end
118
82
 
119
- unless opts[:silent]
120
- raise Tr8n::Exception.new("Unknown context for a token: #{full_name} in #{language.locale}") unless ctx
121
- end
83
+ unless opts[:silent]
84
+ raise Tr8n::Exception.new("Unknown context for a token: #{full_name} in #{language.locale}") unless ctx
85
+ end
122
86
 
123
- ctx
124
- end
87
+ ctx
88
+ end
125
89
 
126
- ##############################################################################
127
- #
128
- # chooses the appropriate case for the token value. case is identified with ::
129
- #
130
- # examples:
131
- #
132
- # tr("Hello {user::nom}", "", :user => current_user)
133
- # tr("{actor} gave {target::dat} a present", "", :actor => user1, :target => user2)
134
- # tr("This is {user::pos} toy", "", :user => current_user)
135
- #
136
- ##############################################################################
137
- def apply_case(key, value, object, options, language)
138
- lcase = language.language_case_by_keyword(key)
139
- return value unless lcase
140
- lcase.apply(value, object, options)
141
- end
90
+ ##############################################################################
91
+ #
92
+ # returns token object from tokens param
93
+ #
94
+ ##############################################################################
95
+
96
+ def self.token_object(token_values, token_name)
97
+ return nil if token_values.nil?
98
+ token_object = Tr8n::Utils.hash_value(token_values, token_name)
99
+ return token_object.first if token_object.is_a?(Array)
100
+ if token_object.is_a?(Hash)
101
+ object = Tr8n::Utils.hash_value(token_object, :object)
102
+ return object if object
103
+ end
104
+ token_object
105
+ end
142
106
 
143
- def decoration?
144
- false
145
- end
107
+ ##############################################################################
108
+ #
109
+ # tr("Hello {user_list}!", "", {:user_list => [[user1, user2, user3], :name]}}
110
+ #
111
+ # first element is an array, the rest of the elements are similar to the
112
+ # regular tokens lambda, symbol, string, with parameters that follow
113
+ #
114
+ # if you want to pass options, then make the second parameter an array as well
115
+ #
116
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], :name]})
117
+ #
118
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], lambda{|user| user.name}]})
119
+ #
120
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], {:attribute => :name})
121
+ #
122
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], {:attribute => :name, :value => "<strong>{$0}</strong>"})
123
+ #
124
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], "<strong>{$0}</strong>")
125
+ #
126
+ # tr("{users} joined the site", {:users => [[user1, user2, user3], :name, {
127
+ # :limit => 4,
128
+ # :separator => ', ',
129
+ # :joiner => 'and',
130
+ # :remainder => lambda{|elements| tr("#{count||other}", :count => elements.size)},
131
+ # :expandable => true,
132
+ # :collapsable => true
133
+ # })
134
+ #
135
+ #
136
+ ##############################################################################
137
+ def token_value_from_array(params, language, options)
138
+ list_options = {
139
+ :description => "List joiner",
140
+ :limit => 4,
141
+ :separator => ", ",
142
+ :joiner => 'and',
143
+ :less => '{laquo} less',
144
+ :expandable => true,
145
+ :collapsable => true
146
+ }
147
+
148
+ objects = params[0]
149
+ method = params[1]
150
+ list_options.merge!(params[2]) if params.size > 2
151
+ list_options[:expandable] = false if options[:skip_decorations]
152
+
153
+ values = objects.collect do |obj|
154
+ if method.is_a?(String)
155
+ method.gsub("{$0}", sanitize(obj.to_s, obj, language, options.merge(:safe => false)))
156
+ elsif method.is_a?(Symbol)
157
+ if obj.is_a?(Hash)
158
+ value = Tr8n::Utils.hash_value(obj, method)
159
+ else
160
+ value = obj.send(method)
161
+ end
162
+ sanitize(value, obj, language, options.merge(:safe => false))
163
+ elsif method.is_a?(Hash)
164
+ attr = Tr8n::Utils.hash_value(method, :attribute) || Tr8n::Utils.hash_value(method, :property)
165
+ if obj.is_a?(Hash)
166
+ value = Tr8n::Utils.hash_value(obj, attr)
167
+ else
168
+ value = obj.send(method)
169
+ end
170
+
171
+ hash_value = Tr8n::Utils.hash_value(method, :value)
172
+ if hash_value
173
+ hash_value.gsub("{$0}", sanitize(value, obj, language, options.merge(:safe => false)))
174
+ else
175
+ sanitize(value, obj, language, options.merge(:safe => false))
176
+ end
177
+ elsif method.is_a?(Proc)
178
+ sanitize(method.call(obj), obj, language, options.merge(:safe => true))
179
+ end
180
+ end
146
181
 
147
- ##############################################################################
148
- #
149
- # gets the value based on various evaluation methods
150
- #
151
- # examples:
152
- #
153
- # tr("Hello {user}", "", {:user => [current_user, current_user.name]}}
154
- # tr("Hello {user}", "", {:user => [current_user, "{$0} {$1}", "param1"]}}
155
- # tr("Hello {user}", "", {:user => [current_user, :name]}}
156
- # tr("Hello {user}", "", {:user => [current_user, :method_name, "param1"]}}
157
- # tr("Hello {user}", "", {:user => [current_user, lambda{|user| user.name}]}}
158
- # tr("Hello {user}", "", {:user => [current_user, lambda{|user, param1| user.name}, "param1"]}}
159
- #
160
- ##############################################################################
161
- def evaluate_token_method_array(object, method_array, options, language)
162
- # if single object in the array return string value of the object
163
- if method_array.size == 1
164
- return sanitize(object, object.to_s, options, language)
165
- end
182
+ return values.first if objects.size == 1
183
+ return values.join(list_options[:separator]) unless list_options[:joiner]
166
184
 
167
- # second params identifies the method to be used with the object
168
- method = method_array[1]
169
- params = method_array[2..-1]
170
- params_with_object = [object] + params
171
-
172
- # if the second param is a string, substitute all of the numeric params,
173
- # with the original object and all the following params
174
- if method.is_a?(String)
175
- parametrized_value = method.clone
176
- if parametrized_value.index("{$")
177
- params_with_object.each_with_index do |val, i|
178
- parametrized_value.gsub!("{$#{i}}", sanitize(object, val, options.merge(:skip_decorations => true), language))
185
+ if values.size <= list_options[:limit]
186
+ return "#{values[0..-2].join(list_options[:separator])} #{language.translate(list_options[:joiner], list_options[:description], {}, options)} #{values.last}"
179
187
  end
180
- end
181
- return sanitize(object, parametrized_value, options, language)
182
- end
183
188
 
184
- # if second param is symbol, invoke the method on the object with the remaining values
185
- if method.is_a?(Symbol)
186
- return sanitize(object, object.send(method, *params), options.merge(:sanitize_values => true), language)
187
- end
189
+ display_ary = values[0..(list_options[:limit]-1)]
190
+ remaining_ary = values[list_options[:limit]..-1]
191
+ result = "#{display_ary.join(list_options[:separator])}"
192
+
193
+ unless list_options[:expandable]
194
+ result << " " << language.translate(list_options[:joiner], list_options[:description], {}, options) << " "
195
+ if list_options[:remainder] and list_options[:remainder].is_a?(Proc)
196
+ result << list_options[:remainder].call(remaining_ary)
197
+ else
198
+ result << language.translate("{count||other}", list_options[:description], {:count => remaining_ary.size}, options)
199
+ end
200
+ return result
201
+ end
188
202
 
189
- # if second param is lambda, call lambda with the remaining values
190
- if method.is_a?(Proc)
191
- return sanitize(object, method.call(*params_with_object), options, language)
192
- end
203
+ joiner = language.translate(list_options[:joiner], list_options[:description], {}, options)
193
204
 
194
- if method.is_a?(Hash)
195
- value = hash_value(method, :value)
196
- attr = hash_value(method, :attribute)
205
+ uniq_id = Tr8n::TranslationKey.generate_key(label, values.join(","))
206
+ result << "<span id=\"tr8n_other_link_#{uniq_id}\">"
207
+ result << " #{joiner} "
197
208
 
198
- unless attr.nil?
199
- if object.is_a?(Hash)
200
- value = hash_value(object, attr)
209
+ result << "<a href='#' onClick=\"Tr8n.Utils.Effects.hide('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.show('tr8n_other_elements_#{uniq_id}'); return false;\">"
210
+ if list_options[:remainder] and list_options[:remainder].is_a?(Proc)
211
+ result << list_options[:remainder].call(remaining_ary)
201
212
  else
202
- value = object.send(attr)
213
+ result << language.translate("{count||other}", list_options[:description], {:count => remaining_ary.size}, options)
214
+ end
215
+ result << "</a></span>"
216
+
217
+ result << "<span id=\"tr8n_other_elements_#{uniq_id}\" style='display:none'>"
218
+ result << list_options[:separator] << " "
219
+ result << remaining_ary[0..-2].join(list_options[:separator])
220
+ result << " #{joiner} "
221
+ result << remaining_ary.last
222
+
223
+ if list_options[:collapsable]
224
+ result << "<a href='#' style='font-size:smaller;white-space:nowrap' onClick=\"Tr8n.Utils.Effects.show('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.hide('tr8n_other_elements_#{uniq_id}'); return false;\"> "
225
+ result << language.translate(list_options[:less], list_options[:description], {}, options)
226
+ result << "</a>"
203
227
  end
204
- end
205
228
 
206
- if value.nil?
207
- return raise Tr8n::Exception.new("Hash object is missing a value or attribute key for a token: #{full_name}")
229
+ result << "</span>"
208
230
  end
209
231
 
210
- return sanitize(object,value, options, language)
211
- end
232
+ ##############################################################################
233
+ #
234
+ # gets the value based on various evaluation methods
235
+ #
236
+ # examples:
237
+ #
238
+ # tr("Hello {user}", {:user => [current_user, current_user.name]}}
239
+ # tr("Hello {user}", {:user => [current_user, :name]}}
240
+ #
241
+ # tr("Hello {user}", {:user => [{:name => "Michael", :gender => :male}, current_user.name]}}
242
+ # tr("Hello {user}", {:user => [{:name => "Michael", :gender => :male}, :name]}}
243
+ #
244
+ ##############################################################################
245
+
246
+ def token_value_from_param_array(array, language, options)
247
+ # if you provided an array, it better have some values
248
+ if array.size < 2
249
+ Tr8n.logger.error("Invalid value for array token #{full_name} in #{label}")
250
+ return full_name
251
+ end
212
252
 
213
- raise Tr8n::Exception.new("Invalid array second token value: #{full_name} in #{label}")
214
- end
253
+ # if the first value of an array is an array handle it here
254
+ if array[0].is_a?(Array)
255
+ return token_value_from_array(array, language, options)
256
+ end
215
257
 
216
- def self.token_object(token_values, token_name)
217
- return nil if token_values.nil?
218
- object = token_values[token_name.to_s] || token_values[token_name.to_sym]
258
+ if array[1].is_a?(String)
259
+ return sanitize(array[1], array[0], language, options.merge(:safe => true))
260
+ end
219
261
 
220
- # for arrays
221
- return object.first if object.is_a?(Array)
262
+ if array[1].is_a?(Hash)
263
+ if array[1].is_a?(Symbol)
264
+ return sanitize(Tr8n::Utils.hash_value(array[0], array[1]), array[0], language, options.merge(:safe => false))
265
+ end
222
266
 
223
- # hash maybe nested {:object => {}, :attribute => ""}
224
- if object.is_a?(Hash)
225
- sub_object = object[:object] || object['object']
226
- return sub_object if sub_object
227
- end
267
+ Tr8n.logger.error("Invalid value for array token #{full_name} in #{label}")
268
+ return full_name
269
+ end
228
270
 
229
- object
230
- end
271
+ # if second param is symbol, invoke the method on the object with the remaining values
272
+ if array[1].is_a?(Symbol)
273
+ return sanitize(array[0].send(array[1]), array[0], language, options.merge(:safe => false))
274
+ end
231
275
 
232
- ##############################################################################
233
- #
234
- # tr("Hello {user_list}!", "", {:user_list => [[user1, user2, user3], :name]}}
235
- #
236
- # first element is an array, the rest of the elements are similar to the
237
- # regular tokens lambda, symbol, string, with parameters that follow
238
- #
239
- # if you want to pass options, then make the second parameter an array as well
240
- # tr("{user_list} joined the site", "",
241
- # {:user_list => [[user1, user2, user3],
242
- # [:name], # this can be any of the value methods
243
- # { :expandable => true,
244
- # :to_sentence => true,
245
- # :limit => 4,
246
- # :separator => ',',
247
- # :andor => 'and',
248
- # :translate_items => false,
249
- # :minimizable => true
250
- # }
251
- # ]
252
- # ]})
253
- #
254
- # acceptable params: expandable,
255
- # to_sentence,
256
- # limit,
257
- # andor,
258
- # more_label,
259
- # less_label,
260
- # separator,
261
- # translate_items,
262
- # minimizable
263
- #
264
- ##############################################################################
265
- def token_array_value(token_value, options, language)
266
- objects = token_value.first
267
-
268
- objects = objects.collect do |obj|
269
- if token_value[1].is_a?(Array)
270
- evaluate_token_method_array(obj, [obj] + token_value[1], options, language)
271
- else
272
- evaluate_token_method_array(obj, token_value, options, language)
276
+ Tr8n.logger.error("Invalid value for array token #{full_name} in #{label}")
277
+ full_name
273
278
  end
274
- end
275
-
276
- list_options = {
277
- :translate_items => false,
278
- :expandable => true,
279
- :minimizable => true,
280
- :to_sentence => true,
281
- :limit => 4,
282
- :separator => ", ",
283
- :andor => 'and'
284
- }
285
-
286
- if token_value[1].is_a?(Array) and token_value.size == 3
287
- list_options.merge!(token_value.last)
288
- end
289
279
 
290
- objects = objects.collect{|obj| obj.translate("List element", {}, options)} if list_options[:translate_items]
280
+ ##############################################################################
281
+ #
282
+ # examples:
283
+ #
284
+ # tr("Hello {user}", {:user => {:value => "Michael", :gender => :male}}}
285
+ #
286
+ # tr("Hello {user}", {:user => {:object => {:gender => :male}, :value => "Michael"}}}
287
+ # tr("Hello {user}", {:user => {:object => {:name => "Michael", :gender => :male}, :property => :name}}}
288
+ # tr("Hello {user}", {:user => {:object => {:name => "Michael", :gender => :male}, :attribute => :name}}}
289
+ #
290
+ # tr("Hello {user}", {:user => {:object => user, :value => "Michael"}}}
291
+ # tr("Hello {user}", {:user => {:object => user, :property => :name}}}
292
+ # tr("Hello {user}", {:user => {:object => user, :attribute => :name}}}
293
+ #
294
+ ##############################################################################
295
+
296
+ def token_value_from_param_hash(hash, language, options)
297
+ value = Tr8n::Utils.hash_value(hash, :value)
298
+ object = Tr8n::Utils.hash_value(hash, :object)
299
+
300
+ unless value.nil?
301
+ return sanitize(value, object || hash, language, options.merge(:safe => true))
302
+ end
291
303
 
292
- # if there is only one element in the array, use it and get out
293
- return objects.first if objects.size == 1
304
+ if object.nil?
305
+ Tr8n.logger.error("Missing value for hash token #{full_name} in #{label}")
306
+ return full_name
307
+ end
294
308
 
295
- list_options[:expandable] = false if options[:skip_decorations]
309
+ attr = Tr8n::Utils.hash_value(hash, :attribute) || Tr8n::Utils.hash_value(hash, :property)
296
310
 
297
- return objects.join(list_options[:separator]) unless list_options[:to_sentence]
311
+ if object.is_a?(Hash)
312
+ unless attr.nil?
313
+ return sanitize(Tr8n::Utils.hash_value(object, attr), object, language, options.merge(:safe => false))
314
+ end
298
315
 
299
- if objects.size <= list_options[:limit]
300
- return "#{objects[0..-2].join(list_options[:separator])} #{list_options[:andor].translate("", {}, options)} #{objects.last}"
301
- end
316
+ Tr8n.logger.error("Missing value for hash token #{full_name} in #{label}")
317
+ return full_name
318
+ end
302
319
 
303
- display_ary = objects[0..(list_options[:limit]-1)]
304
- remaining_ary = objects[list_options[:limit]..-1]
305
- result = "#{display_ary.join(list_options[:separator])}"
320
+ sanitize(object.send(attr), object, language, options.merge(:safe => false))
321
+ end
306
322
 
307
- unless list_options[:expandable]
308
- result << " " << list_options[:andor].translate("", {}, options) << " "
309
- result << "{num|| other}".translate("List elements joiner", {:num => remaining_ary.size}, options)
310
- return result
311
- end
323
+ # evaluate all possible methods for the token value and return sanitized result
324
+ def token_value(object, language, options = {})
325
+ return token_value_from_param_array(object, language, options) if object.is_a?(Array)
326
+ return token_value_from_param_hash(object, language, options) if object.is_a?(Hash)
327
+ sanitize(object, object, language, options)
328
+ end
312
329
 
313
- uniq_id = Tr8n::TranslationKey.generate_key(label, objects.join(","))
314
- result << "<span id=\"tr8n_other_link_#{uniq_id}\">" << " " << list_options[:andor].translate("", {}, options) << " "
315
- result << "<a href='#' onClick=\"Tr8n.Utils.Effects.hide('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.show('tr8n_other_elements_#{uniq_id}'); return false;\">"
316
- result << (list_options[:more_label] ? list_options[:more_label] : "{num|| other}".translate("List elements joiner", {:num => remaining_ary.size}, options))
317
- result << "</a></span>"
318
- result << "<span id=\"tr8n_other_elements_#{uniq_id}\" style='display:none'>" << list_options[:separator]
319
- result << "#{remaining_ary[0..-2].join(list_options[:separator])} #{list_options[:andor].translate("", {}, options)} #{remaining_ary.last}"
320
-
321
- if list_options[:minimizable]
322
- result << "<a href='#' style='font-size:smaller;white-space:nowrap' onClick=\"Tr8n.Utils.Effects.show('tr8n_other_link_#{uniq_id}'); Tr8n.Utils.Effects.hide('tr8n_other_elements_#{uniq_id}'); return false;\"> "
323
- result << (list_options[:less_label] ? list_options[:less_label] : "{laquo} less".translate("List elements joiner", {}, options))
324
- result << "</a>"
325
- end
330
+ def sanitize(value, object, language, options)
331
+ value = value.to_s
326
332
 
327
- result << "</span>"
328
- end
333
+ unless Tr8n.session.block_options[:skip_html_escaping]
334
+ if options[:safe] == false
335
+ value = ERB::Util.html_escape(value)
336
+ end
337
+ end
329
338
 
330
- # evaluate all possible methods for the token value and return sanitized result
331
- def token_value(object, options, language)
332
- # token is an array
333
- if object.is_a?(Array)
334
- # if you provided an array, it better have some values
335
- if object.empty?
336
- return raise Tr8n::Exception.new("Invalid array value for a token: #{full_name}")
339
+ return value unless language_cases_enabled?
340
+ apply_language_cases(value, object, language, options)
337
341
  end
338
342
 
339
- # if the first value of an array is an array handle it here
340
- if object.first.kind_of?(Enumerable)
341
- return token_array_value(object, options, language)
343
+ def language_cases_enabled?
344
+ Tr8n.session.application and Tr8n.session.application.feature_enabled?(:language_cases)
342
345
  end
343
346
 
344
- # if the first item in the array is an object, process it
345
- return evaluate_token_method_array(object.first, object, options, language)
346
- end
347
-
348
- if object.is_a?(Hash)
349
- # if object is a hash, it must be of a form: {:object => {}, :value => "", :attribute => ""}
350
- # either value can be passed, or the attribute. attribute will be used first
351
- if hash_value(object, :object).nil?
352
- return raise Tr8n::Exception.new("Hash token is missing an object key for a token: #{full_name}")
347
+ ##############################################################################
348
+ #
349
+ # chooses the appropriate case for the token value. case is identified with ::
350
+ #
351
+ # examples:
352
+ #
353
+ # tr("Hello {user::nom}", "", :user => current_user)
354
+ # tr("{actor} gave {target::dat} a present", "", :actor => user1, :target => user2)
355
+ # tr("This is {user::pos} toy", "", :user => current_user)
356
+ #
357
+ ##############################################################################
358
+ def apply_case(key, value, object, language, options)
359
+ lcase = language.case_by_keyword(key)
360
+ return value unless lcase
361
+ lcase.apply(value, object, options)
353
362
  end
354
363
 
355
- obj = hash_value(object, :object)
356
- value = hash_value(object, :value)
357
- attr = hash_value(object, :attribute)
358
-
359
- unless attr.nil?
360
- if obj.is_a?(Hash)
361
- value = hash_value(obj, attr)
362
- else
363
- value = obj.send(attr)
364
+ def apply_language_cases(value, object, language, options)
365
+ case_keys.each do |key|
366
+ value = apply_case(key, value, object, language, options)
364
367
  end
365
- end
366
368
 
367
- if value.nil?
368
- return raise Tr8n::Exception.new("Hash object is missing a value or attribute key for a token: #{full_name}")
369
+ value
369
370
  end
370
371
 
371
- return sanitize(obj, value.to_s, options, language)
372
- end
373
-
374
- # simple token
375
- sanitize(object, object.to_s, options, language)
376
- end
372
+ def substitute(label, context, language, options = {})
373
+ # get the object from the values
374
+ object = Tr8n::Utils.hash_value(context, key)
377
375
 
378
- def allowed_in_translation?
379
- true
380
- end
376
+ # see if the token is a default html token
377
+ object = Tr8n.config.default_token_value(key) if object.nil?
381
378
 
382
- def implied?
383
- false
384
- end
385
-
386
- def substitute(label, context, language, options = {})
387
- # get the object from the values
388
- object = hash_value(context, key, :whole => true)
389
-
390
- # see if the token is a default html token
391
- object = Tr8n.config.default_token_value(key) if object.nil?
379
+ if object.nil? and not context.key?(key)
380
+ Tr8n.logger.error("Missing value for #{full_name} in #{label}")
381
+ return label
382
+ end
392
383
 
393
- #if object.nil?
394
- # raise Tr8n::Exception.new("Missing value for a token: #{full_name}")
395
- #end
384
+ if object.nil? and not Tr8n::Config.allow_nil_token_values?
385
+ Tr8n.logger.error("Token value is nil for #{full_name} in #{label}")
386
+ return label
387
+ end
396
388
 
397
- object = object.to_s if object.nil?
389
+ return label.gsub(full_name, "") if object.nil?
398
390
 
399
- value = token_value(object, options, language)
400
- label.gsub(full_name, value)
401
- end
391
+ value = token_value(object, language, options)
392
+ label.gsub(full_name, value)
393
+ end
402
394
 
403
- def sanitized_name
404
- name(:parens => true)
405
- end
395
+ def sanitized_name
396
+ name(:parens => true)
397
+ end
406
398
 
407
- def to_s
408
- full_name
399
+ def to_s
400
+ full_name
401
+ end
402
+ end
409
403
  end
410
404
  end