Allow different default locales per tenant

Note that, for everything to work consistently, we need to make sure
that the default locale is one of the available locales.

Also note that we aren't overwriting the `#save ` method set by
globalize. I didn't feel too comfortable changing a monkey-patch which
ideally shouldn't be there in the first place, I haven't found a case
where `Globalize.locale` is `nil` (since it defaults to `I18n.locale`,
which should never be `nil`), so using `I18n.default_locale` probably
doesn't affect us.
This commit is contained in:
Javi Martín
2024-03-04 20:03:25 +01:00
parent 722e50a669
commit 6de4737b70
22 changed files with 156 additions and 26 deletions

View File

@@ -56,7 +56,7 @@ class ApplicationController < ActionController::Base
elsif Setting.enabled_locales.include?(session[:locale]&.to_sym)
session[:locale]
else
I18n.default_locale
Setting.default_locale
end
end

View File

@@ -44,7 +44,7 @@ class Management::BaseController < ActionController::Base
session[:locale] = params[:locale].to_s
end
session[:locale] ||= I18n.default_locale.to_s
session[:locale] ||= Setting.default_locale.to_s
I18n.with_locale(session[:locale], &action)
end

View File

@@ -37,7 +37,7 @@ module SearchDictionarySelector
private
def find_from_i18n_default
key_to_lookup = I18n.default_locale.to_s.split("-").first.to_sym
key_to_lookup = Setting.default_locale.to_s.split("-").first.to_sym
dictionary = I18N_TO_DICTIONARY[key_to_lookup]
dictionary ||= "simple"

View File

@@ -76,7 +76,7 @@ class Mailer < ApplicationMailer
def user_invite(email)
@email_to = email
I18n.with_locale(I18n.default_locale) do
I18n.with_locale(Setting.default_locale) do
mail(to: @email_to, subject: t("mailers.user_invite.subject", org_name: Setting["org_name"]))
end
end

View File

@@ -82,7 +82,7 @@ module Globalizable
translation_class.instance_eval do
validates method,
length: options[:length],
if: lambda { |translation| translation.locale == I18n.default_locale }
if: lambda { |translation| translation.locale == Setting.default_locale }
end
if options.count > 1
translation_class.instance_eval do

View File

@@ -96,6 +96,7 @@ class Setting < ApplicationRecord
# Code to be included at the top (inside <head>) of every page (useful for tracking)
"html.per_page_code_head": "",
"locales.enabled": nil,
"locales.default": nil,
"map.latitude": 51.48,
"map.longitude": 0.0,
"map.zoom": 10,
@@ -224,7 +225,16 @@ class Setting < ApplicationRecord
def enabled_locales
locales = Setting["locales.enabled"].to_s.split.map(&:to_sym)
(locales & I18n.available_locales).presence || I18n.available_locales
[
default_locale,
*((locales & I18n.available_locales).presence || I18n.available_locales)
].uniq
end
def default_locale
locale = Setting["locales.default"].to_s.strip.to_sym
([locale] & I18n.available_locales).first || I18n.default_locale
end
end
end

View File

@@ -339,7 +339,7 @@ class User < ApplicationRecord
end
def locale
self[:locale] || I18n.default_locale.to_s
self[:locale] || Setting.default_locale.to_s
end
def confirmation_required?

View File

@@ -24,7 +24,7 @@ end
def random_locales
[
I18n.default_locale,
Setting.default_locale,
*(Setting.enabled_locales & %i[en es]),
*Setting.enabled_locales.sample(4)
].uniq.take(5)

View File

@@ -8,6 +8,7 @@ describe Admin::SiteCustomization::ContentBlocks::FormContentBlockComponent do
end
it "only includes enabled settings" do
Setting["locales.default"] = "de"
Setting["locales.enabled"] = "de fr"
render_inline component

View File

