Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
rubyopt: [""]
ruby_version: [head, 3.4, 3.3, 3.2, jruby]
ruby_version: [head, 3.4, 3.3, 3.2, 3.1, jruby]
gemfile:
- Gemfile
- gemfiles/Gemfile.rails-7.0.x
Expand All @@ -37,6 +37,12 @@ jobs:
gemfile: gemfiles/Gemfile.rails-main
- ruby_version: 3.2
gemfile: gemfiles/Gemfile.rails-main
# Rails 8.0.x requires Ruby 3.2+
- ruby_version: 3.1
gemfile: gemfiles/Gemfile.rails-8.0.x
# Rails main requires Ruby 3.3+
- ruby_version: 3.1
gemfile: gemfiles/Gemfile.rails-main
include:
- ruby_version: 3.4
gemfile: Gemfile
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We support Rails versions from 6.0 and up.

### Ruby (without Rails)

We support Ruby versions from 3.2 and up.
We support Ruby versions from 3.1 and up.

If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`:

Expand Down
2 changes: 1 addition & 1 deletion i18n.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.require_path = 'lib'
s.required_rubygems_version = '>= 1.3.5'
s.required_ruby_version = '>= 3.2'
s.required_ruby_version = '>= 3.1'
s.add_dependency 'concurrent-ruby', '~> 1.0'
end
31 changes: 22 additions & 9 deletions lib/i18n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,18 @@ def self.reserved_keys_pattern # :nodoc:
module Base
# Gets I18n configuration object.
def config
current = Fiber[:i18n_config] || self.config = I18n::Config.new
if current.respond_to?(:owned_by?) && !current.owned_by?(Fiber.current)
current = current.dup
Fiber[:i18n_config] = current
end
if Fiber.respond_to?(:[])
current = Fiber[:i18n_config] || self.config = I18n::Config.new
if current.respond_to?(:owned_by?) && !current.owned_by?(Fiber.current)
current = current.dup
Fiber[:i18n_config] = current
end

current
current
else
Thread.current.thread_variable_get(:i18n_config) ||
Thread.current.thread_variable_set(:i18n_config, I18n::Config.new)
end
end

# Gets a mutable I18n configuration object.
Expand All @@ -70,14 +75,22 @@ def writable_config
return current unless current.frozen?

current = current.dup
Fiber[:i18n_config] = current
if Fiber.respond_to?(:[])
Fiber[:i18n_config] = current
else
Thread.current.thread_variable_set(:i18n_config, current)
end
current
end

# Sets I18n configuration object.
def config=(value)
Fiber[:i18n_config] = value
value.owner = Fiber.current if value.respond_to?(:owner=) && !value.frozen?
if Fiber.respond_to?(:[])
Fiber[:i18n_config] = value
value.owner = Fiber.current if value.respond_to?(:owner=) && !value.frozen?
else
Thread.current.thread_variable_set(:i18n_config, value)
end
end

# Write methods which delegates to the configuration object
Expand Down
12 changes: 10 additions & 2 deletions lib/i18n/backend/fallbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@ class << self
# Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
def fallbacks
@@fallbacks ||= I18n::Locale::Fallbacks.new
Fiber[:i18n_fallbacks] || @@fallbacks
if Fiber.respond_to?(:[])
Fiber[:i18n_fallbacks] || @@fallbacks
else
Thread.current[:i18n_fallbacks] || @@fallbacks
end
end

# Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
def fallbacks=(fallbacks)
@@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks
Fiber[:i18n_fallbacks] = @@fallbacks
if Fiber.respond_to?(:[])
Fiber[:i18n_fallbacks] = @@fallbacks
else
Thread.current[:i18n_fallbacks] = @@fallbacks
end
end
end

Expand Down
6 changes: 5 additions & 1 deletion lib/i18n/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ def initialize(app)
def call(env)
@app.call(env)
ensure
Fiber[:i18n_config] = I18n::Config.new
if Fiber.respond_to?(:[])
Fiber[:i18n_config] = I18n::Config.new
else
Thread.current.thread_variable_set(:i18n_config, I18n::Config.new)
end
end

end
Expand Down
12 changes: 10 additions & 2 deletions test/i18n/middleware_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ def setup
end

test "middleware initializes new config object after request" do
old_i18n_config_object_id = Fiber[:i18n_config].object_id
old_i18n_config_object_id = if Fiber.respond_to?(:[])
Fiber[:i18n_config].object_id
else
Thread.current.thread_variable_get(:i18n_config).object_id
end
@middleware.call({})

updated_i18n_config_object_id = Fiber[:i18n_config].object_id
updated_i18n_config_object_id = if Fiber.respond_to?(:[])
Fiber[:i18n_config].object_id
else
Thread.current.thread_variable_get(:i18n_config).object_id
end
refute_equal updated_i18n_config_object_id, old_i18n_config_object_id
end

Expand Down
17 changes: 14 additions & 3 deletions test/i18n_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,14 @@ def setup
assert_equal I18n.default_locale, I18n.locale
end

test "sets the current locale to Fiber local" do
test "sets the current locale to Thread.current or Fiber local" do
assert_nothing_raised { I18n.locale = 'de' }
assert_equal :de, I18n.locale
assert_equal :de, Fiber[:i18n_config].locale
if Fiber.respond_to?(:[])
assert_equal :de, Fiber[:i18n_config].locale
else
assert_equal :de, Thread.current.thread_variable_get(:i18n_config).locale
end
I18n.locale = :en
end

Expand All @@ -80,7 +84,11 @@ def setup
begin
I18n.config = self
assert_equal self, I18n.config
assert_equal self, Fiber[:i18n_config]
if Fiber.respond_to?(:[])
assert_equal self, Fiber[:i18n_config]
else
assert_equal self, Thread.current.thread_variable_get(:i18n_config)
end
ensure
::I18n::Config.new.set!
end
Expand Down Expand Up @@ -593,6 +601,7 @@ def call(exception, locale, key, options); key; end
end

test "I18n.locale is isolated between concurrent Fibers" do
skip "Fiber storage requires Ruby 3.2+" unless Fiber.respond_to?(:[])
I18n.available_locales = [:en, :ja]
I18n.default_locale = :en
I18n.locale = :en
Expand All @@ -616,6 +625,8 @@ def call(exception, locale, key, options); key; end
end

test "I18n.locale write in child Fiber does not leak to parent" do
skip "Fiber storage requires Ruby 3.2+" unless Fiber.respond_to?(:[])

I18n.available_locales = [:en, :ja]
I18n.default_locale = :en
I18n.locale = :en
Expand Down