1 # The InterpolationCompiler module contains optimizations that can tremendously
2 # speed up the interpolation process on the Simple backend.
4 # It works by defining a pre-compiled method on stored translation Strings that
5 # already bring all the knowledge about contained interpolation variables etc.
6 # so that the actual recurring interpolation will be very fast.
8 # To enable pre-compiled interpolations you can simply include the
9 # InterpolationCompiler module to the Simple backend:
11 # I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
13 # Note that InterpolationCompiler does not yield meaningful results and consequently
14 # should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
15 # (jRuby, Rubinius and 1.8.7).
18 module InterpolationCompiler
22 TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
23 INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
25 def compile_if_an_interpolation(string)
26 if interpolated_str?(string)
27 string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
28 def i18n_interpolate(v = {})
29 "#{compiled_interpolation_body(string)}"
37 def interpolated_str?(str)
38 str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
42 # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
47 def compiled_interpolation_body(str)
48 tokenize(str).map do |token|
49 (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
53 def handle_interpolation_token(interpolation, matchdata)
54 escaped, pattern, key = matchdata.values_at(1, 2, 3)
55 escaped ? pattern : compile_interpolation_token(key.to_sym)
58 def compile_interpolation_token(key)
59 "\#{#{interpolate_or_raise_missing(key)}}"
62 def interpolate_or_raise_missing(key)
63 escaped_key = escape_key_sym(key)
64 RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
67 def interpolate_key(key)
68 [direct_key(key), nil_key(key), missing_key(key)].join('||')
72 "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
76 "(v.has_key?(#{key}) && '')"
80 "raise(MissingInterpolationArgument.new(#{key}, self))"
84 "raise(ReservedInterpolationKey.new(#{key}, self))"
87 def escape_plain_str(str)
88 str.gsub(/"|\\|#/) {|x| "\\#{x}"}
91 def escape_key_sym(key)
92 # rely on Ruby to do all the hard work :)
97 def interpolate(locale, string, values)
98 if string.respond_to?(:i18n_interpolate)
99 string.i18n_interpolate(values)
107 def store_translations(locale, data, options = {})
108 compile_all_strings_in(data)
113 def compile_all_strings_in(data)
114 data.each_value do |value|
115 Compiler.compile_if_an_interpolation(value)
116 compile_all_strings_in(value) if value.kind_of?(Hash)