Permalink
Browse files

Initial support for optional "key interpolations": Extended behavior …

…to support for interpolating explicit I18n keys (reference sub-translations within same document) for DRYer translations.

Without "key interpolations":

  interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'  # => "file test.txt opened by {{user}}"

With "key interpolations":

  translate :"file.name"  # => "manifesto.txt"

  interpolate "file {{:file.name}} opened by \\{{user}}", :user => 'Mr. X'  # => "file manifesto.txt opened by {{user}}"
  • Loading branch information...
1 parent 4e1260e commit ff1169dc10d7db8ceca51997a72d040246706a78 @grimen committed Feb 13, 2010
Showing with 129 additions and 0 deletions.
  1. +1 −0 lib/i18n/backend.rb
  2. +67 −0 lib/i18n/backend/key_interpolation.rb
  3. +1 −0 test/api.rb
  4. +1 −0 test/api/simple_test.rb
  5. +59 −0 test/api/tests/key_interpolation.rb
View
1 lib/i18n/backend.rb
@@ -15,5 +15,6 @@ module Backend
autoload :Metadata, 'i18n/backend/metadata'
autoload :Pluralization, 'i18n/backend/pluralization'
autoload :Simple, 'i18n/backend/simple'
+ autoload :KeyInterpolation, 'i18n/backend/key_interpolation'
end
end
View
67 lib/i18n/backend/key_interpolation.rb
@@ -0,0 +1,67 @@
+module I18n
+ module Backend
+ module KeyInterpolation
+ KEY_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{\:([^\}]+)\}\}/
+
+ def self.included(base)
+ base.class_eval do
+ alias_method_chain :interpolate, :key_interpolation
+ end
+ end
+
+ protected
+
+ # Interpolates values into a given string; with extended behavior to support for interpolating
+ # explicit I18n keys (reference sub-translations within same document) for DRYer translations.
+ #
+ # == Base interpolation:
+ #
+ # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X'
+ # # => "file test.txt opened by {{user}}"
+ #
+ # == Key interpolation:
+ #
+ # # translate :"file.name"
+ # # => "manifesto.txt"
+ #
+ # # interpolate "file {{:file.name}} opened by \\{{user}}", :user => 'Mr. X'
+ # # => "file manifesto.txt opened by {{user}}"
+ #
+ # Note that you have to double escape the <tt>\\</tt> when you want to escape
+ # the <tt>{{...}}</tt> key in a string (once for the string and once for the
+ # interpolation).
+ def interpolate_with_key_interpolation(locale, string, values = {})
+ return string unless string.is_a?(::String)
+
+ preserve_encoding(string) do
+ s = string.gsub(KEY_INTERPOLATION_SYNTAX_PATTERN) do
+ escaped, key = $1, $2.to_sym
+
+ if escaped
+ "\\{{:#{key}}}"
+ elsif ::I18n::Backend::Base::RESERVED_KEYS.include?(key)
+ raise ReservedInterpolationKey.new(key, string)
+ else
+ begin
+ value = self.translate(locale, key)
+ rescue
+ # DISCUSS: A leaner approach would be: self.translate(locale, key, :default => "{{:#{key}}}")
+ raise KeyError
+ end
+ # NOTE: Didn't want to mess with I18n String::INTERPOLATION_PATTERN; so using "_" instead of ":".
+ key = key.to_s.tr('.', '_').to_sym
+ values.merge!(:"_#{key}" => value)
+ "{{_#{key}}}"
+ end
+ end
+
+ self.interpolate_without_key_interpolation(locale, s, values)
+ end
+
+ rescue KeyError => e
+ raise MissingInterpolationArgument.new(values, string)
+ end
+
+ end
+ end
+end
View
1 test/api.rb
@@ -3,6 +3,7 @@ module Api
autoload :Basics, 'api/tests/basics'
autoload :Defaults, 'api/tests/defaults'
autoload :Interpolation, 'api/tests/interpolation'
+ autoload :KeyInterpolation, 'api/tests/key_interpolation'
autoload :Link, 'api/tests/link'
autoload :Lookup, 'api/tests/lookup'
autoload :Pluralization, 'api/tests/pluralization'
View
1 test/api/simple_test.rb
@@ -8,6 +8,7 @@ class I18nSimpleBackendApiTest < Test::Unit::TestCase
include Tests::Api::Basics
include Tests::Api::Defaults
include Tests::Api::Interpolation
+ include Tests::Api::KeyInterpolation
include Tests::Api::Link
include Tests::Api::Lookup
include Tests::Api::Pluralization
View
59 test/api/tests/key_interpolation.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+I18n::Backend::Base.class_eval do
+ include ::I18n::Backend::KeyInterpolation
+end
+
+module Tests
+ module Api
+ module KeyInterpolation
+ def interpolate(*args)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ key = args.pop
+ ::I18n.backend.translate('en', key, options)
+ end
+
+ define_method "test key interpolation: given interpolation key but missing this key it raises I18n::MissingInterpolationArgument" do
+ assert_raise(::I18n::MissingInterpolationArgument) do
+ interpolate(:default => 'Hi {{:name}}!')
+ end
+
+ assert_raise(::I18n::MissingInterpolationArgument) do
+ interpolate(:default => 'Hi {{:name}}!', :name => 'David')
+ end
+ end
+
+ define_method "test key interpolation: given valid interpolation translation key it should interpolate the valid translation string" do
+ ::I18n.backend.store_translations(:en, :name => 'David')
+ assert_equal 'Hi David!', interpolate(:default => 'Hi {{:name}}!')
+
+ ::I18n.backend.store_translations(:en, :"a.deeply.nested.name" => 'Goliath')
+ assert_equal 'Hi Goliath!', interpolate(:default => 'Hi {{:a.deeply.nested.name}}!')
+ end
+
+ define_method "test interpolation: given the translation is in utf-8 it still works" do
+ ::I18n.backend.store_translations(:en, :name => 'David')
+ assert_equal 'Häi David!', interpolate(:default => 'Häi {{:name}}!')
+
+ ::I18n.backend.store_translations(:en, :"a.deeply.nested.name" => 'Goliath')
+ assert_equal 'Hi Goliath!', interpolate(:default => 'Hi {{:a.deeply.nested.name}}!')
+ end
+
+ define_method "test interpolation: given the value is in utf-8 it still works" do
+ ::I18n.backend.store_translations(:en, :name => 'ゆきひろ')
+ assert_equal 'Hi ゆきひろ!', interpolate(:default => 'Hi {{:name}}!')
+
+ ::I18n.backend.store_translations(:en, :"a.deeply.nested.name" => 'デビッド')
+ assert_equal 'Hi デビッド!', interpolate(:default => 'Hi {{:a.deeply.nested.name}}!')
+ end
+
+ define_method "test interpolation: given the translation and the value are in utf-8 it still works" do
+ ::I18n.backend.store_translations(:en, :name => 'ゆきひろ')
+ assert_equal 'こんにちは、ゆきひろさん!', interpolate(:default => 'こんにちは、{{:name}}さん!')
+
+ ::I18n.backend.store_translations(:en, :"a.deeply.nested.name" => 'デビッド')
+ assert_equal 'こんにちは デビッドさん!', interpolate(:default => 'こんにちは {{:a.deeply.nested.name}}さん!')
+ end
+
+ end
+ end
+end

0 comments on commit ff1169d

Please sign in to comment.