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

@@ -35,7 +35,8 @@ export function LocationDetail({
onEdit,
onDelete,
}: LocationDetailProps) {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const typeInfo = locationTypes.find(
(lt) => lt.id === location.locationTypeId,
);
@@ -47,7 +48,7 @@ export function LocationDetail({
return (
<Paper p="lg" radius="md" withBorder>
<Group justify="space-between" mb="xs">
<Title order={4}>{location.names.en}</Title>
<Title order={4}>{location.names[locale]}</Title>
<Group gap="xs">
<Button
variant="light"
@@ -89,7 +90,7 @@ export function LocationDetail({
</Badge>
{typeInfo && (
<Badge size="lg" variant="light" color="teal">
{typeInfo.names.en}
{typeInfo.names[locale]}
</Badge>
)}
</Group>
@@ -99,15 +100,9 @@ export function LocationDetail({
<Stack gap="sm">
<div>
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>
{t('location.nameEn')}
{t('location.name')}
</Text>
<Text size="sm">{location.names.en}</Text>
</div>
<div>
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>
{t('location.nameAm')}
</Text>
<Text size="sm">{location.names.am}</Text>
<Text size="sm">{location.names[locale]}</Text>
</div>
<div>
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>
@@ -120,7 +115,7 @@ export function LocationDetail({
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>
{t('location.type')}
</Text>
<Text size="sm">{typeInfo.names.en} (Level {typeInfo.level})</Text>
<Text size="sm">{typeInfo.names[locale]} ({t('location.level')} {typeInfo.level})</Text>
</div>
)}
</Stack>

View File

@@ -44,7 +44,8 @@ export function LocationForm({
onCancel,
isSubmitting,
}: LocationFormProps) {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const isEditing = !!editingLocation;
@@ -125,7 +126,7 @@ export function LocationForm({
{parentLocation && !isEditing && (
<TextInput
label={t('location.parent')}
value={parentLocation.names.en}
value={parentLocation.names[locale]}
disabled
mb="sm"
size="sm"
@@ -139,7 +140,7 @@ export function LocationForm({
placeholder={t('location.selectType')}
data={allAtLevel.map((lt) => ({
value: lt.id,
label: lt.names.en,
label: lt.names[locale],
}))}
{...form.getInputProps('locationTypeId')}
size="sm"
@@ -153,7 +154,7 @@ export function LocationForm({
</Text>
{type ? (
<Badge size="lg" variant="light" color="blue">
{type.names.en}
{type.names[locale]}
</Badge>
) : (
<Text size="sm" c="red">

View File

@@ -32,7 +32,8 @@ interface LocationTypeFormValues {
}
export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClose: () => void }) {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const { data: locationTypes, isLoading } = useGetLocationTypesQuery();
const [createType] = useCreateLocationTypeMutation();
const [updateType] = useUpdateLocationTypeMutation();
@@ -49,9 +50,9 @@ export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClos
level: 1,
},
validate: {
code: (v) => (!v ? 'Code is required' : null),
namesEn: (v) => (!v ? 'English name is required' : null),
namesAm: (v) => (!v ? 'Amharic name is required' : null),
code: (v) => (!v ? t('location.validation.codeRequired') : null),
namesEn: (v) => (!v ? t('location.validation.nameEnRequired') : null),
namesAm: (v) => (!v ? t('location.validation.nameAmRequired') : null),
level: (v) => (v < 1 ? 'Level must be at least 1' : null),
},
});
@@ -119,6 +120,7 @@ export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClos
variant="light"
leftSection={<IconPlus size={16} />}
onClick={() => setShowForm(true)}
mt="md"
mb="md"
size="sm"
>
@@ -130,25 +132,25 @@ export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClos
<form onSubmit={handleSubmit}>
<Stack gap="sm" mb="md">
<TextInput
label="Code"
label={t('location.code')}
placeholder="e.g., COUNTRY, REGION, CITY"
{...form.getInputProps('code')}
size="sm"
/>
<TextInput
label="Name (English)"
label={t('location.nameEn')}
placeholder="English name"
{...form.getInputProps('namesEn')}
size="sm"
/>
<TextInput
label="Name (Amharic)"
label={t('location.nameAm')}
placeholder="የአማርኛ ስም"
{...form.getInputProps('namesAm')}
size="sm"
/>
<NumberInput
label="Level"
label={t('location.level')}
placeholder="1"
min={1}
max={10}
@@ -179,10 +181,9 @@ export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClos
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Level</Table.Th>
<Table.Th>Code</Table.Th>
<Table.Th>Name (EN)</Table.Th>
<Table.Th>Name (AM)</Table.Th>
<Table.Th>{t('location.level')}</Table.Th>
<Table.Th>{t('location.code')}</Table.Th>
<Table.Th>{t('location.name')}</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
@@ -199,8 +200,7 @@ export function LocationTypeModal({ opened, onClose }: { opened: boolean; onClos
{type.code}
</Badge>
</Table.Td>
<Table.Td>{type.names.en}</Table.Td>
<Table.Td>{type.names.am}</Table.Td>
<Table.Td>{type.names[locale]}</Table.Td>
<Table.Td>
<Group gap="xs">
<ActionIcon

View File

@@ -31,7 +31,8 @@ import {
import type { Location } from '../types/location';
export function LocationPage() {
const { t } = useTranslation();
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const { data: locationTypesRes, isLoading: typesLoading } = useGetLocationTypesQuery();
const [createLocation, { isLoading: isCreating }] = useCreateLocationMutation();
const [updateLocation, { isLoading: isUpdating }] = useUpdateLocationMutation();
@@ -221,7 +222,7 @@ export function LocationPage() {
>
<Text mb="md">
{t('location.deleteConfirmText', {
name: selectedLocation?.names.en ?? '',
name: selectedLocation?.names[locale] ?? '',
})}
</Text>
<Group justify="flex-end">