FIX: Include resolved locale in anonymous cache key (#10289)

FIX: Include resolved locale in anonymous cache key (#10289)

This only applies when set_locale_from_accept_language_header is enabled

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e142ac0..ea11922 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -530,17 +530,7 @@ class ApplicationController < ActionController::Base
   private
 
   def locale_from_header
-    begin
-      # Rails I18n uses underscores between the locale and the region; the request
-      # headers use hyphens.
-      require 'http_accept_language' unless defined? HttpAcceptLanguage
-      available_locales = I18n.available_locales.map { |locale| locale.to_s.tr('_', '-') }
-      parser = HttpAcceptLanguage::Parser.new(request.env["HTTP_ACCEPT_LANGUAGE"])
-      parser.language_region_compatible_from(available_locales).tr('-', '_')
-    rescue
-      # If Accept-Language headers are not set.
-      I18n.default_locale
-    end
+    HttpLanguageParser.parse(request.env["HTTP_ACCEPT_LANGUAGE"])
   end
 
   def preload_anonymous_data
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index bc281ef..0ed445e 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1425,7 +1425,7 @@ en:
     delete_old_hidden_posts: "Auto-delete any hidden posts that stay hidden for more than 30 days."
     default_locale: "The default language of this Discourse instance. You can replace the text of system generated categories and topics at <a href='%{base_path}/admin/customize/site_texts' target='_blank'>Customize / Text</a>."
     allow_user_locale: "Allow users to choose their own language interface preference"
-    set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers. (EXPERIMENTAL, does not work with anonymous cache)"
+    set_locale_from_accept_language_header: "set interface language for anonymous users from their web browser's language headers"
     support_mixed_text_direction: "Support mixed left-to-right and right-to-left text directions."
     min_post_length: "Minimum allowed post length in characters"
     min_first_post_length: "Minimum allowed first post (topic body) length in characters"
diff --git a/lib/http_language_parser.rb b/lib/http_language_parser.rb
new file mode 100644
index 0000000..debfddc
--- /dev/null
+++ b/lib/http_language_parser.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module HttpLanguageParser
+  def self.parse(header)
+    # Rails I18n uses underscores between the locale and the region; the request
+    # headers use hyphens.
+    require 'http_accept_language' unless defined? HttpAcceptLanguage
+    available_locales = I18n.available_locales.map { |locale| locale.to_s.tr('_', '-') }
+    parser = HttpAcceptLanguage::Parser.new(header)
+    matched = parser.language_region_compatible_from(available_locales)&.tr('-', '_')
+    matched || SiteSetting.default_locale
+  end
+end
diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb
index 8f1e756..4e9b74b 100644
--- a/lib/middleware/anonymous_cache.rb
+++ b/lib/middleware/anonymous_cache.rb
@@ -3,6 +3,7 @@
 require_dependency "mobile_detection"
 require_dependency "crawler_detection"
 require_dependency "guardian"
+require_dependency "http_language_parser"
 
 module Middleware
   class AnonymousCache
@@ -13,7 +14,8 @@ module Middleware
         c: 'key_is_crawler?',
         b: 'key_has_brotli?',
         t: 'key_cache_theme_ids',
-        ca: 'key_compress_anon'
+        ca: 'key_compress_anon',
+        l: 'key_locale'
       }
     end
 
@@ -89,6 +91,14 @@ module Middleware
         @has_brotli == :true
       end
 
+      def key_locale
+        if SiteSetting.set_locale_from_accept_language_header
+          HttpLanguageParser.parse(@env["HTTP_ACCEPT_LANGUAGE"])
+        else
+          "" # No need to key, it is the same for all anon users
+        end
+      end
+
       def is_crawler?
         @is_crawler ||=
           begin
diff --git a/spec/components/middleware/anonymous_cache_spec.rb b/spec/components/middleware/anonymous_cache_spec.rb
index d76def0..d6dcdb2 100644
--- a/spec/components/middleware/anonymous_cache_spec.rb
+++ b/spec/components/middleware/anonymous_cache_spec.rb
@@ -46,6 +46,32 @@ describe Middleware::AnonymousCache::Helper do
     end
   end
 
+  context "with header-based locale locale" do
+    it "handles different languages" do
+      # Normally does not check the language header
+      french1 = new_helper("HTTP_ACCEPT_LANGUAGE" => "fr").cache_key
+      french2 = new_helper("HTTP_ACCEPT_LANGUAGE" => "FR").cache_key
+      english = new_helper("HTTP_ACCEPT_LANGUAGE" => SiteSetting.default_locale).cache_key
+      none = new_helper.cache_key
+
+      expect(none).to eq(french1)
+      expect(none).to eq(french2)
+      expect(none).to eq(english)
+
+      SiteSetting.allow_user_locale = true
+      SiteSetting.set_locale_from_accept_language_header = true
+
+      french1 = new_helper("HTTP_ACCEPT_LANGUAGE" => "fr").cache_key
+      french2 = new_helper("HTTP_ACCEPT_LANGUAGE" => "FR").cache_key
+      english = new_helper("HTTP_ACCEPT_LANGUAGE" => SiteSetting.default_locale).cache_key
+      none = new_helper.cache_key
+
+      expect(none).to eq(english)
+      expect(french1).to eq(french2)
+      expect(french1).not_to eq(none)
+    end
+  end
+
   context 'force_anonymous!' do
     before do
       RateLimiter.enable

GitHub sha: c09b5807

This commit appears in #10289 which was approved by eviltrout. It was merged by davidtaylorhq.