From d1d15c76f9233954530ed832c5987d5cf33b0263 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 16 Sep 2026 00:13:37 +0930 Subject: [PATCH] fix i18n cache on about/guidelines --- app/Http/Controllers/SiteController.php | 13 ++++++-- tests/Feature/LocalizationTest.php | 41 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index 1ef10496d..02f336286 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -61,7 +61,12 @@ class SiteController extends Controller public function about() { - return Cache::remember('site.about_v2', now()->addMinutes(15), function () { + // Scope the cache key by locale: the rendered view contains many + // translated site.* strings, so a single shared key would let one + // locale's render be served to visitors of other locales. + $cacheKey = 'site.about_v2:'.app()->getLocale(); + + return Cache::remember($cacheKey, now()->addMinutes(15), function () { $user_count = number_format(User::count()); $post_count = number_format(StatusService::totalLocalStatuses()); $rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null; @@ -77,7 +82,11 @@ class SiteController extends Controller public function communityGuidelines(Request $request) { - return Cache::remember('site:help:community-guidelines', now()->addDays(120), function () { + // Scope by locale: the rendered layout contains translated strings, + // so a shared key would leak one locale's render to other locales. + $cacheKey = 'site:help:community-guidelines:'.app()->getLocale(); + + return Cache::remember($cacheKey, now()->addMinutes(15), function () { $slug = '/site/kb/community-guidelines'; $page = Page::whereSlug($slug)->whereActive(true)->first(); diff --git a/tests/Feature/LocalizationTest.php b/tests/Feature/LocalizationTest.php index 713398c91..939f611e6 100644 --- a/tests/Feature/LocalizationTest.php +++ b/tests/Feature/LocalizationTest.php @@ -4,6 +4,7 @@ use App\Models\User; use App\Util\Localization\Localization; use Illuminate\Auth\Events\Login; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; @@ -278,6 +279,46 @@ describe('language settings via /settings/home', function () { }); }); +describe('locale-aware caching of rendered site pages', function () { + beforeEach(function () { + Cache::flush(); + }); + + it('caches /site/about under a locale-scoped key', function () { + $this->withSession(['locale' => 'en-US']) + ->get('/site/about') + ->assertOk(); + + expect(Cache::has('site.about_v2:en-US'))->toBeTrue() + // The old, locale-unaware key must not be used. + ->and(Cache::has('site.about_v2'))->toBeFalse(); + }); + + it('does not let a non-default locale poison the cached render of another', function () { + $other = firstNonDefaultLocale(); + + // A visitor on a non-default locale warms the cache first (cold cache). + $this->withSession(['locale' => $other]) + ->get('/site/about') + ->assertOk(); + + // Then an en-US visitor: must get its own cache entry, not the + // other locale's render. + $this->withSession(['locale' => 'en-US']) + ->get('/site/about') + ->assertOk(); + + // Each locale gets its own cache entry, so the first (non-default) + // render cannot overwrite / be served as the en-US render. We assert + // key isolation rather than content difference, since a partially + // translated locale may legitimately render identically to English. + expect(Cache::has('site.about_v2:'.$other))->toBeTrue() + ->and(Cache::has('site.about_v2:en-US'))->toBeTrue() + // The old shared key must never be written. + ->and(Cache::has('site.about_v2'))->toBeFalse(); + })->skip(fn () => firstNonDefaultLocale() === 'en-US', 'needs a second locale'); +}); + describe('empty string translation fallback', function () { it('falls back to the fallback locale for empty (untranslated) strings', function () { Config::set('app.fallback_locale', 'en-US');