Split useTranslation into state + actions via useBetween singleton

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.
This commit is contained in:
simoleo89
2026-05-11 22:05:51 +02:00
parent e1f5df6b1c
commit eeb9cc66a5
2 changed files with 105 additions and 2 deletions
+18
View File
@@ -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`
+87 -2
View File
@@ -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<ITranslationSettings>(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);