Save selected app language on server
This commit is contained in:
@@ -33,6 +33,8 @@ import { ISourceMetadata } from '@/features/source/Source.types.ts';
|
||||
import { LibraryOptions } from '@/features/library/Library.types.ts';
|
||||
import { MetadataThemeSettings } from '@/features/theme/AppTheme.types.ts';
|
||||
import { TapZoneInvertMode } from '@/features/reader/tap-zones/TapZoneLayout.types.ts';
|
||||
import { detectLocale, getISOLanguage } from '@/lib/ISOLanguageUtil.ts';
|
||||
import { i18nResources } from '@/i18n';
|
||||
|
||||
export const APP_METADATA_KEY_PREFIX = 'webUI';
|
||||
|
||||
@@ -374,6 +376,18 @@ export const APP_METADATA: Record<
|
||||
excludedScanlators: {
|
||||
convert: convertToObject<string[]>,
|
||||
},
|
||||
locale: {
|
||||
convert: convertToString,
|
||||
toConstrainedValue: (value: string) => {
|
||||
const locale = getISOLanguage(value)?.isoCode;
|
||||
|
||||
if (!locale || !i18nResources.includes(locale)) {
|
||||
return detectLocale();
|
||||
}
|
||||
|
||||
return locale;
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA);
|
||||
@@ -446,6 +460,7 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
|
||||
'isEnabled',
|
||||
|
||||
// themes
|
||||
'locale',
|
||||
'customThemes',
|
||||
'mangaThumbnailBackdrop',
|
||||
'mangaDynamicColorSchemes',
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
WebUiInterface,
|
||||
} from '@/lib/graphql/generated/graphql.ts';
|
||||
import { ThemeMode } from '@/features/theme/AppTheme.types.ts';
|
||||
import { getPreferredISOLanguageCodes } from '@/lib/ISOLanguageUtil.ts';
|
||||
|
||||
export const MANGA_GRID_WIDTH = {
|
||||
min: 100,
|
||||
@@ -85,6 +86,7 @@ export const SERVER_SETTINGS_METADATA_DEFAULT: MetadataServerSettings = {
|
||||
serverInformAvailableUpdate: true,
|
||||
|
||||
// themes
|
||||
locale: getPreferredISOLanguageCodes()[0],
|
||||
appTheme: 'default',
|
||||
themeMode: ThemeMode.SYSTEM,
|
||||
shouldUsePureBlackMode: false,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useAppThemeContext } from '@/features/theme/AppThemeContext.tsx';
|
||||
import { Select } from '@/base/components/inputs/Select.tsx';
|
||||
import { MediaQuery } from '@/base/utils/MediaQuery.tsx';
|
||||
import { NumberSetting } from '@/base/components/settings/NumberSetting.tsx';
|
||||
import { I18nResourceCode, i18nResources, loadCatalog } from '@/i18n';
|
||||
import { i18nResources, loadCatalog } from '@/i18n';
|
||||
import { languageCodeToName } from '@/base/utils/Languages.ts';
|
||||
import { ThemeList } from '@/features/theme/components/ThemeList.tsx';
|
||||
import {
|
||||
@@ -38,7 +38,7 @@ import { MANGA_GRID_WIDTH, SERVER_SETTINGS_METADATA_DEFAULT } from '@/features/s
|
||||
import { MUI_THEME_MODE_KEY } from '@/lib/mui/MUI.constants.ts';
|
||||
|
||||
export const Appearance = () => {
|
||||
const { t, i18n } = useLingui();
|
||||
const { t } = useLingui();
|
||||
const { themeMode, setThemeMode, shouldUsePureBlackMode, setShouldUsePureBlackMode } = useAppThemeContext();
|
||||
const { mode, setMode } = useColorScheme();
|
||||
const actualThemeMode = (mode ?? themeMode) as ThemeMode;
|
||||
@@ -46,7 +46,7 @@ export const Appearance = () => {
|
||||
useAppTitle(t`Appearance`);
|
||||
|
||||
const {
|
||||
settings: { mangaThumbnailBackdrop, mangaDynamicColorSchemes, mangaGridItemWidth },
|
||||
settings: { mangaThumbnailBackdrop, mangaDynamicColorSchemes, mangaGridItemWidth, locale },
|
||||
request: { loading, error, refetch },
|
||||
} = useMetadataServerSettings();
|
||||
const updateMetadataSetting = createUpdateMetadataServerSettings<keyof MetadataThemeSettings>((e) =>
|
||||
@@ -135,12 +135,15 @@ export const Appearance = () => {
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
value={i18nResources.includes(i18n.locale as I18nResourceCode) ? i18n.locale : 'en'}
|
||||
onChange={({ target: { value: language } }) =>
|
||||
loadCatalog(language).catch((e: Error) => {
|
||||
value={locale}
|
||||
onChange={({ target: { value: newLocale } }) => {
|
||||
updateMetadataSetting('locale', newLocale).catch((e) =>
|
||||
makeToast(t`Could not save changes`, 'error', getErrorMessage(e)),
|
||||
);
|
||||
loadCatalog(newLocale).catch((e: Error) => {
|
||||
makeToast(t`Could not load language`, 'error', getErrorMessage(e));
|
||||
})
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18nResources.map((language) => (
|
||||
<MenuItem key={language} value={language}>
|
||||
|
||||
@@ -29,6 +29,7 @@ export type TAppThemeContext = {
|
||||
>;
|
||||
};
|
||||
export type MetadataThemeSettings = {
|
||||
locale: string;
|
||||
appTheme: AppThemes;
|
||||
themeMode: ThemeMode;
|
||||
shouldUsePureBlackMode: boolean;
|
||||
|
||||
@@ -25,6 +25,8 @@ import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { DIRECTION_TO_CACHE } from '@/features/theme/ThemeDirectionCache.ts';
|
||||
import { TAppThemeContext, ThemeMode } from '@/features/theme/AppTheme.types.ts';
|
||||
import { getLanguageReadingDirection } from '@/lib/ISOLanguageUtil.ts';
|
||||
import { loadCatalog } from '@/i18n';
|
||||
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
|
||||
|
||||
export const AppThemeContext = React.createContext<TAppThemeContext>({
|
||||
appTheme: 'default',
|
||||
@@ -40,10 +42,10 @@ export const AppThemeContext = React.createContext<TAppThemeContext>({
|
||||
export const useAppThemeContext = () => useContext(AppThemeContext);
|
||||
|
||||
export const AppThemeContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { t, i18n } = useLingui();
|
||||
const { t } = useLingui();
|
||||
const {
|
||||
request: metadataServerSettingsRequest,
|
||||
settings: { appTheme: serverAppTheme, themeMode, shouldUsePureBlackMode, customThemes },
|
||||
settings: { appTheme: serverAppTheme, themeMode, shouldUsePureBlackMode, customThemes, locale },
|
||||
} = useMetadataServerSettings();
|
||||
const [localAppTheme, setLocalAppTheme] = useLocalStorage<AppTheme>(
|
||||
'appTheme',
|
||||
@@ -61,7 +63,7 @@ export const AppThemeContextProvider = ({ children }: { children: ReactNode }) =
|
||||
|
||||
const appTheme = areMetadataServerSettingsReady ? serverAppTheme : localAppTheme.id;
|
||||
const actualThemeMode = areMetadataServerSettingsReady ? themeMode : localThemeMode;
|
||||
const currentDirection = getLanguageReadingDirection(i18n.locale);
|
||||
const currentDirection = getLanguageReadingDirection(locale);
|
||||
|
||||
const updateSetting = createUpdateMetadataServerSettings<'appTheme' | 'themeMode' | 'shouldUsePureBlackMode'>((e) =>
|
||||
makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)),
|
||||
@@ -108,6 +110,14 @@ export const AppThemeContextProvider = ({ children }: { children: ReactNode }) =
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!areMetadataServerSettingsReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadCatalog(locale).catch(defaultPromiseErrorHandler('AppThemeContextProvider::loadCatalog'));
|
||||
}, [areMetadataServerSettingsReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!areMetadataServerSettingsReady) {
|
||||
return;
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
*/
|
||||
|
||||
import { i18n } from '@lingui/core';
|
||||
import { DEFAULT_LANGUAGE, getISOLanguage, getPreferredISOLanguageCodes } from '@/lib/ISOLanguageUtil.ts';
|
||||
import { AppStorage } from '@/lib/storage/AppStorage.ts';
|
||||
import { detectLocale } from '@/lib/ISOLanguageUtil.ts';
|
||||
import localesConfig from '@/i18n/locales.json';
|
||||
|
||||
export const i18nResources = localesConfig.locales;
|
||||
@@ -17,28 +16,11 @@ export type I18nResourceCode = (typeof i18nResources)[number];
|
||||
|
||||
const DEFAULT_LOCALE = localesConfig.defaultLocale;
|
||||
|
||||
const SELECTED_LANGUAGE_CODE_KEY = 'i18n:selected-language';
|
||||
|
||||
export async function loadCatalog(locale: string): Promise<void> {
|
||||
const localeToLoad = i18nResources.includes(locale as (typeof i18nResources)[number]) ? locale : DEFAULT_LOCALE;
|
||||
|
||||
const { messages } = await import(`./locales/${localeToLoad}.po`);
|
||||
i18n.loadAndActivate({ locale: localeToLoad, messages });
|
||||
|
||||
AppStorage.local.setItem(SELECTED_LANGUAGE_CODE_KEY, localeToLoad);
|
||||
}
|
||||
|
||||
function detectLocale(): string {
|
||||
const storedLanguage = AppStorage.local.getItem(SELECTED_LANGUAGE_CODE_KEY);
|
||||
const preferredLanguages = getPreferredISOLanguageCodes();
|
||||
const languageCodes = [storedLanguage, ...preferredLanguages];
|
||||
|
||||
const normalizedLanguageCodes = languageCodes
|
||||
.filter((code) => code !== null)
|
||||
.filter((code) => getISOLanguage(code))
|
||||
.filter((code) => i18nResources.includes(code as (typeof i18nResources)[number]));
|
||||
|
||||
return normalizedLanguageCodes[0] ?? DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
export async function initializeLocalization(): Promise<void> {
|
||||
|
||||
@@ -985,6 +985,10 @@ msgstr "Could not restore backup"
|
||||
msgid "Could not retry failed download."
|
||||
msgstr "Could not retry failed download."
|
||||
|
||||
#: src/features/settings/screens/Appearance.tsx
|
||||
msgid "Could not save changes"
|
||||
msgstr "Could not save changes"
|
||||
|
||||
#: src/features/category/components/CategorySelect.tsx
|
||||
#: src/features/library/components/LibraryOptionsPanel.tsx
|
||||
#: src/features/library/screens/LibrarySettings.tsx
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { ISOLanguage, IsoLanguages } from '@/base/IsoLanguages.ts';
|
||||
import { i18nResources } from '@/i18n';
|
||||
|
||||
export type LanguageObject = ISOLanguage & { orgCode: string; isoCode: string };
|
||||
|
||||
@@ -137,3 +138,12 @@ export function getLanguageReadingDirection(code: string): 'ltr' | 'rtl' {
|
||||
|
||||
return 'ltr';
|
||||
}
|
||||
|
||||
export function detectLocale(): string {
|
||||
const normalizedLanguageCodes = getPreferredISOLanguageCodes()
|
||||
.filter((code) => code !== null)
|
||||
.filter((code) => getISOLanguage(code))
|
||||
.filter((code) => i18nResources.includes(code as (typeof i18nResources)[number]));
|
||||
|
||||
return normalizedLanguageCodes[0] ?? DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user