Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
gburgett committed Mar 7, 2019
2 parents d298938 + 529b83a commit b7e5195
Show file tree
Hide file tree
Showing 18 changed files with 216 additions and 51 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ I18n.t(:test) # => "Dies ist ein Test"
* Cache
* Pluralization: lambda pluralizers stored as translation data
* Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
* [Gettext support](https://github.com/svenfuchs/i18n/wiki/Gettext)
* [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext)
* Translation metadata

## Alternative Backend
Expand All @@ -76,7 +76,7 @@ I18n.t(:test) # => "Dies ist ein Test"
* ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
* KeyValue (uses active_support/json and cannot store procs)

For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/svenfuchs/i18n/wiki/Resources).
For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources).

## Tests

Expand Down
67 changes: 41 additions & 26 deletions lib/i18n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ def reload!
config.backend.reload!
end

# Tells the backend to load translations now. Used in situations like the
# Rails production environment. Backends can implement whatever strategy
# is useful.
def eager_load!
config.backend.eager_load!
end

# Translates, pluralizes and interpolates a given key using a given locale,
# scope, and default, as well as interpolation values.
#
Expand Down Expand Up @@ -169,23 +176,26 @@ def reload!
# from the argument values passed to #translate. Therefor your lambdas should
# always return the same translations/values per unique combination of argument
# values.
def translate(*args)
options = args.last.is_a?(Hash) ? args.pop.dup : {}
key = args.shift
backend = config.backend
locale = options.delete(:locale) || config.locale
handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO deprecate :raise

def translate(key = nil, *, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise
locale ||= config.locale
raise Disabled.new('t') if locale == false
enforce_available_locales!(locale)

backend = config.backend

result = catch(:exception) do
if key.is_a?(Array)
key.map { |k| backend.translate(locale, k, options) }
else
backend.translate(locale, key, options)
end
end
result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result

if result.is_a?(MissingTranslation)
handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
else
result
end
end
alias :t :translate

Expand All @@ -197,7 +207,9 @@ def translate!(key, options = EMPTY_HASH)
alias :t! :translate!

# Returns true if a translation exists for a given key, otherwise returns false.
def exists?(key, locale = config.locale)
def exists?(key, _locale = nil, locale: _locale)
locale ||= config.locale
raise Disabled.new('exists?') if locale == false
raise I18n::ArgumentError if key.is_a?(String) && key.empty?
config.backend.exists?(locale, key)
end
Expand Down Expand Up @@ -253,37 +265,40 @@ def exists?(key, locale = config.locale)
# I18n.transliterate("Jürgen") # => "Juergen"
# I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
# I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
def transliterate(*args)
options = args.pop.dup if args.last.is_a?(Hash)
key = args.shift
locale = options && options.delete(:locale) || config.locale
handling = options && (options.delete(:throw) && :throw || options.delete(:raise) && :raise)
replacement = options && options.delete(:replacement)
def transliterate(key, *, throw: false, raise: false, locale: nil, replacement: nil, **options)
locale ||= config.locale
raise Disabled.new('transliterate') if locale == false
enforce_available_locales!(locale)

config.backend.transliterate(locale, key, replacement)
rescue I18n::ArgumentError => exception
handle_exception(handling, exception, locale, key, options || {})
handle_exception((throw && :throw || raise && :raise), exception, locale, key, options)
end

# Localizes certain objects, such as dates and numbers to local formatting.
def localize(object, options = nil)
options = options ? options.dup : {}
locale = options.delete(:locale) || config.locale
format = options.delete(:format) || :default
def localize(object, locale: nil, format: nil, **options)
locale ||= config.locale
raise Disabled.new('l') if locale == false
enforce_available_locales!(locale)

format ||= :default
config.backend.localize(locale, object, format, options)
end
alias :l :localize

# Executes block with given I18n.locale set.
def with_locale(tmp_locale = nil)
if tmp_locale
if tmp_locale == nil
yield
else
current_locale = self.locale
self.locale = tmp_locale
self.locale = tmp_locale
begin
yield
ensure
self.locale = current_locale
end
end
yield
ensure
self.locale = current_locale if tmp_locale
end

# Merges the given locale, key and scope into a single array of keys.
Expand All @@ -307,7 +322,7 @@ def locale_available?(locale)

# Raises an InvalidLocale exception when the passed locale is not available.
def enforce_available_locales!(locale)
if config.enforce_available_locales
if locale != false && config.enforce_available_locales
raise I18n::InvalidLocale.new(locale) if !locale_available?(locale)
end
end
Expand Down
15 changes: 14 additions & 1 deletion lib/i18n/backend/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,19 @@ def available_locales
end

def reload!
eager_load! if eager_loaded?
end

def eager_load!
@eager_loaded = true
end

protected

def eager_loaded?
@eager_loaded ||= false
end

# The method which actually looks up for the translation in the store.
def lookup(locale, key, scope = [], options = EMPTY_HASH)
raise NotImplementedError
Expand Down Expand Up @@ -248,12 +257,16 @@ def load_json(filename)
end

def translate_localization_format(locale, object, format, options)
format.to_s.gsub(/%[aAbBpP]/) do |match|
format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
case match
when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase
when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday]
when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase
when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
end
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/backend/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def cache_key(locale, key, options)
private