@@ -8,6 +8,7 @@ describe Admin::SiteCustomization::ContentBlocks::FormHeadingContentBlockCompone
end
it "only includes enabled settings" do
Setting["locales.default"] = "de"
Setting["locales.enabled"] = "de fr"
render_inline component

View File

@@ -101,6 +101,7 @@ describe Layout::LocaleSwitcherComponent do
context "when not all available locales are enabled" do
before do
allow(I18n).to receive(:available_locales).and_return(%i[en es fr])
Setting["locales.default"] = "es"
Setting["locales.enabled"] = "es fr"
end

View File

@@ -23,9 +23,11 @@ describe ApplicationController do
describe "#switch_locale" do
it "uses the default locale by default" do
Setting["locales.default"] = "pt-BR"
get :index
expect(response.body).to eq "en"
expect(response.body).to eq "pt-BR"
end
it "uses the locale in the parameters when it's there" do

View File

@@ -34,9 +34,11 @@ describe Management::BaseController do
describe "#switch_locale" do
it "uses the default locale by default" do
Setting["locales.default"] = "pt-BR"
get :index
expect(response.body).to eq "en"
expect(response.body).to eq "pt-BR"
end
it "uses the locale in the parameters when it's there" do

View File

@@ -29,13 +29,14 @@ describe SubscriptionsController do
end
it "only accepts enabled locales" do
Setting["locales.enabled"] = "en nl"
Setting["locales.default"] = "fr"
Setting["locales.enabled"] = "fr nl"
create(:user, locale: "es", subscriptions_token: "mytoken")
get :edit, params: { token: "mytoken" }
expect(session[:locale]).to eq "en"
expect(session[:locale]).to eq "fr"
end
end
end

View File

@@ -3,27 +3,23 @@ require "rails_helper"
describe SearchDictionarySelector do
context "from I18n default locale" do
before { allow(subject).to receive(:call).and_call_original }
around do |example|
original_i18n_default = I18n.default_locale
begin
example.run
ensure
I18n.default_locale = original_i18n_default
end
end
it "returns correct dictionary for simple locale" do
I18n.default_locale = :es
Setting["locales.default"] = "es"
expect(subject.call).to eq("spanish")
end
it "returns correct dictionary for compound locale" do
I18n.default_locale = :"pt-BR"
Setting["locales.default"] = "pt-BR"
expect(subject.call).to eq("portuguese")
end
it "returns simple for unsupported locale" do
expect(I18n).to receive(:default_locale).and_return(:pl) # avoiding I18n::InvalidLocale
allow(I18n).to receive(:available_locales).and_return(%i[en pl])
Setting["locales.default"] = "pl"
expect(subject.call).to eq("simple")
end
end

View File

@@ -30,6 +30,16 @@ describe Mailer do
end
end
describe "#user_invite" do
it "uses the default locale setting" do
Setting["locales.default"] = "es"
Mailer.user_invite("invited@consul.dev").deliver_now
expect(ActionMailer::Base.deliveries.last.body.to_s).to match "<html lang=\"es\""
end
end
describe "#manage_subscriptions_token" do
let(:user) { create(:user) }
let(:proposal) { create(:proposal, author: user) }

View File

@@ -21,6 +21,7 @@ describe Budget::ContentBlock do
end
it "is not valid with a disabled locale" do
Setting["locales.default"] = "pt-BR"
Setting["locales.enabled"] = "nl pt-BR"
block.locale = "en"

View File

