panel names re-render when the words behind them change

Closes the gap left by the thunk: resolving at render proves nothing is cached, but
AppPicker and PanelSlot are not subscribed to anything that changes when the locale
does, so a switch updated every string in the app except the panel titles.

An opaque revision, which this package interprets in no way at all. It is a string
that changed; the caller decides what changing means. officer-web sets it to the
active locale from I18nBridge, during render rather than in an effect — an effect
leaves the first paint after a switch showing the previous language's titles.

A module-level store rather than a prop on WorkspaceView, deliberately: that
component has 28 call sites, and a prop is a thing 28 places can forget. A forgotten
one is a silently stale title, which is the failure being closed.

The new test bumps the revision and asserts the name follows with NO rerender from the
test. The existing one calls rerender, so it could only ever prove the absence of
caching — it could not have caught this.
This commit is contained in:
2026-08-23 15:55:06 +00:00
parent a36fa356ae
commit 736a45e1ff
6 changed files with 101 additions and 5 deletions
+12
View File
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
import type { ReactNode } from 'react';
import type { TranslationMap } from 'i18n';
import { I18nProvider, localeDirection } from 'i18n';
import { setNameRevision } from 'workspaces/react';
import { panelCatalogs } from 'officerdev';
import { uiCatalogs } from '@/components/locales';
import { useSettings } from 'state/useSettings';
@@ -40,6 +41,17 @@ export const I18nBridge = ({ children }: I18nBridgeProps) => {
document.documentElement.dir = localeDirection(locale);
}, [locale]);
/*
Panel names are thunks resolved during the framework's own render (AppRegistryEntry.name), and the
framework is not subscribed to this provider — it has no i18n dependency at all, deliberately. So
tell it that the words changed. It treats the value as opaque; that it happens to be a locale is
this file's business, not the framework's.
Set during render rather than in an effect: an effect would leave the first paint after a locale
switch showing the previous language's titles.
*/
setNameRevision(locale);
return (
<I18nProvider translations={translations} locale={locale} fallbackLocale={FALLBACK_LOCALE}>
{children}