def digest_item(key)
I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.hash
I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.to_s.hash
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/i18n/backend/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def reload!
backends.each { |backend| backend.reload! }
end

def eager_load!
backends.each { |backend| backend.eager_load! }
end

def store_translations(locale, data, options = EMPTY_HASH)
backends.first.store_translations(locale, data, options)
end
Expand Down
6 changes: 6 additions & 0 deletions lib/i18n/backend/memoize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def reload!
super
end

def eager_load!
memoized_lookup
available_locales
super
end

protected

def lookup(locale, key, scope = nil, options = EMPTY_HASH)
Expand Down
5 changes: 5 additions & 0 deletions lib/i18n/backend/simple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ def reload!
super
end

def eager_load!
init_translations unless initialized?
super
end

def translations(do_init: false)
# To avoid returning empty translations,
# call `init_translations`
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Config
# The only configuration value that is not global and scoped to thread is :locale.
# It defaults to the default_locale.
def locale
defined?(@locale) && @locale ? @locale : default_locale
defined?(@locale) && @locale != nil ? @locale : default_locale
end

# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
Expand Down
14 changes: 14 additions & 0 deletions lib/i18n/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ def call(exception, _locale, _key, _options)

class ArgumentError < ::ArgumentError; end

class Disabled < ArgumentError
def initialize(method)
super(<<~MESSAGE)
I18n.#{method} is currently disabled, likely because your application is still in its loading phase.
This method is meant to display text in the user locale, so calling it before the user locale has
been set is likely to display text from the wrong locale to some users.
If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing
the desired locale explictly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
MESSAGE
end
end

class InvalidLocale < ArgumentError
attr_reader :locale
def initialize(locale)
Expand Down
30 changes: 24 additions & 6 deletions lib/i18n/tests/localization/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ def setup
end

test "localize Date: given the short format it uses it" do
# TODO should be Mrz, shouldn't it?
assert_equal '01. Mar', I18n.l(@date, :format => :short, :locale => :de)
assert_equal '01. Mär', I18n.l(@date, :format => :short, :locale => :de)
end

test "localize Date: given the long format it uses it" do
Expand All @@ -27,17 +26,36 @@ def setup
assert_equal 'Samstag', I18n.l(@date, :format => '%A', :locale => :de)
end

test "localize Date: given a uppercased day name format it returns the correct day name in upcase" do
assert_equal 'samstag'.upcase, I18n.l(@date, :format => '%^A', :locale => :de)
end

test "localize Date: given an abbreviated day name format it returns the correct abbreviated day name" do
assert_equal 'Sa', I18n.l(@date, :format => '%a', :locale => :de)
end

test "localize Date: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do
assert_equal 'sa'.upcase, I18n.l(@date, :format => '%^a', :locale => :de)
end

test "localize Date: given a month name format it returns the correct month name" do
assert_equal 'März', I18n.l(@date, :format => '%B', :locale => :de)
end

test "localize Date: given a uppercased month name format it returns the correct month name in upcase" do
assert_equal 'märz'.upcase, I18n.l(@date, :format => '%^B', :locale => :de)
end

test "localize Date: given an abbreviated month name format it returns the correct abbreviated month name" do
# TODO should be Mrz, shouldn't it?
assert_equal 'Mar', I18n.l(@date, :format => '%b', :locale => :de)
assert_equal 'Mär', I18n.l(@date, :format => '%b', :locale => :de)
end

