Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 121 lines (102 sloc) 3.748 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
# The InterpolationCompiler module contains optimizations that can tremendously
# speed up the interpolation process on the Simple backend.
#
# It works by defining a pre-compiled method on stored translation Strings that
# already bring all the knowledge about contained interpolation variables etc.
# so that the actual recurring interpolation will be very fast.
#
# To enable pre-compiled interpolations you can simply include the
# InterpolationCompiler module to the Simple backend:
#
# I18n::Backend::Simple.include(I18n::Backend::InterpolationCompiler)
#
# Note that InterpolationCompiler does not yield meaningful results and consequently
# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
# (jRuby, Rubinius and 1.8.7).
module I18n
  module Backend
    module InterpolationCompiler
      module Compiler
        extend self

        TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
        INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/

        def compile_if_an_interpolation(string)
          if interpolated_str?(string)
            string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
def i18n_interpolate(v = {})
"#{compiled_interpolation_body(string)}"
end
RUBY_EVAL
          end

          string
        end

        def interpolated_str?(str)
          str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
        end

        protected
        # tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
        def tokenize(str)
          str.split(TOKENIZER)
        end

        def compiled_interpolation_body(str)
          tokenize(str).map do |token|
            (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
          end.join
        end

        def handle_interpolation_token(interpolation, matchdata)
          escaped, pattern, key = matchdata.values_at(1, 2, 3)
          escaped ? pattern : compile_interpolation_token(key.to_sym)
        end

        def compile_interpolation_token(key)
          "\#{#{interpolate_or_raise_missing(key)}}"
        end

        def interpolate_or_raise_missing(key)
          escaped_key = escape_key_sym(key)
          RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
        end

        def interpolate_key(key)
          [direct_key(key), nil_key(key), missing_key(key)].join('||')
        end

        def direct_key(key)
          "((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
        end

        def nil_key(key)
          "(v.has_key?(#{key}) && '')"
        end

        def missing_key(key)
          "raise(MissingInterpolationArgument.new(#{key}, self))"
        end

        def reserved_key(key)
          "raise(ReservedInterpolationKey.new(#{key}, self))"
        end

        def escape_plain_str(str)
          str.gsub(/"|\\|#/) {|x| "\\#{x}"}
        end

        def escape_key_sym(key)
          # rely on Ruby to do all the hard work :)
          key.to_sym.inspect
        end
      end

      def interpolate(locale, string, values)
        if string.respond_to?(:i18n_interpolate)
          string.i18n_interpolate(values)
        elsif values
          super
        else
          string
        end
      end

      def store_translations(locale, data, options = {})
        compile_all_strings_in(data)
        super
      end

      protected
      def compile_all_strings_in(data)
        data.each_value do |value|
          Compiler.compile_if_an_interpolation(value)
          compile_all_strings_in(value) if value.kind_of?(Hash)
        end
      end
    end
  end
end
Something went wrong with that request. Please try again.