@@ -0,0 +1,47 @@
require "rails_helper"
describe Globalizable do
before do
dummy_banner = Class.new(ApplicationRecord) do
def self.name
"DummyBanner"
end
self.table_name = "banners"
translates :title, touch: true
include Globalizable
has_many :translations, class_name: "DummyBanner::Translation", foreign_key: "banner_id"
validates_translation :title, length: { minimum: 7 }
end
stub_const("DummyBanner", dummy_banner)
end
describe ".validates_translation" do
it "validates length for the default locale" do
Setting["locales.default"] = "es"
dummy = DummyBanner.new
dummy.translations.build(locale: "es", title: "Short")
dummy.translations.build(locale: "fr", title: "Long enough")
I18n.with_locale(:fr) do
expect(dummy).not_to be_valid
end
end
it "does not validate length for other locales" do
Setting["locales.default"] = "es"
dummy = DummyBanner.new
dummy.translations.build(locale: "es", title: "Long enough")
dummy.translations.build(locale: "fr", title: "Long enough")
dummy.translations.build(locale: "en", title: "Short")
I18n.with_locale(:fr) do
expect(dummy).to be_valid
end
end
end
end

View File

@@ -160,6 +160,7 @@ RSpec.describe I18nContent do
end
it "does not store new keys for disabled translations" do
Setting["locales.default"] = "es"
Setting["locales.enabled"] = "es"
I18nContent.update([{ id: "shared.yes", values: { "value_en" => "Oh, yeah" }}])

View File

@@ -260,7 +260,7 @@ describe Setting do
end
describe ".available_locales" do
before { allow(I18n).to receive(:available_locales).and_return(%i[de en es pt-BR]) }
before { allow(I18n).to receive_messages(default_locale: :de, available_locales: %i[de en es pt-BR]) }
it "uses I18n available locales by default" do
Setting["locales.enabled"] = ""
@@ -280,6 +280,12 @@ describe Setting do
expect(Setting.enabled_locales).to eq %i[de en pt-BR]
end
it "adds the default locale to the list of available locales" do
Setting["locales.enabled"] = "en es"
expect(Setting.enabled_locales).to eq %i[de en es]
end
it "ignores extra whitespace between locales" do
Setting["locales.enabled"] = " de en pt-BR "
@@ -293,9 +299,9 @@ describe Setting do
end
it "ignores words that don't make sense in this context" do
Setting["locales.enabled"] = "yes es 1234 en SuperCool"
Setting["locales.enabled"] = "yes de 1234 en SuperCool"
expect(Setting.enabled_locales).to eq %i[es en]
expect(Setting.enabled_locales).to eq %i[de en]
end
it "uses I18n available locales when no locale is available" do
@@ -304,4 +310,44 @@ describe Setting do
expect(Setting.enabled_locales).to eq %i[de en es pt-BR]
end
end
describe ".default_locale" do
before { allow(I18n).to receive_messages(default_locale: :en, available_locales: %i[de en es pt-BR]) }
it "uses I18n default locale by default" do
Setting["locales.default"] = ""
expect(Setting.default_locale).to eq :en
end
it "allows defining the default locale" do
Setting["locales.default"] = "de"
expect(Setting.default_locale).to eq :de
end
it "handles locales which include a dash" do
Setting["locales.default"] = "pt-BR"
expect(Setting.default_locale).to eq :"pt-BR"
end
it "ignores extra whitespace in the locale name" do
Setting["locales.default"] = " es "
expect(Setting.default_locale).to eq :es
end
it "ignores locales which aren't available" do
Setting["locales.default"] = "fr"
expect(Setting.default_locale).to eq :en
end
it "ignores an array of several locales" do
Setting["locales.default"] = "de es"
expect(Setting.default_locale).to eq :en
end
end
end

View File

@@ -29,6 +29,7 @@ RSpec.describe SiteCustomization::ContentBlock do
end
it "is not valid with a disabled locale" do
Setting["locales.default"] = "nl"
Setting["locales.enabled"] = "nl pt-BR"
block.locale = "en"

View File

@@ -81,6 +81,16 @@ describe User do
end
end
describe "#locale" do
it "defaults to the default locale setting" do
Setting["locales.default"] = "nl"
user = build(:user, locale: nil)
expect(user.locale).to eq "nl"
end
end
describe "preferences" do
describe "email_on_comment" do
it "is false by default" do