This release introduces global preferences — a new, reader-independent layer that owns locale and derives UI direction from it. It is a breaking change for any integration that set locale or direction on reader/audio preferences, read them back via useSharedPreferences, or dispatched setL10n directly.
| Area | Change |
|---|---|
ThPreferences / ThAudioPreferences |
locale and direction fields removed |
createPreferences / createAudioPreferences |
Locale validation removed — use createGlobalPreferences |
useSharedPreferences |
No longer returns locale or direction |
ThDirectionSetter |
No longer accepts a direction prop — derives direction from React Aria context |
Per-reader I18nProvider |
Removed from StatefulReader and StatefulPlayer — now provided globally |
preferencesReducer |
L10nObject, setL10n, and the l10n slice removed |
| Redux store | New globalPreferences key added |
| Preferences helpers | No longer marked "use client" — importable from server components |
| Settings components | New useReaderSetting hook to handle state based on profile epub/wepub |
locale and direction no longer exist on ThPreferences or ThAudioPreferences. They are now managed by the new ThGlobalPreferencesProvider which must be placed above all readers in your component tree.
import { createPreferences } from "@edrlab/thorium-web/preferences";
const prefs = createPreferences({
locale: "ar",
direction: "rtl",
// ... rest of preferences
});
<ThStoreProvider>
<StatefulReaderWrapper
preferences={{ initialPreferences: prefs }}
{ ...props }
/>
</ThStoreProvider>import { createPreferences } from "@edrlab/thorium-web/preferences";
import { createGlobalPreferences, ThGlobalPreferencesProvider } from "@edrlab/thorium-web/preferences";
const globalPrefs = createGlobalPreferences({ locale: "ar" });
const readerPrefs = createPreferences({
// locale and direction no longer accepted here
// ... rest of preferences
});
<ThStoreProvider>
<ThGlobalPreferencesProvider initialPreferences={ globalPrefs }>
<StatefulReaderWrapper
preferences={{ initialPreferences: readerPrefs }}
{ ...props }
/>
</ThGlobalPreferencesProvider>
</ThStoreProvider>ThGlobalPreferencesProvider wraps React Aria's I18nProvider internally and derives the document direction from the locale automatically. You no longer store or manage direction anywhere.
Any component reading locale or direction from useSharedPreferences must be updated.
// Before
const { locale, direction } = useSharedPreferences();
// After — locale
import { useGlobalPreferences } from "@edrlab/thorium-web/preferences";
const { preferences: { locale } } = useGlobalPreferences();
// After — direction (derived, not stored)
import { useLocale } from "react-aria";
const { direction } = useLocale();Important
useLocale() returns the UI direction derived from the locale preference. For publication-level direction (RTL content), continue using state.publication.isRTL from the Redux store.
The l10n slice, setL10n action, and L10nObject type have been removed from preferencesReducer. Locale is now persisted in the dedicated globalPreferencesReducer.
// Before
import { setL10n } from "@edrlab/thorium-web/lib";
dispatch(setL10n({ locale: "fr", direction: "ltr" }));
// After
import { setLocale } from "@edrlab/thorium-web/lib";
dispatch(setLocale("fr"));The Redux store now has a globalPreferences key (GlobalPreferencesReducerState) alongside the existing preferences key. Both are persisted to localStorage automatically.
If you used ThReduxPreferencesAdapter and expected it to sync locale back into your preferences, this no longer happens. Locale is now handled exclusively by ThReduxGlobalPreferencesAdapter.
Use StatefulGlobalPreferencesProvider to wire global preferences to Redux — it follows the same pattern as StatefulPreferencesProvider:
import { StatefulGlobalPreferencesProvider } from "@edrlab/thorium-web/components";
<ThStoreProvider>
<StatefulGlobalPreferencesProvider initialPreferences={ globalPrefs }>
<StatefulReaderWrapper { ...props } />
</StatefulGlobalPreferencesProvider>
</ThStoreProvider>import { ThGlobalPreferencesProvider } from "@edrlab/thorium-web/preferences";
<ThGlobalPreferencesProvider
initialPreferences={{ locale: "fr" }}
adapter={ myGlobalAdapter } // optional — defaults to in-memory
>
{ children }
</ThGlobalPreferencesProvider>The provider:
- Validates the locale via
createGlobalPreferences(unsupported locales fall back to browser default) - Wraps React Aria's
I18nProvidersouseLocale()works anywhere in the tree - Sets
document.documentElement.dirreactively via the updatedThDirectionSetter
| Prop | Type | Description |
|---|---|---|
initialPreferences |
ThGlobalPreferences |
Optional initial locale |
adapter |
ThGlobalPreferencesAdapter |
Optional custom persistence adapter |
children |
ReactNode |
Required |
import { createGlobalPreferences } from "@edrlab/thorium-web/preferences";
const prefs = createGlobalPreferences({ locale: "ar" });
// Unsupported locales are silently discarded and fall back to browser languageThis function is not marked "use client" and can be called in Server Components and layout.tsx.
import { useGlobalPreferences } from "@edrlab/thorium-web/preferences";
const { preferences, updatePreferences } = useGlobalPreferences();
// preferences.locale — current locale string or undefined
updatePreferences({ locale: "fr" });A Redux-backed wrapper around ThGlobalPreferencesProvider. Reads and writes locale through globalPreferencesReducer so it is persisted to localStorage alongside the rest of the app state.
import { StatefulGlobalPreferencesProvider } from "@edrlab/thorium-web/components";See the updated Handling Preferences guide for full integration examples.
createPreferences, createAudioPreferences, and createGlobalPreferences no longer carry the "use client" directive. They can be imported and called in Server Components or layout.tsx directly:
// layout.tsx (Server Component) — now works without error
import { createGlobalPreferences } from "@edrlab/thorium-web/preferences";
const prefs = createGlobalPreferences({ locale: "ar" });Previously, importing from the @edrlab/thorium-web/preferences barrel in a server context would throw because the entire barrel was marked "use client". The directive is now scoped to individual component and hook files only.
<ThStoreProvider>
<ThGlobalPreferencesProvider initialPreferences={ globalPrefs }>
<StatefulReaderWrapper { ...props } />
</ThGlobalPreferencesProvider>
</ThStoreProvider><ThStoreProvider>
<StatefulGlobalPreferencesProvider initialPreferences={ globalPrefs }>
<StatefulReaderWrapper { ...props } />
</StatefulGlobalPreferencesProvider>
</ThStoreProvider>A new centralized hook has been introduced to handle settings’ states:
import { useReaderSetting } from "@edrlab/thorium-web/reader";
// Automatically handles profile (webPub vs epub)
const fontWeight = useReaderSetting("fontWeight");
const zoom = useReaderSetting("zoom"); // Transparently maps to fontSize for epubThe hook:
- Automatically selects the correct store slice (
settingsvswebPubSettings) based on reader profile - Provides fallback from initial state defaults
- Transparently maps profile-specific keys (e.g.,
zoom→fontSizefor non-webPub profiles)
const profile = useAppSelector(state => state.reader.profile);
const isWebPub = profile === "webPub";
const resolvedLanguage = useAppSelector(state => state.publication.resolvedLanguage) || "default";
const fontWeight = useAppSelector(state => {
const w = isWebPub ? state.webPubSettings.fontWeight : state.settings.fontWeight;
return w?.[resolvedLanguage] ?? 400;
});const fontWeight = useReaderSetting("fontWeight");- Remove
localeanddirectionfrom allcreatePreferences/createAudioPreferencescalls - Add
ThGlobalPreferencesProvider(orStatefulGlobalPreferencesProvider) above readers - Replace
useSharedPreferences().localewithuseGlobalPreferences().preferences.locale - Replace
useSharedPreferences().directionwithuseLocale().directionfromreact-aria - Replace
dispatch(setL10n({ locale }))withdispatch(setLocale(locale)) - Remove
L10nObjecttype references - Verify no component reads
state.preferences.l10n— it no longer exists - Migrate settings components to use
useReaderSettinginstead of manualuseAppSelectorcalls