mirror of
https://github.com/inventree/InvenTree.git
synced 2026-03-04 03:11:46 +00:00
[UI] Default locale (#11412)
* [UI] Support default server language * Handle faulty theme * Add option for default language * Improve language selection * Brief docs entry * Fix typo * Fix yarn build * Remove debug msg * Fix calendar locale
This commit is contained in:
@@ -29,6 +29,10 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import {
|
||||
defaultLocale,
|
||||
getPriorityLocale
|
||||
} from '../../contexts/LanguageContext';
|
||||
import type { CalendarState } from '../../hooks/UseCalendar';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { FilterSelectDrawer } from '../../tables/FilterSelectDrawer';
|
||||
@@ -60,7 +64,19 @@ export default function Calendar({
|
||||
const [locale] = useLocalState(useShallow((s) => [s.language]));
|
||||
|
||||
// Ensure underscore is replaced with dash
|
||||
const calendarLocale = useMemo(() => locale.replace('_', '-'), [locale]);
|
||||
const calendarLocale = useMemo(() => {
|
||||
let _locale: string | null = locale;
|
||||
|
||||
if (!_locale) {
|
||||
_locale = getPriorityLocale();
|
||||
}
|
||||
|
||||
_locale = _locale || defaultLocale;
|
||||
|
||||
_locale = _locale.replace('_', '-');
|
||||
|
||||
return _locale;
|
||||
}, [locale]);
|
||||
|
||||
const selectMonth = useCallback(
|
||||
(date: DateValue) => {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Select } from '@mantine/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { getSupportedLanguages } from '../../contexts/LanguageContext';
|
||||
import {
|
||||
activateLocale,
|
||||
getSupportedLanguages
|
||||
} from '../../contexts/LanguageContext';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
|
||||
export function LanguageSelect({ width = 80 }: Readonly<{ width?: number }>) {
|
||||
@@ -28,13 +32,21 @@ export function LanguageSelect({ width = 80 }: Readonly<{ width?: number }>) {
|
||||
}));
|
||||
setLangOptions(newLangOptions);
|
||||
setValue(locale);
|
||||
activateLocale(locale); // Ensure the locale is activated on component load
|
||||
}, [locale]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
w={width}
|
||||
data={langOptions}
|
||||
data={[
|
||||
{
|
||||
value: '',
|
||||
label: t`Default Language`
|
||||
},
|
||||
...langOptions
|
||||
]}
|
||||
value={value}
|
||||
defaultValue={''}
|
||||
onChange={setValue}
|
||||
searchable
|
||||
aria-label='Select language'
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type InvenTreePluginContext
|
||||
} from '@lib/types/Plugins';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { defaultLocale } from '../../contexts/LanguageContext';
|
||||
import {
|
||||
useAddStockItem,
|
||||
useAssignStockItem,
|
||||
@@ -35,10 +36,12 @@ import {
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useServerApiState } from '../../states/ServerApiState';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
|
||||
export const useInvenTreeContext = () => {
|
||||
const [locale, host] = useLocalState(useShallow((s) => [s.language, s.host]));
|
||||
const [server] = useServerApiState(useShallow((s) => [s.server]));
|
||||
const navigate = useNavigate();
|
||||
const user = useUserState();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
@@ -57,7 +60,7 @@ export const useInvenTreeContext = () => {
|
||||
user: user,
|
||||
host: host,
|
||||
i18n: i18n,
|
||||
locale: locale,
|
||||
locale: locale || server.default_locale || defaultLocale,
|
||||
api: api,
|
||||
queryClient: queryClient,
|
||||
navigate: navigate,
|
||||
|
||||
@@ -65,9 +65,29 @@ export function LanguageContext({
|
||||
const [language] = useLocalState(useShallow((state) => [state.language]));
|
||||
const [server] = useServerApiState(useShallow((state) => [state.server]));
|
||||
|
||||
const [activeLocale, setActiveLocale] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
activateLocale(defaultLocale);
|
||||
}, []);
|
||||
// Update the locale based on prioritization:
|
||||
// 1. Locally selected locale
|
||||
// 2. Server default locale
|
||||
// 3. English (fallback)
|
||||
|
||||
let locale: string | null = activeLocale;
|
||||
|
||||
if (!!language) {
|
||||
locale = language;
|
||||
} else if (!!server.default_locale) {
|
||||
locale = server.default_locale;
|
||||
} else {
|
||||
locale = defaultLocale;
|
||||
}
|
||||
|
||||
if (locale != activeLocale) {
|
||||
setActiveLocale(locale);
|
||||
activateLocale(locale);
|
||||
}
|
||||
}, [activeLocale, language, server.default_locale, defaultLocale]);
|
||||
|
||||
const [loadedState, setLoadedState] = useState<
|
||||
'loading' | 'loaded' | 'error'
|
||||
@@ -77,7 +97,7 @@ export function LanguageContext({
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
let lang = language;
|
||||
let lang: string = language || defaultLocale;
|
||||
|
||||
// Ensure that the selected language is supported
|
||||
if (!Object.keys(getSupportedLanguages()).includes(lang)) {
|
||||
@@ -96,7 +116,7 @@ export function LanguageContext({
|
||||
*/
|
||||
const locales: (string | undefined)[] = [];
|
||||
|
||||
if (lang != 'pseudo-LOCALE') {
|
||||
if (!!lang && lang != 'pseudo-LOCALE') {
|
||||
locales.push(lang);
|
||||
}
|
||||
|
||||
@@ -156,8 +176,26 @@ export function LanguageContext({
|
||||
return <I18nProvider i18n={i18n}>{children}</I18nProvider>;
|
||||
}
|
||||
|
||||
export async function activateLocale(locale: string) {
|
||||
const { messages } = await import(`../locales/${locale}/messages.ts`);
|
||||
i18n.load(locale, messages);
|
||||
i18n.activate(locale);
|
||||
// This function is used to determine the locale to activate based on the prioritization rules.
|
||||
export function getPriorityLocale(): string {
|
||||
const serverDefault = useServerApiState.getState().server.default_locale;
|
||||
const userDefault = useLocalState.getState().language;
|
||||
|
||||
return userDefault || serverDefault || defaultLocale;
|
||||
}
|
||||
|
||||
export async function activateLocale(locale: string | null) {
|
||||
if (!locale) {
|
||||
locale = getPriorityLocale();
|
||||
}
|
||||
|
||||
const localeDir = locale.split('-')[0]; // Extract the base locale (e.g., 'en' from 'en-US')
|
||||
|
||||
try {
|
||||
const { messages } = await import(`../locales/${localeDir}/messages.ts`);
|
||||
i18n.load(locale, messages);
|
||||
i18n.activate(locale);
|
||||
} catch (err) {
|
||||
console.error(`Failed to load locale ${locale}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react';
|
||||
import { MantineProvider, createTheme } from '@mantine/core';
|
||||
import {
|
||||
MantineProvider,
|
||||
type MantineThemeOverride,
|
||||
createTheme
|
||||
} from '@mantine/core';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import { ContextMenuProvider } from 'mantine-contextmenu';
|
||||
@@ -20,23 +24,31 @@ export function ThemeContext({
|
||||
}: Readonly<{ children: JSX.Element }>) {
|
||||
const [userTheme] = useLocalState(useShallow((state) => [state.userTheme]));
|
||||
|
||||
let customUserTheme: MantineThemeOverride | undefined = undefined;
|
||||
|
||||
// Theme
|
||||
const myTheme = createTheme({
|
||||
primaryColor: userTheme.primaryColor,
|
||||
white: userTheme.whiteColor,
|
||||
black: userTheme.blackColor,
|
||||
defaultRadius: userTheme.radius,
|
||||
breakpoints: {
|
||||
xs: '30em',
|
||||
sm: '48em',
|
||||
md: '64em',
|
||||
lg: '74em',
|
||||
xl: '90em'
|
||||
}
|
||||
});
|
||||
try {
|
||||
customUserTheme = createTheme({
|
||||
primaryColor: userTheme.primaryColor,
|
||||
white: userTheme.whiteColor,
|
||||
black: userTheme.blackColor,
|
||||
defaultRadius: userTheme.radius,
|
||||
breakpoints: {
|
||||
xs: '30em',
|
||||
sm: '48em',
|
||||
md: '64em',
|
||||
lg: '74em',
|
||||
xl: '90em'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating theme with user settings:', error);
|
||||
// Fallback to default theme if there's an error
|
||||
customUserTheme = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<MantineProvider theme={myTheme} colorSchemeManager={colorSchema}>
|
||||
<MantineProvider theme={customUserTheme} colorSchemeManager={colorSchema}>
|
||||
<ContextMenuProvider>
|
||||
<LanguageContext>
|
||||
<ModalsProvider
|
||||
|
||||
@@ -17,8 +17,8 @@ interface LocalStateProps {
|
||||
hostKey: string;
|
||||
hostList: HostList;
|
||||
setHostList: (newHostList: HostList) => void;
|
||||
language: string;
|
||||
setLanguage: (newLanguage: string, noPatch?: boolean) => void;
|
||||
language: string | null;
|
||||
setLanguage: (newLanguage: string | null, noPatch?: boolean) => void;
|
||||
userTheme: UserTheme;
|
||||
setTheme: (
|
||||
newValues: {
|
||||
@@ -78,7 +78,7 @@ export const useLocalState = create<LocalStateProps>()(
|
||||
hostKey: '',
|
||||
hostList: {},
|
||||
setHostList: (newHostList) => set({ hostList: newHostList }),
|
||||
language: 'en',
|
||||
language: null,
|
||||
setLanguage: (newLanguage, noPatch = false) => {
|
||||
set({ language: newLanguage });
|
||||
if (!noPatch) patchUser('language', newLanguage);
|
||||
|
||||
Reference in New Issue
Block a user