mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-30 00:38:12 +00:00
added internationalization for backoffice
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Group,
|
||||
Text,
|
||||
@@ -29,23 +30,19 @@ interface AppHeaderProps {
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const ROUTE_LABELS: Record<string, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
um: 'User Management',
|
||||
};
|
||||
|
||||
function useBreadcrumbs() {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const segments = location.pathname.split('/').filter(Boolean);
|
||||
|
||||
const crumbs = [
|
||||
{ label: 'Home', path: '/dashboard', icon: <IconHome size={14} /> },
|
||||
{ label: t('breadcrumbs.home'), path: '/dashboard', icon: <IconHome size={14} /> },
|
||||
...segments
|
||||
.filter((seg) => ROUTE_LABELS[seg])
|
||||
.filter((seg) => ['dashboard', 'um'].includes(seg))
|
||||
.map((seg, i) => ({
|
||||
label: ROUTE_LABELS[seg],
|
||||
label: t(`breadcrumbs.${seg}`),
|
||||
path: '/' + segments.slice(0, i + 1).join('/'),
|
||||
icon: null,
|
||||
})),
|
||||
@@ -55,6 +52,7 @@ function useBreadcrumbs() {
|
||||
}
|
||||
|
||||
export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { crumbs } = useBreadcrumbs();
|
||||
@@ -106,7 +104,7 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
<ColorSchemeToggle />
|
||||
|
||||
<Indicator color="red" size={8} offset={6} withBorder>
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label="Notifications">
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label={t('common.notifications')}>
|
||||
<IconBell size={19} />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
@@ -123,7 +121,7 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
Amanuel D.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" visibleFrom="sm">
|
||||
Administrator
|
||||
{t('common.administrator')}
|
||||
</Text>
|
||||
</div>
|
||||
<IconChevronDown size={14} />
|
||||
@@ -131,11 +129,11 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
</UnstyledButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item leftSection={<IconUserCircle size={16} />}>Profile</Menu.Item>
|
||||
<Menu.Item leftSection={<IconSettings size={16} />}>Settings</Menu.Item>
|
||||
<Menu.Item leftSection={<IconUserCircle size={16} />}>{t('common.profile')}</Menu.Item>
|
||||
<Menu.Item leftSection={<IconSettings size={16} />}>{t('common.settings')}</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item color="red" leftSection={<IconLogout size={16} />} onClick={handleLogout}>
|
||||
Logout
|
||||
{t('common.logout')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AppShell,
|
||||
Box,
|
||||
@@ -20,12 +21,8 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { logout } from '@ema-platform/auth';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ label: 'Dashboard', icon: IconLayoutDashboard, path: '/dashboard' },
|
||||
{ label: 'User Management', icon: IconUsers, path: '/um/user-management/dashboard' },
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { pathname } = useLocation();
|
||||
@@ -44,10 +41,10 @@ export function AppSidebar() {
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={700} lh={1.1}>
|
||||
EMA Admin
|
||||
{t('app.name')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" lh={1.2}>
|
||||
Control Center
|
||||
{t('app.tagline')}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
@@ -56,20 +53,28 @@ export function AppSidebar() {
|
||||
<AppShell.Section grow component={ScrollArea}>
|
||||
<Stack gap={4}>
|
||||
<Text size="xs" fw={700} c="dimmed" px="xs" mb={4} style={{ letterSpacing: 1 }}>
|
||||
MENU
|
||||
{t('nav.menu')}
|
||||
</Text>
|
||||
{NAV_ITEMS.map(({ label, icon: Icon, path }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
label={label}
|
||||
leftSection={<Icon size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith(path)}
|
||||
onClick={() => navigate(path)}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
))}
|
||||
<NavLink
|
||||
key="dashboard"
|
||||
label={t('nav.dashboard')}
|
||||
leftSection={<IconLayoutDashboard size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith('/dashboard')}
|
||||
onClick={() => navigate('/dashboard')}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
<NavLink
|
||||
key="um"
|
||||
label={t('nav.userManagement')}
|
||||
leftSection={<IconUsers size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith('/um')}
|
||||
onClick={() => navigate('/um/user-management/dashboard')}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
</Stack>
|
||||
</AppShell.Section>
|
||||
|
||||
@@ -91,13 +96,13 @@ export function AppSidebar() {
|
||||
Amanuel D.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Administrator
|
||||
{t('common.administrator')}
|
||||
</Text>
|
||||
</Box>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label="Log out"
|
||||
aria-label={t('common.logout')}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<IconLogout size={18} />
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Menu, ActionIcon } from '@mantine/core';
|
||||
import { IconWorld, IconCheck } from '@tabler/icons-react';
|
||||
|
||||
const LANGUAGES = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'am', label: 'አማርኛ' },
|
||||
];
|
||||
import { i18n, SUPPORTED_LANGUAGES } from '../../i18n/config';
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const [current, setCurrent] = useState('en');
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Menu shadow="md" width={160} position="bottom-end" withinPortal>
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label="Language">
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label={t('language.label')}>
|
||||
<IconWorld size={19} />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>Language</Menu.Label>
|
||||
{LANGUAGES.map((lng) => (
|
||||
<Menu.Label>{t('language.label')}</Menu.Label>
|
||||
{SUPPORTED_LANGUAGES.map((code) => (
|
||||
<Menu.Item
|
||||
key={lng.code}
|
||||
onClick={() => setCurrent(lng.code)}
|
||||
rightSection={current === lng.code ? <IconCheck size={16} /> : undefined}
|
||||
key={code}
|
||||
onClick={() => i18n.changeLanguage(code)}
|
||||
rightSection={i18n.language === code ? <IconCheck size={16} /> : undefined}
|
||||
>
|
||||
{lng.label}
|
||||
{t(`language.${code}`)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
|
||||
Reference in New Issue
Block a user