tr8n_core 4.0.4 → 4.0.5

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