Skip to content

Commit c372fbe

Browse files
Document i18n cache key prefix and configurable cache config (5.next) (#8289)
* Add docs for i18n cache key prefix and configurable cache config Documents the new `TranslatorRegistry::setCacheKeyPrefix()`, `clearInMemoryRegistry()` and `I18n::setCacheConfig()` APIs added in 5.4.0, with the multi-tenant translation cache isolation use case as the worked example. Also adds entries to the 5.4 migration guide. * Update docs for renamed `TranslatorRegistry::clear()` method Reflects the rename from `clearInMemoryRegistry()` after review feedback. * Update docs/en/core-libraries/internationalization-and-localization.md Co-authored-by: Mark Story <mark@mark-story.com> --------- Co-authored-by: Mark Story <mark@mark-story.com>
1 parent d7396c4 commit c372fbe

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

docs/en/appendices/5-4-migration-guide.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ See [Application and Plugin Events](../core-libraries/events#registering-event-l
152152

153153
- `Number::toReadableSize()` now uses decimal units (KB = 1000 bytes) by default.
154154
Binary units (KiB = 1024 bytes) can be enabled via parameter or `Number::setUseIecUnits()`.
155+
- Added `TranslatorRegistry::setCacheKeyPrefix()` to isolate translator caches
156+
per tenant when a custom loader produces different messages for the same
157+
domain and locale. Accepts a static string or a `Closure` resolved on every
158+
lookup. See [Isolating Translations Per Tenant](../core-libraries/internationalization-and-localization#isolating-translations-per-tenant).
159+
- Added `TranslatorRegistry::clear()` to drop the in-memory translator map
160+
without touching the persistent cacher. Intended for long-running workers
161+
that switch tenants between jobs.
162+
- Added `I18n::setCacheConfig()` to route translator persistence to a Cache
163+
config other than the default `_cake_translations_`.
155164

156165
### ORM
157166

docs/en/core-libraries/internationalization-and-localization.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,94 @@ I18n::config('_fallback', function ($domain, $locale) {
562562
});
563563
```
564564

565+
### Isolating Translations Per Tenant
566+
567+
::: info Added in version 5.4.0
568+
The cache key prefix and configurable cache config APIs were added in 5.4.0.
569+
:::
570+
571+
If your application needs to serve tenant specific translated content for a given domain & locale, you need to use a cache key prefix to scope both translator cache data to the tenant.
572+
573+
#### Cache Key Prefix
574+
575+
`TranslatorRegistry::setCacheKeyPrefix()` adds a segment to both the persistent
576+
cache key and the in-memory lookup bucket. It accepts either a static string or
577+
a `Closure` that returns one. When given a `Closure`, it is evaluated on every
578+
`get()` call, so the current tenant identifier is pulled *from* user-land
579+
instead of being *pushed into* the registry:
580+
581+
```php
582+
use Cake\I18n\I18n;
583+
584+
I18n::translators()->setCacheKeyPrefix(
585+
fn (): string => TenantContext::current()?->id ?? ''
586+
);
587+
```
588+
589+
With a non-empty prefix the cache key becomes
590+
`translations.{prefix}.{domain}.{locale}`. An empty resolved value disables
591+
prefixing and keeps the legacy key format, so the API is fully backwards
592+
compatible for non-multi-tenant applications.
593+
594+
The `Closure` receives the requested package name and resolved locale
595+
(`function (string $name, string $locale): string`), which lets you skip
596+
prefixing for shared packages or vary the prefix per locale:
597+
598+
```php
599+
I18n::translators()->setCacheKeyPrefix(
600+
function (string $name, string $locale): string {
601+
// Shared packages (e.g. validation messages) stay un-prefixed.
602+
if ($name === 'cake' || str_starts_with($name, 'shared/')) {
603+
return '';
604+
}
605+
606+
return TenantContext::current()?->id ?? '';
607+
}
608+
);
609+
```
610+
611+
Prefix values must match `[A-Za-z0-9._-]+` to stay safe across every built-in
612+
cache engine.
613+
614+
> [!NOTE]
615+
> `setCacheKeyPrefix()` is unrelated to the gettext message context used by
616+
> [`__x()`](#using-translation-functions). The "context" in `__x()` disambiguates
617+
> two messages with the same source text; the cache key prefix isolates the
618+
> *cache* of resolved messages.
619+
620+
#### Resetting the In-Memory Registry
621+
622+
Long-running workers (e.g. queue runners) that switch tenants between jobs
623+
should drop the in-memory translator map between batches to bound memory
624+
growth and ensure freshly-resolved tenants don't read another tenant's
625+
in-memory translator. The persistent cacher and configured prefix/cacher
626+
are left untouched:
627+
628+
```php
629+
foreach ($jobsByTenant as $tenantId => $jobs) {
630+
TenantContext::set($tenantId);
631+
// ... process jobs ...
632+
I18n::translators()->clear();
633+
}
634+
```
635+
636+
#### Choosing a Different Cache Config
637+
638+
By default, translators are persisted to the `_cake_translations_` Cache
639+
config. If you want a separate config — for example, to give translations
640+
their own TTL or storage engine — call `I18n::setCacheConfig()` before any
641+
translator is resolved:
642+
643+
```php
644+
// in config/bootstrap.php, before any __() / I18n call
645+
I18n::setCacheConfig('_my_translations_');
646+
```
647+
648+
`setCacheConfig()` throws a `RuntimeException` if it is called after the
649+
translators registry has been built, to surface ordering bugs loudly instead
650+
of silently ignoring the setting. To swap the cacher *after* translators
651+
have been built, use `I18n::translators()->setCacher()` directly.
652+
565653
### Plurals and Context in Custom Translators
566654

567655
The arrays used for `setMessages()` can be crafted to instruct the translator

0 commit comments

Comments
 (0)