Skip to content

Commit

Permalink
Ensure the underscore locale is included in i18n fallbacks
Browse files Browse the repository at this point in the history
When a site has an underscore locale (e.g. "fr_BE") set as the default,
it is not included in the I18n fallbacks for non-default locales.

    I18n.fallbacks
    # => { :fr_BE => [:fr_BE, :"fr-BE", :fr],
    #      :nl_BE => [:nl_BE, :"fr-BE", :fr] }

    AlaveteliLocalization.with_locale(:fr_BE) { Globalize.fallbacks }
    # => [:fr_BE, :"fr-BE", :fr]
    AlaveteliLocalization.with_locale(:nl_BE) { Globalize.fallbacks }
    # => [:nl_BE, :"fr-BE", :fr]

In the example above, this means that a missing model translation when
using the "nl_BE" locale will **not** correctly fall back to the
translation for the default locale ("fr_BE").

This commit ensures that the default locale is always available in the
fallbacks for non-default locales. We try to add it before the
non-underscore version so that the regional translation is preferred,
with the non-regional fallback available as a last-ditch attempt to find
a useful translation.

Globalize model attributes fallbacks specs contributed by @gbp.

Fixes #5382
Fixes #5895
  • Loading branch information
garethrees committed Sep 25, 2020
1 parent 17d1443 commit bdf8423
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 6 deletions.
9 changes: 8 additions & 1 deletion lib/alaveteli_localization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ def set_fast_gettext_locales(available, default)
end

def set_i18n_locales(available, default)
# Make all locales and their fallbacks available
I18n.available_locales =
available.flat_map(&:self_and_parents).map(&:to_sym).uniq
available.flat_map(&:i18n_fallbacks).map(&:to_sym).uniq

# Configure the specific fallbacks for each locale
available.each do |locale|
I18n.fallbacks[locale.to_sym] = locale.i18n_fallbacks(default)
end

# Set the current locale as the default locale
I18n.locale = I18n.default_locale = default.hyphenate.to_sym
end

Expand Down
6 changes: 6 additions & 0 deletions lib/alaveteli_localization/hyphenated_locale.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
class AlaveteliLocalization
# Handle transformations of a hyphenated Locale identifier (e.g. "en-GB").
class HyphenatedLocale < Locale
def self_and_parents
without_canonicalized = super
index = without_canonicalized.find_index(self) + 1
without_canonicalized.insert(index, canonicalize)
end

private

def split
Expand Down
7 changes: 6 additions & 1 deletion lib/alaveteli_localization/locale.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,18 @@ def hyphenate
self.class.parse(split.join('-'))
end

# Note that self_and_parents only uses hyphenated locales
def self_and_parents
I18n::Locale::Tag::Simple.new(hyphenate).
self_and_parents.
map { |tag| self.class.parse(tag) }
end

def i18n_fallbacks(default = nil)
default = self.class.parse(default) if default
(self_and_parents | Array(default&.i18n_fallbacks)).
compact.map(&:to_sym).uniq
end

def to_s
identifier
end
Expand Down
4 changes: 4 additions & 0 deletions lib/alaveteli_localization/underscorred_locale.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class AlaveteliLocalization
# Handle transformations of un anderscorred Locale identifier (e.g. "en_GB").
class UnderscorredLocale < Locale
def self_and_parents
super.prepend(canonicalize)
end

private

def split
Expand Down
29 changes: 28 additions & 1 deletion spec/lib/alaveteli_localization/hyphenated_locale_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,34 @@

describe '#self_and_parents' do
subject { described_class.new(identifier).self_and_parents }
it { is_expected.to eq(%w[en-GB en]) }
it { is_expected.to eq(%w[en-GB en_GB en]) }
end

describe '#i18n_fallbacks' do
context 'without a default_locale' do
subject { described_class.new(identifier).i18n_fallbacks }
it { is_expected.to eq(%i[en-GB en_GB en]) }
end

context 'with a default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr') }
it { is_expected.to eq(%i[en-GB en_GB en fr]) }
end

context 'with the default locale given to default_locale' do
subject { described_class.new(identifier).i18n_fallbacks(identifier) }
it { is_expected.to eq(%i[en-GB en_GB en]) }
end

context 'with a hyphenated default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr-BE') }
it { is_expected.to eq(%i[en-GB en_GB en fr-BE fr_BE fr]) }
end

context 'with an underscorred default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr_BE') }
it { is_expected.to eq(%i[en-GB en_GB en fr_BE fr-BE fr]) }
end
end