test "localize Date: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do
assert_equal 'mär'.upcase, I18n.l(@date, :format => '%^b', :locale => :de)
end

test "localize Date: given a date format with the month name upcased it returns the correct value" do
assert_equal '1. FEBRUAR 2008', I18n.l(::Date.new(2008, 2, 1), :format => "%-d. %^B %Y", :locale => :de)
end

test "localize Date: given missing translations it returns the correct error message" do
Expand All @@ -50,7 +68,7 @@ def setup

test "localize Date: does not modify the options hash" do
options = { :format => '%b', :locale => :de }
assert_equal 'Mar', I18n.l(@date, options)
assert_equal 'Mär', I18n.l(@date, options)
assert_equal({ :format => '%b', :locale => :de }, options)
assert_nothing_raised { I18n.l(@date, options.freeze) }
end
Expand Down Expand Up @@ -89,7 +107,7 @@ def setup_date_translations
:day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag),
:abbr_day_names => %w(So Mo Di Mi Do Fr Sa),
:month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil),
:abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil)
:abbr_month_names => %w(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil)
}
}
end
Expand Down
29 changes: 23 additions & 6 deletions lib/i18n/tests/localization/date_time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,51 @@ def setup
end

test "localize DateTime: given the short format it uses it" do
# TODO should be Mrz, shouldn't it?
assert_equal '01. Mar 06:00', I18n.l(@datetime, :format => :short, :locale => :de)
assert_equal '01. Mär 06:00', I18n.l(@datetime, :format => :short, :locale => :de)
end

test "localize DateTime: given the long format it uses it" do
assert_equal '01. März 2008 06:00', I18n.l(@datetime, :format => :long, :locale => :de)
end

test "localize DateTime: given the default format it uses it" do
# TODO should be Mrz, shouldn't it?
assert_equal 'Sa, 01. Mar 2008 06:00:00 +0000', I18n.l(@datetime, :format => :default, :locale => :de)
assert_equal 'Sa, 01. Mär 2008 06:00:00 +0000', I18n.l(@datetime, :format => :default, :locale => :de)
end

test "localize DateTime: given a day name format it returns the correct day name" do
assert_equal 'Samstag', I18n.l(@datetime, :format => '%A', :locale => :de)
end

test "localize DateTime: given a uppercased day name format it returns the correct day name in upcase" do
assert_equal 'samstag'.upcase, I18n.l(@datetime, :format => '%^A', :locale => :de)
end

test "localize DateTime: given an abbreviated day name format it returns the correct abbreviated day name" do
assert_equal 'Sa', I18n.l(@datetime, :format => '%a', :locale => :de)
end

test "localize DateTime: given an abbreviated and uppercased day name format it returns the correct abbreviated day name in upcase" do
assert_equal 'sa'.upcase, I18n.l(@datetime, :format => '%^a', :locale => :de)
end

test "localize DateTime: given a month name format it returns the correct month name" do
assert_equal 'März', I18n.l(@datetime, :format => '%B', :locale => :de)
end

test "localize DateTime: given a uppercased month name format it returns the correct month name in upcase" do
assert_equal 'märz'.upcase, I18n.l(@datetime, :format => '%^B', :locale => :de)
end

test "localize DateTime: given an abbreviated month name format it returns the correct abbreviated month name" do
# TODO should be Mrz, shouldn't it?
assert_equal 'Mar', I18n.l(@datetime, :format => '%b', :locale => :de)
assert_equal 'Mär', I18n.l(@datetime, :format => '%b', :locale => :de)
end

test "localize DateTime: given an abbreviated and uppercased month name format it returns the correct abbreviated month name in upcase" do
assert_equal 'mär'.upcase, I18n.l(@datetime, :format => '%^b', :locale => :de)
end

test "localize DateTime: given a date format with the month name upcased it returns the correct value" do
assert_equal '1. FEBRUAR 2008', I18n.l(::DateTime.new(2008, 2, 1, 6), :format => "%-d. %^B %Y", :locale => :de)
end

test "localize DateTime: given missing translations it returns the correct error message" do
Expand Down
Loading

0 comments on commit b7e5195

Please sign in to comment.