Skip to content

Commit

Permalink
un-monkey-patch and refactor string interpolation
Browse files Browse the repository at this point in the history
  • Loading branch information
Sven Fuchs committed Nov 18, 2010
1 parent a12b951 commit 93dbfb6
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 260 deletions.
16 changes: 0 additions & 16 deletions Gemfile.lock
@@ -1,39 +1,23 @@
GEM
remote: http://rubygems.org/
specs:
ParseTree (3.0.6)
RubyInline (>= 3.7.0)
sexp_processor (>= 3.0.0)
RubyInline (3.8.6)
ZenTest (~> 4.3)
ZenTest (4.4.0)
activesupport (3.0.3)
ffi (0.6.3)
rake (>= 0.8.7)
mocha (0.9.9)
rake
rake (0.8.7)
ruby-cldr (0.0.1)
ruby2ruby (1.2.5)
ruby_parser (~> 2.0)
sexp_processor (~> 3.0)
ruby_parser (2.0.5)
sexp_processor (~> 3.0)
rufus-tokyo (1.0.7)
sexp_processor (3.0.5)
sqlite3-ruby (1.3.2)
test_declarative (0.0.4)

PLATFORMS
ruby

DEPENDENCIES
ParseTree
activesupport (~> 3.0.0)
ffi
mocha
ruby-cldr
ruby2ruby
rufus-tokyo
sqlite3-ruby
test_declarative
Expand Down
12 changes: 4 additions & 8 deletions lib/i18n.rb
@@ -1,13 +1,6 @@
# Authors:: Sven Fuchs (http://www.artweb-design.de),
# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
# Stephan Soller (http://www.arkanis-development.de/),
# Saimon Moore (http://saimonmoore.net),
# Matt Aimonetti (http://railsontherun.com/)
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
# License:: MIT
require 'i18n/version'
require 'i18n/exceptions'
require 'i18n/core_ext/string/interpolate'
require 'i18n/interpolate/ruby.rb'

module I18n
autoload :Backend, 'i18n/backend'
Expand All @@ -16,6 +9,9 @@ module I18n
autoload :Locale, 'i18n/locale'
autoload :Tests, 'i18n/tests'

RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :raise, :rescue_format]
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/

class << self
# Gets I18n configuration object.
def config
Expand Down
37 changes: 3 additions & 34 deletions lib/i18n/backend/base.rb
Expand Up @@ -7,9 +7,6 @@ module Backend
module Base
include I18n::Backend::Transliterator

RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback]
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/

# Accepts a list of paths to translation files. Loads translations from
# plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
# for details.
Expand Down Expand Up @@ -141,36 +138,14 @@ def pluralize(locale, entry, count)
#
# interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
# # => "file test.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(locale, string, values = {})
return string unless string.is_a?(::String) && !values.empty?

values.each do |key, value|
value = value.call(values) if interpolate_lambda?(value, string, key)
value = value.to_s unless value.is_a?(::String)
values[key] = value
end

suppress_warnings do
string % values
end
rescue KeyError => e
if string =~ RESERVED_KEYS_PATTERN
raise ReservedInterpolationKey.new($1.to_sym, string)
if string.is_a?(::String) && !values.empty?
I18n.interpolate(string, values)
else
raise MissingInterpolationArgument.new(values, string)
string
end
end

# returns true when the given value responds to :call and the key is
# an interpolation placeholder in the given string
def interpolate_lambda?(object, string, key)
object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
end

# Loads a single translations file by delegating to #load_rb or
# #load_yml depending on the file extension and directly merges the
# data to the existing translations. Raises I18n::UnknownFileType
Expand All @@ -194,12 +169,6 @@ def load_rb(filename)
def load_yml(filename)
YAML.load_file(filename)
end

def warn_syntax_deprecation!(locale, string) #:nodoc:
return if @skip_syntax_deprecation
warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{locale} - #{string}\n"
@skip_syntax_deprecation = true
end
end
end
end
2 changes: 1 addition & 1 deletion lib/i18n/backend/interpolation_compiler.rb
Expand Up @@ -61,7 +61,7 @@ def compile_interpolation_token(key)

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

