From eeb9cc66a57798681b942ac00937088e9d0cc5d6 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 22:05:51 +0200 Subject: [PATCH] Split useTranslation into state + actions via useBetween singleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same pattern as the wired-tools split: 600-line useTranslation backs 6 consumers with a wide state + action surface. Split along the read/write seam: - useTranslationStore (internal, was the inner useTranslationState) — the previous singleton body, untouched except for the rename and a doc-comment. - useTranslationState (public, read-only) — useBetween filter exposing settings, the supported-languages list, the loading/loaded flags, the detected-language tags, lastError, and the pure getLanguageName helper. - useTranslationActions (public, imperative) — same singleton filter exposing updateSettings, ensureSupportedLanguagesLoaded, the four translate/queue helpers. Also re-exposes 'settings' because most call sites need 'if(settings.enabled)' before dispatching. - useTranslation (deprecated shim) — composes the singleton via useBetween, preserving the historical full-shape return. applyTextTranslationLocale stays exported from the same module path so LoginView's import keeps working. Updates docs/ARCHITECTURE.md proposal #4 section to list the three new splits (chat-input + wired-tools + translation) alongside the previous five. --- docs/ARCHITECTURE.md | 18 +++++ src/hooks/translation/useTranslation.ts | 89 ++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c459bbc..13280af 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -387,6 +387,24 @@ The current branch (**`feat/react19-modernization`**, PR #2) has applied: bridges + 1 derive effect) + `useFriendRequestActions` (thin adapter on the friends store) + shim. Exports `ActiveFriendRequest` type. + - **chat input**: `useChatInputState` (5 state slices + 3 event + listeners + 3 lifecycle effects: flood countdown, idle auto-clear, + typing-indicator sync) + `useChatInputActions` (`sendChat` with + the full slash-command repertoire and the outgoing-translation + pipeline) + shim. Single consumer (`ChatInputView`) keeps the + original tuple. + - **wired tools**: `useWiredToolsStore` (internal singleton — state, + listeners, effects, 13 actions in one closure) + `useWiredToolsState` + / `useWiredToolsActions` (read-only and imperative `useBetween` + filters over the same singleton) + `useWiredTools` shim. Used by + ~20 consumers; the singleton sharing keeps a single source of + truth while letting consumers import only the slice they touch. + - **translation**: `useTranslationStore` (internal singleton) + + `useTranslationState` / `useTranslationActions` (filtered + `useBetween` views) + `useTranslation` shim. Same pattern as + Wired tools — six consumers split across read-only views + (settings panel, bootstrap) and dispatch sites (messenger, chat + input). - **Zustand** (proposal #5) — **enabled**. `zustand` installed; factory at `src/state/createNitroStore.ts`. First adoption: the `let isCreatingRoom` / `createRoomTimeout` module-level pair in `NavigatorRoomCreatorView` diff --git a/src/hooks/translation/useTranslation.ts b/src/hooks/translation/useTranslation.ts index 0d1f17e..a290459 100644 --- a/src/hooks/translation/useTranslation.ts +++ b/src/hooks/translation/useTranslation.ts @@ -228,7 +228,17 @@ const resolveSupportedLanguage = (value: string, languages: ITranslationLanguage return languages[0].code; }; -const useTranslationState = () => +/** + * Internal singleton state + actions hook. Public consumers should + * call useTranslationState (read-only) or useTranslationActions + * (imperative) instead. useTranslation is the deprecated shim that + * composes both. + * + * Wrapped in useBetween at each public-hook layer so every consumer + * in the tree sees the same instance (preserves the previous + * useBetween(useTranslationState) behavior). + */ +const useTranslationStore = () => { const defaultTargetLanguage = getBrowserLanguageCode(); const [ settings, setSettings ] = useLocalStorage(LocalStorageKeys.CHAT_TRANSLATION_SETTINGS, { @@ -597,4 +607,79 @@ const useTranslationState = () => }; }; -export const useTranslation = () => useBetween(useTranslationState); +/** + * Read-only slice of the translation store: persisted settings, the + * supported languages list, the loading/loaded flags, the last + * incoming/outgoing detected language tags, and the last error message + * surfaced to the UI. + * + * Components that only render translation state subscribe here. + */ +export const useTranslationState = () => +{ + const { + settings, + supportedLanguages, + availableTextLocales, + languagesLoading, + languagesLoaded, + localizationTextsLoading, + lastIncomingLanguage, + lastOutgoingLanguage, + lastError, + getLanguageName + } = useBetween(useTranslationStore); + + return { + settings, + supportedLanguages, + availableTextLocales, + languagesLoading, + languagesLoaded, + localizationTextsLoading, + lastIncomingLanguage, + lastOutgoingLanguage, + lastError, + getLanguageName + }; +}; + +/** + * Imperative slice of the translation store: settings mutation, + * supported-languages refresh, the translate* helpers, and the + * outgoing-queue write/read pair. Stays separate so components that + * only invoke actions (e.g. ChatInputActions) don't pull the full + * state shape. + */ +export const useTranslationActions = () => +{ + const { + settings, + updateSettings, + ensureSupportedLanguagesLoaded, + translateIncoming, + translateOutgoing, + enqueueOutgoingTranslation, + consumeOutgoingTranslation + } = useBetween(useTranslationStore); + + return { + // settings is exposed here too because most action call sites + // need `if(settings.enabled)` checks before dispatching. + settings, + updateSettings, + ensureSupportedLanguagesLoaded, + translateIncoming, + translateOutgoing, + enqueueOutgoingTranslation, + consumeOutgoingTranslation + }; +}; + +/** + * @deprecated Prefer `useTranslationState` (read-only) and + * `useTranslationActions` (imperative) directly. This shim composes + * both into the historical `useTranslation()` shape so the six + * existing consumers keep working unchanged. + */ +export const useTranslation = () => useBetween(useTranslationStore);