describe '#to_s' do
Expand Down
27 changes: 27 additions & 0 deletions spec/lib/alaveteli_localization/locale_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,33 @@
it { is_expected.to eq(%w[en]) }
end

describe '#i18n_fallbacks' do
context 'without a default_locale' do
subject { described_class.new('en').i18n_fallbacks }
it { is_expected.to eq(%i[en]) }
end

context 'with a default_locale' do
subject { described_class.new('en').i18n_fallbacks('fr') }
it { is_expected.to eq(%i[en fr]) }
end

context 'with the default locale given to default_locale' do
subject { described_class.new('en').i18n_fallbacks('en') }
it { is_expected.to eq(%i[en]) }
end

context 'with a hyphenated default_locale' do
subject { described_class.new('en').i18n_fallbacks('fr-BE') }
it { is_expected.to eq(%i[en fr-BE fr_BE fr]) }
end

context 'with an underscorred default_locale' do
subject { described_class.new('en').i18n_fallbacks('fr_BE') }
it { is_expected.to eq(%i[en fr_BE fr-BE fr]) }
end
end

describe '#to_s' do
subject { described_class.new('en').to_s }
it { is_expected.to eq('en') }
Expand Down
30 changes: 28 additions & 2 deletions spec/lib/alaveteli_localization/underscorred_locale_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,34 @@

describe '#self_and_parents' do
subject { described_class.new(identifier).self_and_parents }
# Note that self_and_parents only uses hyphenated locales
it { is_expected.to eq(%w[en-GB en]) }
it { is_expected.to eq(%w[en_GB en-GB en]) }
end

describe '#i18n_fallbacks' do
context 'without a default_locale' do
subject { described_class.new(identifier).i18n_fallbacks }
it { is_expected.to eq(%i[en_GB en-GB en]) }
end

context 'with a default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr') }
it { is_expected.to eq(%i[en_GB en-GB en fr]) }
end

context 'with the default locale given to default_locale' do
subject { described_class.new(identifier).i18n_fallbacks(identifier) }
it { is_expected.to eq(%i[en_GB en-GB en]) }
end

context 'with a hyphenated default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr-BE') }
it { is_expected.to eq(%i[en_GB en-GB en fr-BE fr_BE fr]) }
end

context 'with an underscorred default_locale' do
subject { described_class.new(identifier).i18n_fallbacks('fr_BE') }
it { is_expected.to eq(%i[en_GB en-GB en fr_BE fr-BE fr]) }
end
end

describe '#to_s' do
Expand Down
48 changes: 47 additions & 1 deletion spec/lib/alaveteli_localization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,55 @@
end

it 'sets I18n.available_locales' do
expect(I18n.available_locales).to eq(%i[en-GB en es])
expect(I18n.available_locales).to eq(%i[en_GB en-GB en es])
end

it 'sets correct fallbacks when using an underscore default locale' do
AlaveteliLocalization.set_locales('en_RW rw', 'en_RW')
expect(I18n.fallbacks[:en_RW]).to eq(%i[en_RW en-RW en])
expect(I18n.fallbacks[:rw]).to eq(%i[rw en_RW en-RW en])
end
end

context 'when dealing with Globalize' do
it 'sets correct fallbacks when using an underscore default locale' do
AlaveteliLocalization.set_locales('en_RW rw', 'en_RW')
expect(Globalize.fallbacks(:en_RW)).to eq(%i[en_RW en-RW en])
expect(Globalize.fallbacks(:rw)).to eq(%i[rw en_RW en-RW en])
end
end

context 'Globalize model attributes fallbacks' do
before do
AlaveteliLocalization.set_locales('en_RW rw', 'en_RW')
end

let(:body_with_default_locale) do
AlaveteliLocalization.with_locale('en_RW') do
FactoryBot.create(:public_body, url_name: 'default')
end
end

let(:body_with_non_default_locale) do
AlaveteliLocalization.with_locale('rw') do
FactoryBot.create(:public_body, url_name: 'non_default')
end
end

context 'when using non-default locale' do
it 'falls back to the default locale attribute' do
AlaveteliLocalization.with_locale('rw') do
expect(body_with_default_locale.url_name).to eq 'default'
end
end

it 'returns the translated non-default locale attribute' do
AlaveteliLocalization.with_locale('rw') do
expect(body_with_non_default_locale.url_name).to eq 'non_default'
end
end

end
end

context 'when translating' do
Expand Down

0 comments on commit bdf8423

Please sign in to comment.