1 # encoding: utf-8
2
3 # The InterpolationCompiler module contains optimizations that can tremendously
4 # speed up the interpolation process on the Simple backend.
5 #
6 # It works by defining a pre-compiled method on stored translation Strings that
7 # already bring all the knowledge about contained interpolation variables etc.
8 # so that the actual recurring interpolation will be very fast.
9 #
10 # To enable pre-compiled interpolations you can simply include the
11 # InterpolationCompiler module to the Simple backend:
12 #
13 # I18n::Backend::Simple.send(:include, I18n::Backend::InterpolationCompiler)
14 #
15 # Note that InterpolationCompiler does not yield meaningful results and consequently
16 # should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
17 # (jRuby, Rubinius and 1.8.7).
18 module I18n
19 module Backend
20 module InterpolationCompiler
21 module Compiler
22 extend self
23
24 TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
25 INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
26
27 def compile_if_an_interpolation(string)
28 if interpolated_str?(string)
29 string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
30 def i18n_interpolate(v = {})
31 "#{compiled_interpolation_body(string)}"
32 end
33 RUBY_EVAL
34 end
35
36 string
37 end
38
39 def interpolated_str?(str)
40 str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
41 end
42
43 protected
44 # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
45 def tokenize(str)
46 str.split(TOKENIZER)
47 end
48
49 def compiled_interpolation_body(str)
50 tokenize(str).map do |token|
51 (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
52 end.join
53 end
54
55 def handle_interpolation_token(interpolation, matchdata)
56 escaped, pattern, key = matchdata.values_at(1, 2, 3)
57 escaped ? pattern : compile_interpolation_token(key.to_sym)
58 end
59
60 def compile_interpolation_token(key)
61 "\#{#{interpolate_or_raise_missing(key)}}"
62 end
63
64 def interpolate_or_raise_missing(key)
65 escaped_key = escape_key_sym(key)
66 Base::RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
67 end
68
69 def interpolate_key(key)
70 [direct_key(key), nil_key(key), missing_key(key)].join('||')
71 end
72
73 def direct_key(key)
74 "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
75 end
76
77 def nil_key(key)
78 "(v.has_key?(#{key}) && '')"
79 end
80
81 def missing_key(key)
82 "raise(MissingInterpolationArgument.new(#{key}, self))"
83 end
84
85 def reserved_key(key)
86 "raise(ReservedInterpolationKey.new(#{key}, self))"
87 end
88
89 def escape_plain_str(str)
90 str.gsub(/"|\\|#/) {|x| "\\#{x}"}
91 end
92
93 def escape_key_sym(key)
94 # rely on Ruby to do all the hard work :)
95 key.to_sym.inspect
96 end
97 end
98
99 def interpolate(locale, string, values)
100 if string.respond_to?(:i18n_interpolate)
101 string.i18n_interpolate(values)
102 elsif values
103 super
104 else
105 string
106 end
107 end
108
109 def store_translations(locale, data, options = {})
110 compile_all_strings_in(data)
111 super
112 end
113
114 protected
115 def compile_all_strings_in(data)
116 data.each_value do |value|
117 Compiler.compile_if_an_interpolation(value)
118 compile_all_strings_in(value) if value.kind_of?(Hash)
119 end
120 end
121 end
122 end
123 en