def interpolate_key(key)
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/backend/metadata.rb
Expand Up @@ -38,7 +38,7 @@ def translate(locale, key, options = {})
:scope => options[:scope],
:default => options[:default],
:separator => options[:separator],
:values => options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }
:values => options.reject { |name, value| RESERVED_KEYS.include?(name) }
}
with_metadata(metadata) { super }
end
Expand Down
96 changes: 0 additions & 96 deletions lib/i18n/core_ext/string/interpolate.rb
@@ -1,96 +0,0 @@
=begin
heavily based on Masao Mutoh's gettext String interpolation extension
http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
Copyright (C) 2005-2009 Masao Mutoh
You may redistribute it and/or modify it under the same license terms as Ruby.
=end

begin
raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b'
rescue ArgumentError
# KeyError is raised by String#% when the string contains a named placeholder
# that is not contained in the given arguments hash. Ruby 1.9 includes and
# raises this exception natively. We define it to mimic Ruby 1.9's behaviour
# in Ruby 1.8.x
class KeyError < IndexError
def initialize(message = nil)
super(message || "key not found")
end
end unless defined?(KeyError)

# Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
#
# String#% method which accept "named argument". The translator can know
# the meaning of the msgids using "named argument" instead of %s/%d style.
class String
# For older ruby versions, such as ruby-1.8.5
alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
alias :interpolate_without_ruby_19_syntax :% # :nodoc:

INTERPOLATION_PATTERN = Regexp.union(
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
)

INTERPOLATION_PATTERN_WITH_ESCAPE = Regexp.union(
/%%/,
INTERPOLATION_PATTERN
)

# % uses self (i.e. the String) as a format specification and returns the
# result of applying it to the given arguments. In other words it interpolates
# the given arguments to the string according to the formats the string
# defines.
#
# There are three ways to use it:
#
# * Using a single argument or Array of arguments.
#
# This is the default behaviour of the String class. See Kernel#sprintf for
# more details about the format string.
#
# Example:
#
# "%d %s" % [1, "message"]
# # => "1 message"
#
# * Using a Hash as an argument and unformatted, named placeholders.
#
# When you pass a Hash as an argument and specify placeholders with %{foo}
# it will interpret the hash values as named arguments.
#
# Example:
#
# "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
# # => "Masao Mutoh"
#
# * Using a Hash as an argument and formatted, named placeholders.
#
# When you pass a Hash as an argument and specify placeholders with %<foo>d
# it will interpret the hash values as named arguments and format the value
# according to the formatting instruction appended to the closing >.
#
# Example:
#
# "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
# # => "10, 43.3"
def %(args)
if args.kind_of?(Hash)
dup.gsub(INTERPOLATION_PATTERN_WITH_ESCAPE) do |match|
if match == '%%'
'%'
else
key = ($1 || $2).to_sym
raise KeyError unless args.has_key?(key)
$3 ? sprintf("%#{$3}", args[key]) : args[key]
end
end
elsif self =~ INTERPOLATION_PATTERN
raise ArgumentError.new('one hash required')
else
result = gsub(/%([{<])/, '%%\1')
result.send :'interpolate_without_ruby_19_syntax', args
end
end
end
end
6 changes: 0 additions & 6 deletions lib/i18n/exceptions.rb
@@ -1,9 +1,3 @@
class KeyError < IndexError
def initialize(message = nil)
super(message || "key not found")
end
end unless defined?(KeyError)

module I18n
# Handles exceptions raised in the backend. All exceptions except for
# MissingTranslationData exceptions are re-raised. When a MissingTranslationData
Expand Down
31 changes: 31 additions & 0 deletions lib/i18n/interpolate/ruby.rb
@@ -0,0 +1,31 @@
# heavily based on Masao Mutoh's gettext String interpolation extension
# http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb

module I18n
INTERPOLATION_PATTERN = Regexp.union(
/%%/,
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
)

class << self
def interpolate(string, values)
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
interpolate_hash(string, values)
end

def interpolate_hash(string, values)
string.gsub(INTERPOLATION_PATTERN) do |match|
if match == '%%'
'%'
else
key = ($1 || $2).to_sym
value = values.key?(key) ? values[key] : raise(MissingInterpolationArgument.new(values, string))
value = value.call(values) if value.respond_to?(:call)
$3 ? sprintf("%#{$3}", value) : value
end
end
end
end
end
98 changes: 0 additions & 98 deletions test/core_ext/string/interpolate_test.rb

This file was deleted.

File renamed without changes.

0 comments on commit 93dbfb6

Please sign in to comment.