From a513f152ed037cdf8f4666de53259e233bdeab61 Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 6 Mar 2025 18:24:46 -0800 Subject: [PATCH] fix: Extend the locale without variant check for article locales as well (#11021) We allow users to select locale variants when creating the help center (e.g., pt_BR or en_UK). However, the selected variant may not always be available for translation in the app. In such cases, we need to fall back to either the base language or the default locale. While this fallback logic was implemented for the portal locale, it was missing for article locales. This PR fixes that issue. --- app/controllers/concerns/switch_locale.rb | 33 +++++---- .../public/api/v1/portals/base_controller.rb | 23 +++--- spec/models/concerns/switch_locale_spec.rb | 74 +++++++++++++++++++ 3 files changed, 102 insertions(+), 28 deletions(-) create mode 100644 spec/models/concerns/switch_locale_spec.rb diff --git a/app/controllers/concerns/switch_locale.rb b/app/controllers/concerns/switch_locale.rb index 3013ff3cc..a8ea8ae05 100644 --- a/app/controllers/concerns/switch_locale.rb +++ b/app/controllers/concerns/switch_locale.rb @@ -5,10 +5,11 @@ module SwitchLocale def switch_locale(&) # priority is for locale set in query string (mostly for widget/from js sdk) - locale ||= locale_from_params + locale ||= params[:locale] + locale ||= locale_from_custom_domain # if locale is not set in account, let's use DEFAULT_LOCALE env variable - locale ||= locale_from_env_variable + locale ||= ENV.fetch('DEFAULT_LOCALE', nil) set_locale(locale, &) end @@ -32,26 +33,30 @@ module SwitchLocale end def set_locale(locale, &) - # if locale is empty, use default_locale - locale ||= I18n.default_locale + safe_locale = validate_and_get_locale(locale) # Ensure locale won't bleed into other requests # https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests - I18n.with_locale(locale, &) + I18n.with_locale(safe_locale, &) end - def locale_from_params - I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil + def validate_and_get_locale(locale) + return I18n.default_locale.to_s if locale.blank? + + available_locales = I18n.available_locales.map(&:to_s) + locale_without_variant = locale.split('_')[0] + + if available_locales.include?(locale) + locale + elsif available_locales.include?(locale_without_variant) + locale_without_variant + else + I18n.default_locale.to_s + end end def locale_from_account(account) return unless account - I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil - end - - def locale_from_env_variable - return unless ENV.fetch('DEFAULT_LOCALE', nil) - - I18n.available_locales.map(&:to_s).include?(ENV.fetch('DEFAULT_LOCALE')) ? ENV.fetch('DEFAULT_LOCALE') : nil + account.locale end end diff --git a/app/controllers/public/api/v1/portals/base_controller.rb b/app/controllers/public/api/v1/portals/base_controller.rb index f6c10f7c4..66b052b1e 100644 --- a/app/controllers/public/api/v1/portals/base_controller.rb +++ b/app/controllers/public/api/v1/portals/base_controller.rb @@ -1,4 +1,6 @@ class Public::Api::V1::Portals::BaseController < PublicController + include SwitchLocale + before_action :show_plain_layout before_action :set_color_scheme before_action :set_global_config @@ -27,14 +29,7 @@ class Public::Api::V1::Portals::BaseController < PublicController end def switch_locale_with_portal(&) - locale_without_variant = params[:locale].split('_')[0] - is_locale_available = I18n.available_locales.map(&:to_s).include?(params[:locale]) - is_locale_variant_available = I18n.available_locales.map(&:to_s).include?(locale_without_variant) - if is_locale_available - @locale = params[:locale] - elsif is_locale_variant_available - @locale = locale_without_variant - end + @locale = validate_and_get_locale(params[:locale]) I18n.with_locale(@locale, &) end @@ -44,12 +39,12 @@ class Public::Api::V1::Portals::BaseController < PublicController Rails.logger.info "Article: not found for slug: #{params[:article_slug]}" render_404 && return if article.blank? - @locale = if article.category.present? - article.category.locale - else - article.portal.default_locale - end - + article_locale = if article.category.present? + article.category.locale + else + article.portal.default_locale + end + @locale = validate_and_get_locale(article_locale) I18n.with_locale(@locale, &) end diff --git a/spec/models/concerns/switch_locale_spec.rb b/spec/models/concerns/switch_locale_spec.rb new file mode 100644 index 000000000..98628b92c --- /dev/null +++ b/spec/models/concerns/switch_locale_spec.rb @@ -0,0 +1,74 @@ +require 'rails_helper' + +RSpec.describe 'SwitchLocale Concern', type: :controller do + controller(ApplicationController) do + include SwitchLocale + + def index + switch_locale { render plain: I18n.locale } + end + + def account_locale + switch_locale_using_account_locale { render plain: I18n.locale } + end + end + + let(:account) { create(:account, locale: 'es') } + let(:portal) { create(:portal, custom_domain: 'custom.example.com', config: { default_locale: 'fr_FR' }) } + + describe '#switch_locale' do + context 'when locale is provided in params' do + it 'sets locale from params' do + get :index, params: { locale: 'es' } + expect(response.body).to eq('es') + end + + it 'falls back to default locale if invalid' do + get :index, params: { locale: 'invalid' } + expect(response.body).to eq('en') + end + end + + context 'when request is from custom domain' do + before { request.host = portal.custom_domain } + + it 'sets locale from portal' do + get :index + expect(response.body).to eq('fr') + end + + it 'overrides portal locale with param' do + get :index, params: { locale: 'es' } + expect(response.body).to eq('es') + end + end + + context 'when locale is not provided anywhere' do + it 'sets locale from environment variable' do + with_modified_env(DEFAULT_LOCALE: 'de_DE') do + get :index + expect(response.body).to eq('de') + end + end + + it 'falls back to default locale if env locale invalid' do + with_modified_env(DEFAULT_LOCALE: 'invalid') do + get :index + expect(response.body).to eq('en') + end + end + end + end + + describe '#switch_locale_using_account_locale' do + before do + routes.draw { get 'account_locale' => 'anonymous#account_locale' } + end + + it 'sets locale from account' do + controller.instance_variable_set(:@current_account, account) + get :account_locale + expect(response.body).to eq('es') + end + end +end