tml 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +243 -0
  4. data/Rakefile +9 -0
  5. data/lib/tml.rb +56 -0
  6. data/lib/tml/api/client.rb +206 -0
  7. data/lib/tml/api/post_office.rb +71 -0
  8. data/lib/tml/application.rb +254 -0
  9. data/lib/tml/base.rb +116 -0
  10. data/lib/tml/cache.rb +143 -0
  11. data/lib/tml/cache_adapters/file.rb +89 -0
  12. data/lib/tml/cache_adapters/memcache.rb +104 -0
  13. data/lib/tml/cache_adapters/memory.rb +85 -0
  14. data/lib/tml/cache_adapters/redis.rb +108 -0
  15. data/lib/tml/config.rb +410 -0
  16. data/lib/tml/decorators/base.rb +52 -0
  17. data/lib/tml/decorators/default.rb +43 -0
  18. data/lib/tml/decorators/html.rb +102 -0
  19. data/lib/tml/exception.rb +35 -0
  20. data/lib/tml/ext/array.rb +86 -0
  21. data/lib/tml/ext/date.rb +99 -0
  22. data/lib/tml/ext/fixnum.rb +47 -0
  23. data/lib/tml/ext/hash.rb +99 -0
  24. data/lib/tml/ext/string.rb +56 -0
  25. data/lib/tml/ext/time.rb +89 -0
  26. data/lib/tml/generators/cache/base.rb +117 -0
  27. data/lib/tml/generators/cache/file.rb +159 -0
  28. data/lib/tml/language.rb +175 -0
  29. data/lib/tml/language_case.rb +105 -0
  30. data/lib/tml/language_case_rule.rb +76 -0
  31. data/lib/tml/language_context.rb +117 -0
  32. data/lib/tml/language_context_rule.rb +56 -0
  33. data/lib/tml/languages/en.json +1363 -0
  34. data/lib/tml/logger.rb +109 -0
  35. data/lib/tml/rules_engine/evaluator.rb +162 -0
  36. data/lib/tml/rules_engine/parser.rb +65 -0
  37. data/lib/tml/session.rb +199 -0
  38. data/lib/tml/source.rb +106 -0
  39. data/lib/tml/tokenizers/data.rb +96 -0
  40. data/lib/tml/tokenizers/decoration.rb +204 -0
  41. data/lib/tml/tokenizers/dom.rb +346 -0
  42. data/lib/tml/tokens/data.rb +403 -0
  43. data/lib/tml/tokens/method.rb +61 -0
  44. data/lib/tml/tokens/transform.rb +223 -0
  45. data/lib/tml/translation.rb +67 -0
  46. data/lib/tml/translation_key.rb +178 -0
  47. data/lib/tml/translator.rb +47 -0
  48. data/lib/tml/utils.rb +130 -0
  49. data/lib/tml/version.rb +34 -0
  50. metadata +121 -0
