feat: add i18n support and localized labels to certification and profession features commit

This commit is contained in:
mengstabketemaw
2026-06-30 15:57:25 +03:00
parent ef69448bbd
commit 9fd59f52ed
19 changed files with 593 additions and 354 deletions

View File

@@ -17,6 +17,7 @@ import {
Alert,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCertificate } from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import {
@@ -38,6 +39,7 @@ function CertificationForm({
onSubmit: (values: { nameEn: string; nameAm: string; descEn: string; descAm: string }, isEdit: boolean) => void;
onCancel: () => void;
}) {
const { t } = useTranslation();
const [nameEn, setNameEn] = useState(editing?.name?.en ?? '');
const [nameAm, setNameAm] = useState(editing?.name?.am ?? '');
const [descEn, setDescEn] = useState(editing?.description?.en ?? '');
@@ -56,13 +58,13 @@ function CertificationForm({
<Paper p="md" withBorder mb="md" radius="md">
<form onSubmit={handleSubmit}>
<Stack gap="sm">
<TextInput label="Name (English)" placeholder="Certificate name in English" value={nameEn} onChange={(e) => setNameEn(e.currentTarget.value)} size="sm" required />
<TextInput label="Name (Amharic)" placeholder="የምስክር ወረቀት ስም" value={nameAm} onChange={(e) => setNameAm(e.currentTarget.value)} size="sm" required />
<Textarea label="Description (English)" placeholder="English description" value={descEn} onChange={(e) => setDescEn(e.currentTarget.value)} size="sm" autosize minRows={2} />
<Textarea label="Description (Amharic)" placeholder="የአማርኛ መግለጫ" value={descAm} onChange={(e) => setDescAm(e.currentTarget.value)} size="sm" autosize minRows={2} />
<TextInput label={t('certification.form.nameEn')} placeholder={t('certification.form.nameEnPlaceholder')} value={nameEn} onChange={(e) => setNameEn(e.currentTarget.value)} size="sm" required />
<TextInput label={t('certification.form.nameAm')} placeholder={t('certification.form.nameAmPlaceholder')} value={nameAm} onChange={(e) => setNameAm(e.currentTarget.value)} size="sm" required />
<Textarea label={t('certification.form.descEn')} placeholder={t('certification.form.descEnPlaceholder')} value={descEn} onChange={(e) => setDescEn(e.currentTarget.value)} size="sm" autosize minRows={2} />
<Textarea label={t('certification.form.descAm')} placeholder={t('certification.form.descAmPlaceholder')} value={descAm} onChange={(e) => setDescAm(e.currentTarget.value)} size="sm" autosize minRows={2} />
<Group justify="flex-end">
<Button variant="default" onClick={onCancel} size="sm">Cancel</Button>
<Button type="submit" size="sm" loading={isSubmitting}>{editing ? 'Update' : 'Create'}</Button>
<Button variant="default" onClick={onCancel} size="sm">{t('certification.cancel')}</Button>
<Button type="submit" size="sm" loading={isSubmitting}>{editing ? t('certification.update') : t('certification.create')}</Button>
</Group>
</Stack>
</form>
@@ -71,6 +73,8 @@ function CertificationForm({
}
export function CertificationPage() {
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const { data, isLoading, isError } = useGetCertificationsQuery();
const [createCert, { isLoading: isCreating }] = useCreateCertificationMutation();
const [updateCert, { isLoading: isUpdating }] = useUpdateCertificationMutation();
@@ -94,14 +98,14 @@ export function CertificationPage() {
try {
if (isEdit && editing) {
await updateCert({ id: editing.id, name, description }).unwrap();
notify.success('Certification updated');
notify.success(t('certification.updated'));
} else {
await createCert({ name, description }).unwrap();
notify.success('Certification created');
notify.success(t('certification.created'));
}
resetForm();
} catch {
notify.error('Operation failed');
notify.error(t('certification.error'));
}
};
@@ -109,27 +113,27 @@ export function CertificationPage() {
if (!deleteTarget) return;
try {
await deleteCert(deleteTarget.id).unwrap();
notify.success('Certification deleted');
notify.success(t('certification.deleted'));
closeDelete();
setDeleteTarget(null);
} catch {
notify.error('Failed to delete');
notify.error(t('certification.error'));
}
};
if (isLoading) return <Center py="xl"><Loader /></Center>;
if (isError) return <Alert icon={<IconInfoCircle size={16} />} color="red" title="Error loading certifications" />;
if (isError) return <Alert icon={<IconInfoCircle size={16} />} color="red" title={t('certification.loadError')} />;
return (
<Stack gap="lg">
<Group justify="space-between" align="flex-end">
<div>
<Title order={2}>Certifications</Title>
<Text fz="sm" c="dimmed">Manage certification types (e.g. CoC, CoP)</Text>
<Title order={2}>{t('certification.title')}</Title>
<Text fz="sm" c="dimmed">{t('certification.subtitle')}</Text>
</div>
{!showForm && (
<Button variant="light" leftSection={<IconPlus size={16} />} onClick={() => setShowForm(true)} size="sm">
Add Certification
{t('certification.add')}
</Button>
)}
</Group>
@@ -147,24 +151,22 @@ export function CertificationPage() {
<Table striped highlightOnHover>
<Table.Thead bg="var(--mantine-color-default-hover)">
<Table.Tr>
<Table.Th>Name (EN)</Table.Th>
<Table.Th>Name (AM)</Table.Th>
<Table.Th>Description</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th>{t('certification.columns.name')}</Table.Th>
<Table.Th>{t('certification.columns.description')}</Table.Th>
<Table.Th>{t('certification.columns.status')}</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{certifications.map((cert) => (
<Table.Tr key={cert.id}>
<Table.Td><Text fz="sm" fw={500}>{cert.name.en}</Text></Table.Td>
<Table.Td>{cert.name.am}</Table.Td>
<Table.Td><Text fz="sm" fw={500}>{cert.name[locale]}</Text></Table.Td>
<Table.Td>
<Text fz="sm" lineClamp={2} maw={250}>{cert.description.en || cert.description.am}</Text>
<Text fz="sm" lineClamp={2} maw={250}>{cert.description[locale]}</Text>
</Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color={cert.isActive ? 'teal' : 'gray'}>
{cert.isActive ? 'Active' : 'Inactive'}
{cert.isActive ? t('certification.status.active') : t('certification.status.inactive')}
</Badge>
</Table.Td>
<Table.Td>
@@ -181,8 +183,8 @@ export function CertificationPage() {
))}
{certifications.length === 0 && (
<Table.Tr>
<Table.Td colSpan={5}>
<Text c="dimmed" ta="center" py="xl">No certifications found</Text>
<Table.Td colSpan={4}>
<Text c="dimmed" ta="center" py="xl">{t('certification.noItems')}</Text>
</Table.Td>
</Table.Tr>
)}
@@ -190,11 +192,11 @@ export function CertificationPage() {
</Table>
</Paper>
<Modal opened={deleteOpened} onClose={closeDelete} title="Delete Certification" size="sm">
<Text mb="md">Are you sure you want to delete <strong>{deleteTarget?.name?.en}</strong>?</Text>
<Modal opened={deleteOpened} onClose={closeDelete} title={t('certification.confirmDelete')} size="sm">
<Text mb="md">{t('certification.deleteConfirmText', { name: deleteTarget?.name?.[locale] })}</Text>
<Group justify="flex-end">
<Button variant="default" onClick={closeDelete} size="sm">Cancel</Button>
<Button color="red" onClick={handleDelete} size="sm">Delete</Button>
<Button variant="default" onClick={closeDelete} size="sm">{t('certification.cancel')}</Button>
<Button color="red" onClick={handleDelete} size="sm">{t('certification.delete')}</Button>
</Group>
</Modal>
</Stack>