diff --git a/.rubocop_todo/lint/redundant_cop_disable_directive.yml b/.rubocop_todo/lint/redundant_cop_disable_directive.yml index 2c7bad0b3ca9181f1d971f00b59fa2b39ee78e62..0f0905bfeeeb89ac4c70dd493066a9d5e09d2d07 100644 --- a/.rubocop_todo/lint/redundant_cop_disable_directive.yml +++ b/.rubocop_todo/lint/redundant_cop_disable_directive.yml @@ -364,7 +364,6 @@ Lint/RedundantCopDisableDirective: - 'sidekiq_cluster/cli.rb' - 'sidekiq_cluster/sidekiq_cluster.rb' - 'spec/components/previews/pajamas/banner_component_preview.rb' - - 'spec/controllers/concerns/preferred_language_switcher_spec.rb' - 'spec/controllers/groups/milestones_controller_spec.rb' - 'spec/controllers/profiles/two_factor_auths_controller_spec.rb' - 'spec/controllers/projects/milestones_controller_spec.rb' diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml index 064767d68b18e8a71fd12d27e7b7e710ad940fac..e6f6154acc238f7c8b2be41db09f67dc688f5ade 100644 --- a/.rubocop_todo/rspec/feature_category.yml +++ b/.rubocop_todo/rspec/feature_category.yml @@ -1229,7 +1229,6 @@ RSpec/FeatureCategory: - 'spec/controllers/concerns/issuable_actions_spec.rb' - 'spec/controllers/concerns/issuable_collections_spec.rb' - 'spec/controllers/concerns/page_limiter_spec.rb' - - 'spec/controllers/concerns/preferred_language_switcher_spec.rb' - 'spec/controllers/concerns/project_unauthorized_spec.rb' - 'spec/controllers/concerns/redirects_for_missing_path_on_tree_spec.rb' - 'spec/controllers/concerns/renders_commits_spec.rb' diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml index 536591ff27f99959863f532f53b051e6cd8f3241..a72c233962c52d181a5ab78e1982dc88ee2291ba 100644 --- a/.rubocop_todo/rspec/named_subject.yml +++ b/.rubocop_todo/rspec/named_subject.yml @@ -1218,7 +1218,6 @@ RSpec/NamedSubject: - 'spec/controllers/concerns/check_rate_limit_spec.rb' - 'spec/controllers/concerns/graceful_timeout_handling_spec.rb' - 'spec/controllers/concerns/page_limiter_spec.rb' - - 'spec/controllers/concerns/preferred_language_switcher_spec.rb' - 'spec/controllers/concerns/product_analytics_tracking_spec.rb' - 'spec/controllers/concerns/renders_commits_spec.rb' - 'spec/controllers/concerns/routable_actions_spec.rb' diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml index 872ecd87635eede138e527bb199f53f19e8565fe..fd15c534ebe46acd8dea5421937a166e97e04b7b 100644 --- a/.rubocop_todo/style/inline_disable_annotation.yml +++ b/.rubocop_todo/style/inline_disable_annotation.yml @@ -2488,7 +2488,6 @@ Style/InlineDisableAnnotation: - 'spec/config/application_spec.rb' - 'spec/controllers/concerns/content_security_policy_patch_spec.rb' - 'spec/controllers/concerns/continue_params_spec.rb' - - 'spec/controllers/concerns/preferred_language_switcher_spec.rb' - 'spec/controllers/groups/milestones_controller_spec.rb' - 'spec/controllers/omniauth_callbacks_controller_spec.rb' - 'spec/controllers/profiles/two_factor_auths_controller_spec.rb' diff --git a/app/controllers/concerns/preferred_language_switcher.rb b/app/controllers/concerns/preferred_language_switcher.rb index 011bdbbca1053662dd192888194d6225b0821834..664c1717f7065c97b9f3dc89318995e717a381c1 100644 --- a/app/controllers/concerns/preferred_language_switcher.rb +++ b/app/controllers/concerns/preferred_language_switcher.rb @@ -15,7 +15,7 @@ def init_preferred_language def preferred_language cookies[:preferred_language].presence_in(Gitlab::I18n.available_locales) || - selectable_language(marketing_site_language) || + selectable_language(language_from_params) || selectable_language(browser_languages) || Gitlab::CurrentSettings.default_preferred_language end @@ -37,14 +37,9 @@ def browser_languages end strong_memoize_attr :browser_languages - def marketing_site_language - # Our marketing site will be the only thing we are sure of the language placement in the url for. - locale = params[:glm_source]&.match(%r{\A#{ApplicationHelper.promo_host}/([a-z]{2})-([a-z]{2})}i)&.captures - - return [] if locale.blank? - - # This is local and then locale_region - the marketing site will always send locale-region pairs like fr-fr. - [locale[0], "#{locale[0]}_#{locale[1]}"] + def language_from_params + # overridden in ee + [] end end diff --git a/ee/app/controllers/concerns/ee/preferred_language_switcher.rb b/ee/app/controllers/concerns/ee/preferred_language_switcher.rb new file mode 100644 index 0000000000000000000000000000000000000000..3a3121541c6a05c7c186ac01c3f48c536269d66a --- /dev/null +++ b/ee/app/controllers/concerns/ee/preferred_language_switcher.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module EE + module PreferredLanguageSwitcher + extend ::Gitlab::Utils::Override + + private + + def language_from_params + return super unless ::Gitlab::Saas.feature_available?(:marketing_site_language) + + # Our marketing site will be the only thing we are sure of the language placement in the url for. + glm_source = params.permit(:glm_source)[:glm_source] + locale = glm_source&.match(%r{\A#{::ApplicationHelper.promo_host}/([a-z]{2})-([a-z]{2})}i)&.captures + + return [] if locale.blank? + + # This is local and then locale_region - the marketing site will always send locale-region pairs like fr-fr. + [locale[0], "#{locale[0]}_#{locale[1]}"] + end + end +end diff --git a/ee/config/saas_features/marketing_site_language.yml b/ee/config/saas_features/marketing_site_language.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a30ffd985aae0045d39aceea377aa419bc6f019 --- /dev/null +++ b/ee/config/saas_features/marketing_site_language.yml @@ -0,0 +1,5 @@ +--- +name: marketing_site_language +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165093 +milestone: '17.5' +group: group::acquisition diff --git a/ee/lib/ee/gitlab/saas.rb b/ee/lib/ee/gitlab/saas.rb index b177f5b48a8d66f854cc1bdbc7dbcd8a036a6256..d86fcbf795ce89521cb84788be9cff56d9fe297c 100644 --- a/ee/lib/ee/gitlab/saas.rb +++ b/ee/lib/ee/gitlab/saas.rb @@ -12,6 +12,7 @@ module Saas ai_vertex_embeddings experimentation marketing_google_tag_manager + marketing_site_language namespaces_storage_limit onboarding purchases_additional_minutes diff --git a/ee/spec/controllers/concerns/ee/preferred_language_switcher_spec.rb b/ee/spec/controllers/concerns/ee/preferred_language_switcher_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..949e86c15c45ba3f3e834349191624e6fe1a3a92 --- /dev/null +++ b/ee/spec/controllers/concerns/ee/preferred_language_switcher_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe PreferredLanguageSwitcher, type: :controller, feature_category: :acquisition do + controller(ActionController::Base) do + include PreferredLanguageSwitcher + + before_action :init_preferred_language, only: :new + + def new + render html: 'new page' + end + end + + subject { cookies[:preferred_language] } + + before do + stub_feature_flags(disable_preferred_language_cookie: false) + end + + context 'when the marketing_site_language SaaS feature is available' do + before do + stub_saas_features(marketing_site_language: true) + end + + context 'when first visit' do + let(:glm_source) { 'about.gitlab.com' } + let(:accept_language_header) { nil } + + before do + request.env['HTTP_ACCEPT_LANGUAGE'] = accept_language_header + + get :new, params: { glm_source: glm_source } + end + + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } + + context 'when language param is valid' do + let(:glm_source) { 'about.gitlab.com/fr-fr/' } + + it { is_expected.to eq 'fr' } + + context 'for case insensitivity on language' do + let(:glm_source) { 'about.gitlab.com/fr-FR/' } + + it { is_expected.to eq 'fr' } + end + + context 'for case insensitivity on marketing site URL' do + let(:glm_source) { 'ABOUT.gitlab.com/fr-fr/' } + + it { is_expected.to eq 'fr' } + end + + context 'when language param is invalid' do + let(:glm_source) { 'about.gitlab.com/ko-ko/' } + + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } + end + + context 'when the glm_source is not the marketing site' do + let(:glm_source) { 'some.othersite.com/fr-fr/' } + + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } + end + end + + context 'when language params and language header are both valid' do + let(:accept_language_header) { 'zh-CN,zh;q=0.8,zh-TW;q=0.7' } + let(:glm_source) { 'about.gitlab.com/fr-fr/' } + + it { is_expected.to eq 'fr' } + end + end + end + + context 'when the marketing_site_language SaaS feature is not available' do + before do + stub_saas_features(marketing_site_language: false) + end + + context 'for first visit' do + let(:glm_source) { 'about.gitlab.com/fr-fr/' } + let(:accept_language_header) { nil } + + before do + request.env['HTTP_ACCEPT_LANGUAGE'] = accept_language_header + + get :new, params: { glm_source: glm_source } + end + + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } + end + end +end diff --git a/spec/controllers/concerns/preferred_language_switcher_spec.rb b/spec/controllers/concerns/preferred_language_switcher_spec.rb index 9f897b14a21e6c3b0617f791b8f467e91d3db8fa..23219388850a142503e32cab9d15c523b3778298 100644 --- a/spec/controllers/concerns/preferred_language_switcher_spec.rb +++ b/spec/controllers/concerns/preferred_language_switcher_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe PreferredLanguageSwitcher, type: :controller do +RSpec.describe PreferredLanguageSwitcher, type: :controller, feature_category: :acquisition do controller(ActionController::Base) do - include PreferredLanguageSwitcher # rubocop:disable RSpec/DescribedClass + include PreferredLanguageSwitcher before_action :init_preferred_language, only: :new @@ -19,100 +19,40 @@ def new stub_feature_flags(disable_preferred_language_cookie: false) end - context 'when first visit' do - let(:glm_source) { 'about.gitlab.com' } + context 'for first visit' do let(:accept_language_header) { nil } before do request.env['HTTP_ACCEPT_LANGUAGE'] = accept_language_header - get :new, params: { glm_source: glm_source } - end - - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language + get :new end - context 'when language param is valid' do - let(:glm_source) { 'about.gitlab.com/fr-fr/' } - - it 'sets preferred_language accordingly' do - expect(subject).to eq 'fr' - end - - context 'for case insensitivity on language' do - let(:glm_source) { 'about.gitlab.com/fr-FR/' } - - it 'sets preferred_language accordingly' do - expect(subject).to eq 'fr' - end - end - - context 'for case insensitivity on marketing site URL' do - let(:glm_source) { 'ABOUT.gitlab.com/fr-fr/' } - - it 'sets preferred_language accordingly' do - expect(subject).to eq 'fr' - end - end - - context 'when language param is invalid' do - let(:glm_source) { 'about.gitlab.com/ko-ko/' } - - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language - end - end - - context 'when the glm_source is not the marketing site' do - let(:glm_source) { 'some.othersite.com/fr-fr/' } - - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language - end - end - end + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } context 'when browser preferred language is not english' do context 'with selectable language' do let(:accept_language_header) { 'zh-CN,zh;q=0.8,zh-TW;q=0.7' } - it 'sets preferred_language accordingly' do - expect(subject).to eq 'zh_CN' - end + it { is_expected.to eq 'zh_CN' } end context 'with unselectable language' do let(:accept_language_header) { 'nl-NL;q=0.8' } - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language - end + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } end context 'with empty string in language header' do let(:accept_language_header) { '' } - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language - end + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } end context 'with language header without dashes' do let(:accept_language_header) { 'fr;q=8' } - it 'sets preferred_language accordingly' do - expect(subject).to eq 'fr' - end - end - end - - context 'when language params and language header are both valid' do - let(:accept_language_header) { 'zh-CN,zh;q=0.8,zh-TW;q=0.7' } - let(:glm_source) { 'about.gitlab.com/fr-fr/' } - - it 'sets preferred_language according to language params' do - expect(subject).to eq 'fr' + it { is_expected.to eq 'fr' } end end end @@ -129,17 +69,13 @@ def new context 'with a valid value' do let(:user_preferred_language) { 'zh_CN' } - it 'keeps preferred language unchanged' do - expect(subject).to eq user_preferred_language - end + it { is_expected.to eq user_preferred_language } end context 'with an invalid value' do let(:user_preferred_language) { 'xxx' } - it 'sets preferred_language to default' do - expect(subject).to eq Gitlab::CurrentSettings.default_preferred_language - end + it { is_expected.to eq Gitlab::CurrentSettings.default_preferred_language } end end @@ -149,8 +85,6 @@ def new get :new end - it 'does not set the cookie' do - expect(subject).to be_nil - end + it { is_expected.to be_nil } end end