@@ -0,0 +1,175 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ class Tml::Language < Tml::Base
34
+ belongs_to :application
35
+ attributes :locale, :name, :english_name, :native_name, :right_to_left, :flag_url
36
+ has_many :contexts, :cases
37
+
38
+ def self.cache_key(locale)
39
+ File.join(locale, 'language')
40
+ end
41
+
42
+ def fetch
43
+ update_attributes(application.api_client.get("language/#{locale}", {}, {:cache_key => self.class.cache_key(locale)}))
44
+ rescue Tml::Exception => ex
45
+ Tml.logger.error("Failed to load language: #{ex}")
46
+ self
47
+ end
48
+
49
+ def update_attributes(attrs)
50
+ super
51
+
52
+ self.attributes[:contexts] = {}
53
+ if hash_value(attrs, :contexts)
54
+ hash_value(attrs, :contexts).each do |key, context|
55
+ self.attributes[:contexts][key] = Tml::LanguageContext.new(context.merge(:keyword => key, :language => self))
56
+ end
57
+ end
58
+
59
+ self.attributes[:cases] = {}
60
+ if hash_value(attrs, :cases)
61
+ hash_value(attrs, :cases).each do |key, lcase|
62
+ self.attributes[:cases][key] = Tml::LanguageCase.new(lcase.merge(:keyword => key, :language => self))
63
+ end
64
+ end
65
+ end
66
+
67
+ def context_by_keyword(keyword)
68
+ hash_value(contexts, keyword)
69
+ end
70
+
71
+ def context_by_token_name(token_name)
72
+ contexts.values.detect{|ctx| ctx.applies_to_token?(token_name)}
73
+ end
74
+
75
+ def case_by_keyword(keyword)
76
+ cases[keyword]
77
+ end
78
+
79
+ def has_definition?
80
+ contexts.any?
81
+ end
82
+
83
+ def default?
84
+ return true unless application
85
+ application.default_locale == locale
86
+ end
87
+
88
+ def dir
89
+ right_to_left? ? 'rtl' : 'ltr'
90
+ end
91
+
92
+ def align(dest)
93
+ return dest unless right_to_left?
94
+ dest.to_s == 'left' ? 'right' : 'left'
95
+ end
96
+
97
+ def full_name
98
+ return english_name if english_name == native_name
99
+ "#{english_name} - #{native_name}"
100
+ end
101
+
102
+ def current_source(options)
103
+ (options[:source] || Tml.session.block_options[:source] || Tml.session.current_source || 'undefined').to_s
104
+ end
105
+
106
+ #######################################################################################################
107
+ # Translation Methods
108
+ #
109
+ # Note - when inline translation mode is enable, cache will not be used and translators will
110
+ # always hit the live service to get the most recent translations
111
+ #
112
+ # Some cache adapters cache by source, others by key. Some are read-only, some are built on the fly.
113
+ #
114
+ # There are three ways to call the tr method
115
+ #
116
+ # tr(label, description = "", tokens = {}, options = {})
117
+ # or
118
+ # tr(label, tokens = {}, options = {})
119
+ # or
120
+ # tr(:label => label, :description => "", :tokens => {}, :options => {})
121
+ ########################################################################################################
122
+
123
+ def translate(label, description = nil, tokens = {}, options = {})
124
+ params = Tml::Utils.normalize_tr_params(label, description, tokens, options)
125
+ return params[:label] if params[:label].tml_translated?
126
+
127
+ translation_key = Tml::TranslationKey.new({
128
+ :application => application,
129
+ :label => params[:label],
130
+ :description => params[:description],
131
+ :locale => hash_value(params[:options], :locale) || hash_value(Tml.session.block_options, :locale) || Tml.config.default_locale,
132
+ :level => hash_value(params[:options], :level) || hash_value(Tml.session.block_options, :level) || Tml.config.default_level,
133
+ :translations => []
134
+ })
135
+
136
+ #Tml.logger.info("Translating " + params[:label] + " from: " + translation_key.locale.inspect + " to " + locale.inspect)
137
+
138
+ params[:tokens] ||= {}
139
+ params[:tokens][:viewing_user] ||= Tml.session.current_user
140
+
141
+ if Tml.config.disabled? or self.locale == translation_key.locale
142
+ return translation_key.substitute_tokens(
143
+ params[:label],
144
+ params[:tokens],
145
+ self,
146
+ params[:options]
147
+ ).tml_translated
148
+ end
149
+
150
+ cached_translations = application.cached_translations(locale, translation_key.key)
151
+ if cached_translations
152
+ translation_key.set_translations(locale, cached_translations)
153
+ return translation_key.translate(self, params[:tokens], params[:options]).tml_translated
154
+ end
155
+
156
+ source_key = current_source(options)
157
+
158
+ if Tml.cache.segmented? or Tml.session.inline_mode?
159
+ source = application.source(source_key, locale)
160
+ cached_translations = source.cached_translations(locale, translation_key.key)
161
+ else
162
+ cached_translations = application.fetch_translations(locale)[translation_key.key]
163
+ end
164
+
165
+ if cached_translations
166
+ translation_key.set_translations(locale, cached_translations)
167
+ else
168
+ application.register_missing_key(source_key, translation_key)
169
+ end
170
+
171
+ translation_key.translate(self, params[:tokens], params[:options]).tml_translated
172
+ end
173
+ alias :tr :translate
174
+
175
+ end
@@ -0,0 +1,105 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ class Tml::LanguageCase < Tml::Base
34
+ belongs_to :language
35
+ attributes :id, :keyword, :latin_name, :native_name, :description, :application
36
+ has_many :rules
37
+
38
+ TR8N_HTML_TAGS_REGEX = /<\/?[^>]*>/
39
+
40
+ def initialize(attrs = {})
41
+ super
42
+ self.attributes[:rules] = []
43
+ if hash_value(attrs, :rules)
44
+ self.attributes[:rules] = hash_value(attrs, :rules).collect{ |rule| Tml::LanguageCaseRule.new(rule.merge(:language_case => self)) }
45
+ end
46
+ end
47
+
48
+ def find_matching_rule(value, object = nil)
49
+ rules.each do |rule|
50
+ return rule if rule.evaluate(value, object)
51
+ end
52
+ nil
53
+ end
54
+
55
+ #######################################################################################################
56
+ ## Evaluation Methods
57
+ #######################################################################################################
58
+
59
+ def apply(value, object = nil, options = {})
60
+ value = value.to_s
61
+
62
+ decorator = Tml::Decorators::Base.decorator
63
+
64
+ options = options.merge(:skip_decorations => true) if value.index('not_translated')
65
+
66
+ html_tokens = value.scan(TR8N_HTML_TAGS_REGEX).uniq
67
+ sanitized_value = value.gsub(TR8N_HTML_TAGS_REGEX, '')
68
+
69
+ if application.to_s == 'phrase'
70
+ words = [sanitized_value]
71
+ else
72
+ words = sanitized_value.split(/[\s\/\\]/).uniq
73
+ end
74
+
75
+ # replace html tokens with temporary placeholders {$h1}
76
+ html_tokens.each_with_index do |html_token, index|
77
+ value = value.gsub(html_token, "{$h#{index}}")
78
+ end
79
+
80
+ # replace words with temporary placeholders {$w1}
81
+ words.each_with_index do |word, index|
82
+ value = value.gsub(word, "{$w#{index}}")
83
+ end
84
+
85
+ transformed_words = []
86
+ words.each do |word|
87
+ case_rule = find_matching_rule(word, object)
88
+ case_value = case_rule ? case_rule.apply(word) : word
89
+ transformed_words << decorator.decorate_language_case(self, case_rule, word, case_value, options)
90
+ end
91
+
92
+ # replace back the temporary placeholders with the html tokens
93
+ transformed_words.each_with_index do |word, index|
94
+ value = value.gsub("{$w#{index}}", word)
95
+ end
96
+
97
+ # replace back the temporary placeholders with the html tokens
98
+ html_tokens.each_with_index do |html_token, index|
99
+ value = value.gsub("{$h#{index}}", html_token)
100
+ end
101
+
102
+ value
103
+ end
104
+
105
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ class Tml::LanguageCaseRule < Tml::Base
34
+ belongs_to :language_case
35
+ attributes :id, :description, :examples, :conditions, :conditions_expression, :operations, :operations_expression
36
+
37
+ def conditions_expression
38
+ self.attributes[:conditions_expression] ||= Tml::RulesEngine::Parser.new(self.conditions).parse
39
+ end
40
+
41
+ def operations_expression
42
+ self.attributes[:operations_expression] ||= Tml::RulesEngine::Parser.new(self.operations).parse
43
+ end
44
+
45
+ def gender_variables(object)
46
+ return {} unless self.conditions.index('@gender')
47
+ return {'@gender' => 'unknown'} unless object
48
+ context = language_case.language.context_by_keyword(:gender)
49
+ return {'@gender' => 'unknown'} unless context
50
+ context.vars(object)
51
+ end
52
+
53
+ def evaluate(value, object = nil)
54
+ return false if conditions.nil?
55
+
56
+ re = Tml::RulesEngine::Evaluator.new
57
+ re.evaluate(['let', '@value', value])
58
+
59
+ gender_variables(object).each do |key, value|
60
+ re.evaluate(['let', key, value])
61
+ end
62
+
63
+ re.evaluate(conditions_expression)
64
+ end
65
+
66
+ def apply(value)
67
+ value = value.to_s
68
+ return value if operations.nil?
69
+
70
+ re = Tml::RulesEngine::Evaluator.new
71
+ re.evaluate(['let', '@value', value])
72
+
73
+ re.evaluate(operations_expression)
74
+ end
75
+
76
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: UTF-8
2
+ #--
3
+ # Copyright (c) 2015 Translation Exchange, Inc
4
+ #
5
+ # _______ _ _ _ ______ _
6
+ # |__ __| | | | | (_) | ____| | |
7
+ # | |_ __ __ _ _ __ ___| | __ _| |_ _ ___ _ __ | |__ __ _____| |__ __ _ _ __ __ _ ___
8
+ # | | '__/ _` | '_ \/ __| |/ _` | __| |/ _ \| '_ \| __| \ \/ / __| '_ \ / _` | '_ \ / _` |/ _ \
9
+ # | | | | (_| | | | \__ \ | (_| | |_| | (_) | | | | |____ > < (__| | | | (_| | | | | (_| | __/
10
+ # |_|_| \__,_|_| |_|___/_|\__,_|\__|_|\___/|_| |_|______/_/\_\___|_| |_|\__,_|_| |_|\__, |\___|
11
+ # __/ |
12
+ # |___/
13
+ # Permission is hereby granted, free of charge, to any person obtaining
14
+ # a copy of this software and associated documentation files (the
15
+ # "Software"), to deal in the Software without restriction, including
16
+ # without limitation the rights to use, copy, modify, merge, publish,
17
+ # distribute, sublicense, and/or sell copies of the Software, and to
18
+ # permit persons to whom the Software is furnished to do so, subject to
19
+ # the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be
22
+ # included in all copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
+ #++
32
+
33
+ class Tml::LanguageContext < Tml::Base
34
+ belongs_to :language
35
+ attributes :keyword, :description, :default_key, :token_expression, :variables, :token_mapping
36
+ has_many :keys, :rules
37
+
38
+ def initialize(attrs = {})
39
+ super
40
+
41
+ self.attributes[:rules] = {}
42
+ if hash_value(attrs, :rules)
43
+ hash_value(attrs, :rules).each do |key, rule|
44
+ self.attributes[:rules][key] = Tml::LanguageContextRule.new(rule.merge(
45
+ :keyword => key,
46
+ :language_context => self
47
+ ))
48
+ end
49
+ end
50
+ end
51
+
52
+ def config
53
+ context_rules = Tml.config.context_rules
54
+ hash_value(context_rules, self.keyword.to_sym) || {}
55
+ end
56
+
57
+ def token_expression
58
+ @token_expression ||= begin
59
+ exp = self.attributes[:token_expression]
60
+ exp = Regexp.new(exp[1..-2])
61
+ exp
62
+ end
63
+ end
64
+
65
+ def applies_to_token?(token)
66
+ token_expression.match(token) != nil
67
+ end
68
+
69
+ def fallback_rule
70
+ @fallback_rule ||= rules.values.detect{|rule| rule.fallback?}
71
+ end
72
+
73
+ # prepare variables for evaluation
74
+ def vars(obj)
75
+ vars = {}
76
+
77
+ variables.each do |key|
78
+ method = hash_value(config, "variables.#{key}")
79
+ unless method
80
+ vars[key] = obj
81
+ next
82
+ end
83
+
84
+ if method.is_a?(String)
85
+ if obj.is_a?(Hash)
86
+ object = hash_value(obj, 'object') || obj
87
+ if object.is_a?(Hash)
88
+ vars[key] = hash_value(object, method, :whole => true)
89
+ else
90
+ vars[key] = object.send(method)
91
+ end
92
+ elsif obj.is_a?(String)
93
+ vars[key] = obj
94
+ else
95
+ vars[key] = obj.send(method)
96
+ end
97
+ elsif method.is_a?(Proc)
98
+ vars[key] = method.call(obj)
99
+ else
100
+ vars[key] = obj
101
+ end
102
+
103
+ vars[key] = vars[key].to_s if vars[key].is_a?(Symbol)
104
+ end
105
+ vars
106
+ end
107
+
108
+ def find_matching_rule(obj)
109
+ token_vars = vars(obj)
110
+ rules.values.each do |rule|
111
+ next if rule.fallback?
112
+ return rule if rule.evaluate(token_vars)
113
+ end
114
+ fallback_rule
115
+ end
116
+
117
+ end