@@ -393,8 +405,8 @@ export function MedicalVerificationPage() {
),
},
{
- header: 'Vessel',
- label: 'Vessel',
+ header: t('recordVerification.columns.vessel', 'Vessel'),
+ label: t('recordVerification.columns.vessel', 'Vessel'),
accessorKey: 'vesselName',
cell: ({ row }) => (
@@ -408,14 +420,14 @@ export function MedicalVerificationPage() {
),
},
{
- header: 'Rank',
- label: 'Rank',
+ header: t('recordVerification.columns.rank', 'Rank'),
+ label: t('recordVerification.columns.rank', 'Rank'),
accessorKey: 'rank',
cell: ({ row }) =>
{row.original.rank} ,
},
{
- header: 'Period',
- label: 'Period',
+ header: t('recordVerification.columns.period', 'Period'),
+ label: t('recordVerification.columns.period', 'Period'),
cell: ({ row }) => (
{showDate(row.original.engagementDate)} → {showDate(row.original.dischargeDate)}
@@ -424,7 +436,7 @@ export function MedicalVerificationPage() {
},
{
header: '',
- label: 'Actions',
+ label: t('recordVerification.columns.actions', 'Actions'),
align: 'right',
cell: ({ row }) => (
@@ -437,11 +449,14 @@ export function MedicalVerificationPage() {
setAttachmentModal({
ownerType: 'SEA_SERVICE_RECORD',
ownerId: row.original.id,
- title: `Evidence for ${ownerName(row.original.profile)} Sea Service Record`,
+ title: t('recordVerification.evidenceTitleSeaService', {
+ name: ownerName(row.original.profile),
+ defaultValue: 'Evidence for {{name}} Sea Service Record',
+ }),
})
}
>
- Evidence
+ {t('recordVerification.evidence', 'Evidence')}
- Verify
+ {t('recordVerification.verify', 'Verify')}
}
onClick={() => setRejectSeaService(row.original)}
>
- Reject
+ {t('recordVerification.reject', 'Reject')}
),
},
],
- [rulingSeaService, rule, verifySeaService, showDate],
+ [rulingSeaService, rule, verifySeaService, showDate, t],
);
return (
- Record verification
+ {t('recordVerification.title', 'Record verification')}
- Submitted sea-service records and medical certificates awaiting a
- ruling. Verified records are frozen; rejections return to the seafarer
- with your remark.
+ {t(
+ 'recordVerification.subtitle',
+ 'Submitted sea-service records and medical certificates awaiting a ruling. Verified records are frozen; rejections return to the seafarer with your remark.',
+ )}
}>
- Medical ({pendingMedicalList.length})
+ {t('recordVerification.tabs.medical', {
+ count: pendingMedicalList.length,
+ defaultValue: 'Medical ({{count}})',
+ })}
}>
- Sea Service ({pendingSeaServiceList.length})
+ {t('recordVerification.tabs.seaService', {
+ count: pendingSeaServiceList.length,
+ defaultValue: 'Sea Service ({{count}})',
+ })}
@@ -502,7 +524,10 @@ export function MedicalVerificationPage() {
@@ -518,7 +543,10 @@ export function MedicalVerificationPage() {
@@ -535,12 +563,12 @@ export function MedicalVerificationPage() {
opened={Boolean(attachmentModal)}
ownerType={attachmentModal?.ownerType ?? 'MEDICAL_CERTIFICATE'}
ownerId={attachmentModal?.ownerId ?? null}
- title={attachmentModal?.title ?? 'Evidence Attachments'}
+ title={attachmentModal?.title ?? t('recordVerification.evidenceModalTitle', 'Evidence Attachments')}
onClose={() => setAttachmentModal(null)}
/>
setRejectMedical(null)}
loading={rulingMedical}
@@ -553,13 +581,13 @@ export function MedicalVerificationPage() {
outcome: 'REJECTED',
remark,
}).unwrap(),
- 'Certificate rejected',
+ t('recordVerification.certificateRejected', 'Certificate rejected'),
);
setRejectMedical(null);
}}
/>
setRejectSeaService(null)}
loading={rulingSeaService}
@@ -572,7 +600,7 @@ export function MedicalVerificationPage() {
outcome: 'REJECTED',
remark,
}).unwrap(),
- 'Sea-service record rejected',
+ t('recordVerification.seaServiceRejected', 'Sea-service record rejected'),
);
setRejectSeaService(null);
}}
diff --git a/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx b/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx
index 5d4055386..a393a3032 100644
--- a/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx
+++ b/apps/backoffice/src/app/features/payment-config/pages/PaymentConfigPage.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import {
Alert,
Badge,
@@ -59,6 +60,7 @@ function feeText(amount: string | number | null, currency: string): string {
}
export function PaymentConfigPage() {
+ const { t } = useTranslation();
const { data, isLoading, isFetching, error, refetch } =
useGetLicenseTypesQuery();
const { data: capabilities } = useGetPaymentCapabilitiesQuery();
@@ -78,7 +80,7 @@ export function PaymentConfigPage() {
}
- title="Could not load licence types"
+ title={t('paymentConfig.loadError', 'Could not load licence types')}
>
{extractErrorMessage(error)}
@@ -92,7 +94,7 @@ export function PaymentConfigPage() {
const columns: AdvancedColumn[] = [
{
- header: 'Licence type',
+ header: t('paymentConfig.columns.type', 'Licence type'),
cell: ({ row }) => (
<>
@@ -105,7 +107,7 @@ export function PaymentConfigPage() {
),
},
{
- header: 'New application',
+ header: t('paymentConfig.columns.newApplication', 'New application'),
cell: ({ row }) => (
{feeText(row.original.feeNewApplication, row.original.feeCurrency)}
@@ -113,7 +115,7 @@ export function PaymentConfigPage() {
),
},
{
- header: 'Renewal',
+ header: t('paymentConfig.columns.renewal', 'Renewal'),
cell: ({ row }) => {
const type = row.original;
if (type.feeNewApplication === null) {
@@ -126,11 +128,16 @@ export function PaymentConfigPage() {
}
if (type.feeRenewal === null) {
return (
-
+
{feeText(type.feeNewApplication, type.feeCurrency)}{' '}
- (same as new)
+ {t('paymentConfig.renewalSameAsNew', '(same as new)')}
@@ -144,16 +151,21 @@ export function PaymentConfigPage() {
},
},
{
- header: 'Charged?',
+ header: t('paymentConfig.columns.charged', 'Charged?'),
cell: ({ row }) =>
row.original.issuesCertificate ? (
- On approval
+ {t('paymentConfig.chargedOnApproval', 'On approval')}
) : (
-
+
- Not charged
+ {t('paymentConfig.chargedNotCharged', 'Not charged')}
),
@@ -169,7 +181,7 @@ export function PaymentConfigPage() {
leftSection={ }
onClick={() => setEditing(row.original)}
>
- Edit
+ {t('paymentConfig.edit', 'Edit')}
),
},
@@ -179,10 +191,12 @@ export function PaymentConfigPage() {
-
Payment configuration
+ {t('paymentConfig.title', 'Payment configuration')}
- What each licence costs. Applicants are charged after approval, and
- the amount is fixed onto the application at that moment.
+ {t(
+ 'paymentConfig.subtitle',
+ 'What each licence costs. Applicants are charged after approval, and the amount is fixed onto the application at that moment.',
+ )}
@@ -197,9 +211,10 @@ export function PaymentConfigPage() {
icon={ }
>
- A change applies to applications approved from now on. Anything
- already approved keeps the amount it was quoted, so an edit here can
- never alter what an applicant has already been asked to pay.
+ {t(
+ 'paymentConfig.changeNotice',
+ 'A change applies to applications approved from now on. Anything already approved keeps the amount it was quoted, so an edit here can never alter what an applicant has already been asked to pay.',
+ )}
@@ -229,6 +244,7 @@ export function PaymentConfigPage() {
* them as editable fields would be a lie.
*/
function GatewayPanel({ bypassEnabled }: { bypassEnabled?: boolean }) {
+ const { t } = useTranslation();
return (
@@ -236,23 +252,31 @@ function GatewayPanel({ bypassEnabled }: { bypassEnabled?: boolean }) {
- Payment gateway
+ {t('paymentConfig.gateway.title', 'Payment gateway')}
- Set by the payment service environment — not editable here.
+ {t(
+ 'paymentConfig.gateway.subtitle',
+ 'Set by the payment service environment — not editable here.',
+ )}
- Telebirr
+ {t('paymentConfig.gateway.provider', 'Telebirr')}
{bypassEnabled ? (
-
+
- Test bypass enabled
+ {t('paymentConfig.gateway.bypassEnabled', 'Test bypass enabled')}
) : (
- Test bypass off
+ {t('paymentConfig.gateway.bypassOff', 'Test bypass off')}
)}
@@ -267,6 +291,7 @@ function FeeEditModal({
licenseType: LicenseType | null;
onClose: () => void;
}) {
+ const { t } = useTranslation();
const [updateFees, { isLoading }] = useUpdateLicenseFeesMutation();
const [newFee, setNewFee] = useState('');
const [renewalFee, setRenewalFee] = useState('');
@@ -294,11 +319,21 @@ function FeeEditModal({
async function save() {
if (!licenseType) return;
if (chargeable && newFee === '') {
- notify.error('Enter a new-application fee, or turn off "carries a fee".');
+ notify.error(
+ t(
+ 'paymentConfig.modal.missingNewFee',
+ 'Enter a new-application fee, or turn off "carries a fee".',
+ ),
+ );
return;
}
if (chargeable && !sameAsNew && renewalFee === '') {
- notify.error('Enter a renewal fee, or charge renewal at the same rate.');
+ notify.error(
+ t(
+ 'paymentConfig.modal.missingRenewalFee',
+ 'Enter a renewal fee, or charge renewal at the same rate.',
+ ),
+ );
return;
}
try {
@@ -308,10 +343,17 @@ function FeeEditModal({
feeRenewal: chargeable && !sameAsNew ? Number(renewalFee) : null,
feeCurrency: currency.trim() || 'ETB',
}).unwrap();
- notify.success(`${localized(licenseType.name)} fees updated.`);
+ notify.success(
+ t('paymentConfig.modal.updated', {
+ type: localized(licenseType.name),
+ defaultValue: '{{type}} fees updated.',
+ }),
+ );
onClose();
} catch (err) {
- notify.error(extractErrorMessage(err, 'Could not save the fees'));
+ notify.error(
+ extractErrorMessage(err, t('paymentConfig.modal.saveFailed', 'Could not save the fees')),
+ );
}
}
@@ -319,7 +361,14 @@ function FeeEditModal({
@@ -332,9 +381,10 @@ function FeeEditModal({
icon={ }
>
- This licence type concludes with an EMA decision and never
- reaches a payment stage, so a fee set here stays unused until
- that changes.
+ {t(
+ 'paymentConfig.modal.noPaymentStage',
+ 'This licence type concludes with an EMA decision and never reaches a payment stage, so a fee set here stays unused until that changes.',
+ )}
)}
@@ -342,14 +392,17 @@ function FeeEditModal({
setChargeable(e.currentTarget.checked)}
- label="This licence carries a fee"
- description="Turn off for licence types applicants are never charged for."
+ label={t('paymentConfig.modal.chargeableLabel', 'This licence carries a fee')}
+ description={t(
+ 'paymentConfig.modal.chargeableDescription',
+ 'Turn off for licence types applicants are never charged for.',
+ )}
/>
{chargeable && (
<>
setNewFee(v === '' ? '' : Number(v))}
min={0}
@@ -362,13 +415,16 @@ function FeeEditModal({
setSameAsNew(e.currentTarget.checked)}
- label="Charge renewal at the same rate"
- description="Turn off to set a separate renewal fee."
+ label={t('paymentConfig.modal.sameRateLabel', 'Charge renewal at the same rate')}
+ description={t(
+ 'paymentConfig.modal.sameRateDescription',
+ 'Turn off to set a separate renewal fee.',
+ )}
/>
{!sameAsNew && (
setRenewalFee(v === '' ? '' : Number(v))}
min={0}
@@ -380,7 +436,7 @@ function FeeEditModal({
)}
setCurrency(e.currentTarget.value.toUpperCase())
@@ -392,10 +448,10 @@ function FeeEditModal({
- Cancel
+ {t('paymentConfig.modal.cancel', 'Cancel')}
- Save fees
+ {t('paymentConfig.modal.save', 'Save fees')}
diff --git a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx
index 1b0231691..336953f66 100644
--- a/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx
+++ b/apps/backoffice/src/app/features/seafarer-registry/pages/SeafarerRegistryPage.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
import {
Badge,
Button,
@@ -77,6 +78,7 @@ function SeafarerDetailDrawer({
profile: ProfileRow | null;
onClose: () => void;
}) {
+ const { t } = useTranslation();
const profileId = profile?.id ?? '';
const { data: seaService, isLoading: loadingSea } =
useGetSeaServiceForProfileQuery(profileId, { skip: !profileId });
@@ -103,7 +105,7 @@ function SeafarerDetailDrawer({
- Seafarer number
+ {t('seafarerRegistry.drawer.seafarerNumber', 'Seafarer number')}
{profile.seafarerNumber ?? '—'}
@@ -111,41 +113,49 @@ function SeafarerDetailDrawer({
- Department
+ {t('seafarerRegistry.drawer.department', 'Department')}
{profile.seafarerDepartment
- ? DEPARTMENT_LABELS[profile.seafarerDepartment] ??
- profile.seafarerDepartment
+ ? t(
+ `seafarerRegistry.departments.${profile.seafarerDepartment}`,
+ DEPARTMENT_LABELS[profile.seafarerDepartment] ??
+ profile.seafarerDepartment,
+ )
: '—'}
- Status
+ {t('seafarerRegistry.drawer.status', 'Status')}
- {profile.seafarerStatus ?? 'NOT REGISTERED'}
+ {profile.seafarerStatus
+ ? t(`seafarerRegistry.status.${profile.seafarerStatus}`, profile.seafarerStatus)
+ : t('seafarerRegistry.drawer.notRegistered', 'NOT REGISTERED')}
{profile.seafarerStatusReason && (
- Status reason: {profile.seafarerStatusReason}
+ {t('seafarerRegistry.drawer.statusReason', {
+ reason: profile.seafarerStatusReason,
+ defaultValue: 'Status reason: {{reason}}',
+ })}
)}
}>
- Sea Service
+ {t('seafarerRegistry.drawer.seaServiceTab', 'Sea Service')}
}>
- Medical
+ {t('seafarerRegistry.drawer.medicalTab', 'Medical')}
@@ -154,16 +164,16 @@ function SeafarerDetailDrawer({
) : (seaService ?? []).length === 0 ? (
- No sea-service records.
+ {t('seafarerRegistry.drawer.noSeaService', 'No sea-service records.')}
) : (
- Vessel
- Rank
- Period
- Status
+ {t('seafarerRegistry.drawer.vessel', 'Vessel')}
+ {t('seafarerRegistry.drawer.rank', 'Rank')}
+ {t('seafarerRegistry.drawer.period', 'Period')}
+ {t('seafarerRegistry.drawer.status', 'Status')}
@@ -173,7 +183,10 @@ function SeafarerDetailDrawer({
{record.vesselName}
{record.imoNumber && (
- IMO {record.imoNumber}
+ {t('seafarerRegistry.drawer.imoPrefix', {
+ number: record.imoNumber,
+ defaultValue: 'IMO {{number}}',
+ })}
)}
@@ -183,7 +196,7 @@ function SeafarerDetailDrawer({
- {record.status}
+ {t(`seafarerRegistry.recordStatus.${record.status}`, record.status)}
@@ -198,16 +211,16 @@ function SeafarerDetailDrawer({
) : (medical ?? []).length === 0 ? (
- No medical certificates.
+ {t('seafarerRegistry.drawer.noMedical', 'No medical certificates.')}
) : (
- Issuer
- Validity
- Fitness
- Status
+ {t('seafarerRegistry.drawer.issuer', 'Issuer')}
+ {t('seafarerRegistry.drawer.validity', 'Validity')}
+ {t('seafarerRegistry.drawer.fitness', 'Fitness')}
+ {t('seafarerRegistry.drawer.status', 'Status')}
@@ -223,7 +236,7 @@ function SeafarerDetailDrawer({
size="sm"
color={RECORD_STATUS_COLORS[certificate.status]}
>
- {certificate.status}
+ {t(`seafarerRegistry.recordStatus.${certificate.status}`, certificate.status)}
@@ -249,6 +262,7 @@ function StatusModal({
onClose: () => void;
onDone: () => void;
}) {
+ const { t } = useTranslation();
const [status, setStatus] = useState(null);
const [reason, setReason] = useState('');
const [updateStatus, { isLoading }] = useUpdateSeafarerStatusMutation();
@@ -261,11 +275,13 @@ function StatusModal({
status: status as 'ACTIVE' | 'INACTIVE' | 'SUSPENDED' | 'PENDING',
reason,
}).unwrap();
- notify.success('Seafarer status updated');
+ notify.success(t('seafarerRegistry.modal.updated', 'Seafarer status updated'));
onClose();
onDone();
} catch (error) {
- notify.error(extractErrorMessage(error, 'Could not update the status'));
+ notify.error(
+ extractErrorMessage(error, t('seafarerRegistry.modal.updateFailed', 'Could not update the status')),
+ );
}
};
@@ -273,27 +289,33 @@ function StatusModal({
- {profile?.seafarerNumber} — currently {profile?.seafarerStatus}. The
- reason is recorded and visible to the seafarer.
+ {t('seafarerRegistry.modal.body', {
+ number: profile?.seafarerNumber,
+ status: profile?.seafarerStatus
+ ? t(`seafarerRegistry.status.${profile.seafarerStatus}`, profile.seafarerStatus)
+ : profile?.seafarerStatus,
+ defaultValue:
+ '{{number}} — currently {{status}}. The reason is recorded and visible to the seafarer.',
+ })}
o.value !== profile?.seafarerStatus)}
value={status}
onChange={setStatus}
/>
- Cancel
+ {t('seafarerRegistry.modal.cancel', 'Cancel')}
- Confirm
+ {t('seafarerRegistry.modal.confirm', 'Confirm')}
@@ -325,6 +347,7 @@ function StatusModal({
* register — numbers, departments, statuses, and each seafarer's records.
*/
export function SeafarerRegistryPage() {
+ const { t } = useTranslation();
const [search, setSearch] = useState('');
const [detail, setDetail] = useState(null);
const [statusTarget, setStatusTarget] = useState(null);
@@ -355,7 +378,7 @@ export function SeafarerRegistryPage() {
const columns: AdvancedColumn[] = [
{
- header: 'Name',
+ header: t('seafarerRegistry.columns.name', 'Name'),
cell: ({ row }) => (
{row.original.seafarerNumber ?? '—'} ,
},
{
- header: 'Department',
+ header: t('seafarerRegistry.columns.department', 'Department'),
cell: ({ row }) => (
{row.original.seafarerDepartment
- ? DEPARTMENT_LABELS[row.original.seafarerDepartment] ?? row.original.seafarerDepartment
+ ? t(
+ `seafarerRegistry.departments.${row.original.seafarerDepartment}`,
+ DEPARTMENT_LABELS[row.original.seafarerDepartment] ?? row.original.seafarerDepartment,
+ )
: '—'}
),
},
{
- header: 'ID number',
+ header: t('seafarerRegistry.columns.idNumber', 'ID number'),
cell: ({ row }) => {row.original.address?.idNumber ?? '—'} ,
},
{
- header: 'Phone',
+ header: t('seafarerRegistry.columns.phone', 'Phone'),
cell: ({ row }) => {row.original.address?.primaryPhoneNumber ?? '—'} ,
},
{
- header: 'Status',
+ header: t('seafarerRegistry.columns.status', 'Status'),
cell: ({ row }) =>
row.original.seafarerNumber ? (
- {row.original.seafarerStatus}
+ {t(`seafarerRegistry.status.${row.original.seafarerStatus}`, row.original.seafarerStatus ?? '')}
) : (
- {row.original.isComplete ? 'Not registered' : 'Incomplete'}
+ {row.original.isComplete
+ ? t('seafarerRegistry.notRegistered', 'Not registered')
+ : t('seafarerRegistry.incomplete', 'Incomplete')}
),
},
{
header: '',
- label: 'Actions',
+ label: t('seafarerRegistry.columns.actions', 'Actions'),
cell: ({ row }) =>
row.original.seafarerNumber ? (
-
+
}
onClick={() => setStatusTarget(row.original)}
>
- Status
+ {t('seafarerRegistry.statusAction', 'Status')}
) : null,
@@ -425,13 +453,16 @@ export function SeafarerRegistryPage() {
-
Seafarer registry
+ {t('seafarerRegistry.title', 'Seafarer registry')}
- {data?.total ?? 0} profile{(data?.total ?? 0) === 1 ? '' : 's'}
+ {t('seafarerRegistry.profileCount', {
+ count: data?.total ?? 0,
+ defaultValue: '{{count}} profile(s)',
+ })}
}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
@@ -443,7 +474,7 @@ export function SeafarerRegistryPage() {
diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts
index 01c0f4e1a..cf28d12c5 100644
--- a/apps/backoffice/src/app/i18n/locales/am.ts
+++ b/apps/backoffice/src/app/i18n/locales/am.ts
@@ -786,6 +786,23 @@ export const am: Translations = {
anyType: "ማንኛውም",
typeCol: "ዓይነት",
statusCol: "ሁኔታ",
+ statusValues: {
+ DRAFT: "ረቂቅ",
+ SUBMITTED: "ቀርቧል",
+ UNDER_REVIEW: "በግምገማ ላይ",
+ UNDER_EVALUATION: "በምዘና ላይ",
+ RESUBMIT_REQUIRED: "እንደገና ማቅረብ ያስፈልጋል",
+ INSPECTION_PENDING: "ምርመራ በመጠባበቅ ላይ",
+ INSPECTION_COMPLETED: "ምርመራ ተጠናቋል",
+ APPROVED: "ጸድቋል",
+ REJECTED: "ውድቅ ተደርጓል",
+ ON_HOLD: "ለጊዜው ተይዟል",
+ PAYMENT_PENDING: "ክፍያ በመጠባበቅ ላይ",
+ PAID: "ተከፍሏል",
+ PAYMENT_CONFIRMED: "ሰርተፍኬት በዝግጅት ላይ",
+ CERTIFICATE_ISSUED: "ሰርተፍኬት ተሰጥቷል",
+ COMPLETED: "ተጠናቋል",
+ },
submittedFrom: "ከቀን ጀምሮ የቀረበ",
submittedTo: "እስከ ቀን የቀረበ",
clearFilters: "አጽዳ",
@@ -1071,4 +1088,160 @@ export const am: Translations = {
noPermission: "ፈቃድ የለዎትም",
noPublishPermission: "ንድፎችን ማተም አይችሉም",
},
+
+ seafarerRegistry: {
+ title: "የመርከበኞች መዝገብ",
+ profileCount_one: "{{count}} መገለጫ",
+ profileCount_other: "{{count}} መገለጫዎች",
+ searchPlaceholder: "ስም፣ መታወቂያ ወይም የመርከበኛ ቁጥር",
+ emptySearch: "ከዚህ ፍለጋ ጋር የሚዛመድ መገለጫ የለም።",
+ emptyNone: "እስካሁን የተመዘገበ መርከበኛ የለም።",
+ columns: {
+ name: "ስም",
+ number: "የመርከበኛ ቁ.",
+ department: "ክፍል",
+ idNumber: "የመታወቂያ ቁጥር",
+ phone: "ስልክ",
+ status: "ሁኔታ",
+ actions: "ተግባራት",
+ },
+ notRegistered: "አልተመዘገበም",
+ incomplete: "ያልተሟላ",
+ statusAction: "ሁኔታ",
+ statusActionTooltip: "አግድ / መልስ / ዝጋ",
+ status: {
+ ACTIVE: "ንቁ",
+ INACTIVE: "ንቁ ያልሆነ",
+ PENDING: "በመጠባበቅ ላይ",
+ SUSPENDED: "ታግዷል",
+ },
+ departments: {
+ DECK: "ደክ",
+ ENGINE: "ኤንጂን",
+ CATERING: "ኬተሪንግ",
+ },
+ recordStatus: {
+ SUBMITTED: "ቀርቧል",
+ VERIFIED: "ተረጋግጧል",
+ REJECTED: "ውድቅ ተደርጓል",
+ },
+ drawer: {
+ seafarerNumber: "የመርከበኛ ቁጥር",
+ department: "ክፍል",
+ status: "ሁኔታ",
+ notRegistered: "አልተመዘገበም",
+ statusReason: "የሁኔታ ምክንያት: {{reason}}",
+ seaServiceTab: "የባህር አገልግሎት",
+ medicalTab: "ህክምና",
+ noSeaService: "ምንም የባህር አገልግሎት መዝገቦች የሉም።",
+ noMedical: "ምንም የህክምና ሰርተፍኬቶች የሉም።",
+ vessel: "መርከብ",
+ rank: "ማዕረግ",
+ period: "ጊዜ",
+ imoPrefix: "IMO {{number}}",
+ issuer: "አውጪ",
+ validity: "የሚያገለግልበት ጊዜ",
+ fitness: "ብቃት",
+ },
+ modal: {
+ title: "የመርከበኛ ሁኔታ ይቀይሩ",
+ body: "{{number}} — በአሁኑ ጊዜ {{status}}። ምክንያቱ ተመዝግቦ ለመርከበኛው ይታያል።",
+ newStatus: "አዲስ ሁኔታ",
+ suspend: "አግድ",
+ close: "ዝጋ",
+ reinstate: "መልስ",
+ reason: "ምክንያት",
+ cancel: "ሰርዝ",
+ confirm: "አረጋግጥ",
+ updated: "የመርከበኛ ሁኔታ ተዘምኗል",
+ updateFailed: "ሁኔታውን ማዘመን አልተቻለም",
+ },
+ },
+
+ paymentConfig: {
+ title: "የክፍያ ውቅረት",
+ subtitle: "እያንዳንዱ ፈቃድ ስንት እንደሚያስከፍል። አመልካቾች ከጸደቀ በኋላ ይከፍላሉ፣ መጠኑም በዚያ ቅጽበት በማመልከቻው ላይ ይወሰናል።",
+ changeNotice: "ለውጥ ከአሁን ጀምሮ በሚጸድቁ ማመልከቻዎች ላይ ተፈጻሚ ይሆናል። ቀደም ሲል የጸደቁ ማመልከቻዎች የተጠየቁበትን መጠን ይይዛሉ፣ ስለዚህ እዚህ የሚደረግ ማስተካከያ አመልካች ቀደም ሲል የተጠየቀውን ክፍያ በፍጹም ሊቀይር አይችልም።",
+ loadError: "የፈቃድ ዓይነቶችን መጫን አልተቻለም",
+ columns: {
+ type: "የፈቃድ ዓይነት",
+ newApplication: "አዲስ ማመልከቻ",
+ renewal: "እድሳት",
+ charged: "ይከፈላል?",
+ },
+ renewalSameTooltip: "የተለየ የእድሳት ክፍያ የለም — እድሳት በአዲስ ማመልከቻ ተመን ይከፈላል",
+ renewalSameAsNew: "(እንደ አዲሱ)",
+ chargedOnApproval: "ሲጸድቅ",
+ chargedNotChargedTooltip: "ይህ የፈቃድ ዓይነት በኢማ ውሳኔ ያበቃል እና ወደ ክፍያ ደረጃ አይደርስም",
+ chargedNotCharged: "አይከፈልም",
+ edit: "አርትዕ",
+ gateway: {
+ title: "የክፍያ በር",
+ subtitle: "በክፍያ አገልግሎቱ አካባቢ የተቀመጠ — እዚህ አይስተካከልም።",
+ provider: "ቴሌብር",
+ bypassTooltip: "ALLOW_PAYMENT_BYPASS በርቷል፣ ስለዚህ አመልካች ሳይከፍል ክፍያን ማጠናቀቅ ይችላል። በምርት አካባቢ ይህ ተከልክሏል።",
+ bypassEnabled: "የሙከራ ማለፊያ በርቷል",
+ bypassOff: "የሙከራ ማለፊያ ጠፍቷል",
+ },
+ modal: {
+ title: "ክፍያዎች — {{type}}",
+ noPaymentStage: "ይህ የፈቃድ ዓይነት በኢማ ውሳኔ ያበቃል እና ወደ ክፍያ ደረጃ አይደርስም፣ ስለዚህ እዚህ የሚቀመጥ ክፍያ ይህ እስኪቀየር ድረስ ጥቅም ላይ አይውልም።",
+ chargeableLabel: "ይህ ፈቃድ ክፍያ አለው",
+ chargeableDescription: "አመልካቾች ፈጽሞ ለማይከፍሉባቸው የፈቃድ ዓይነቶች ያጥፉ።",
+ newFeeLabel: "የአዲስ ማመልከቻ ክፍያ",
+ sameRateLabel: "እድሳትን በተመሳሳይ ተመን ያስከፍሉ",
+ sameRateDescription: "የተለየ የእድሳት ክፍያ ለማዘጋጀት ያጥፉ።",
+ renewalFeeLabel: "የእድሳት ክፍያ",
+ currencyLabel: "ገንዘብ",
+ cancel: "ሰርዝ",
+ save: "ክፍያዎችን አስቀምጥ",
+ missingNewFee: "የአዲስ ማመልከቻ ክፍያ ያስገቡ፣ ወይም «ክፍያ አለው»ን ያጥፉ።",
+ missingRenewalFee: "የእድሳት ክፍያ ያስገቡ፣ ወይም እድሳትን በተመሳሳይ ተመን ያስከፍሉ።",
+ updated: "{{type}} ክፍያዎች ተዘምነዋል።",
+ saveFailed: "ክፍያዎቹን ማስቀመጥ አልተቻለም",
+ },
+ },
+
+ recordVerification: {
+ title: "የመዝገብ ማረጋገጫ",
+ subtitle: "ውሳኔ የሚጠባበቁ የቀረቡ የባህር አገልግሎት መዝገቦች እና የሕክምና የምስክር ወረቀቶች። የተረጋገጡ መዝገቦች ይቀዘቅዛሉ፤ ውድቅ የተደረጉት ከእርስዎ አስተያየት ጋር ለመርከበኛው ይመለሳሉ።",
+ tabs: {
+ medical: "ሕክምና ({{count}})",
+ seaService: "የባህር አገልግሎት ({{count}})",
+ },
+ columns: {
+ seafarer: "መርከበኛ",
+ issuer: "አውጪ",
+ validity: "የሚያገለግልበት ጊዜ",
+ fitness: "ብቃት",
+ vessel: "መርከብ",
+ rank: "ማዕረግ",
+ period: "ጊዜ",
+ actions: "ተግባራት",
+ },
+ fitness: {
+ FIT: "ብቁ",
+ FIT_WITH_RESTRICTIONS: "ከገደብ ጋር ብቁ",
+ UNFIT: "ብቁ ያልሆነ",
+ },
+ evidence: "ማስረጃ",
+ verify: "አረጋግጥ",
+ reject: "ውድቅ አድርግ",
+ evidenceTitleMedical: "ማስረጃ ለ{{name}} የሕክምና የምስክር ወረቀት",
+ evidenceTitleSeaService: "ማስረጃ ለ{{name}} የባህር አገልግሎት መዝገብ",
+ certificateVerified: "የምስክር ወረቀቱ ተረጋግጧል",
+ seaServiceVerified: "የባህር አገልግሎት መዝገብ ተረጋግጧል",
+ certificateRejected: "የምስክር ወረቀቱ ውድቅ ተደርጓል",
+ seaServiceRejected: "የባህር አገልግሎት መዝገብ ውድቅ ተደርጓል",
+ rulingFailed: "ውሳኔውን መመዝገብ አልተቻለም",
+ emptyText: "ማረጋገጫ የሚጠባበቅ ነገር የለም።",
+ rejectMedicalTitle: "የሕክምና የምስክር ወረቀት ውድቅ አድርግ",
+ rejectSeaServiceTitle: "የባህር አገልግሎት መዝገብ ውድቅ አድርግ",
+ rejectReasonLabel: "መርከበኛው ምን ማስተካከል አለበት?",
+ cancel: "ሰርዝ",
+ evidenceModalTitle: "የማስረጃ አባሪዎች",
+ noAttachments: "ለዚህ መዝገብ የተጫነ ማስረጃ አባሪ የለም።",
+ view: "ይመልከቱ",
+ noUrl: "URL የለም",
+ },
};
diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts
index be502f3b8..41c8b00f3 100644
--- a/apps/backoffice/src/app/i18n/locales/en.ts
+++ b/apps/backoffice/src/app/i18n/locales/en.ts
@@ -787,6 +787,23 @@ export const en = {
anyType: 'Any',
typeCol: 'Type',
statusCol: 'Status',
+ statusValues: {
+ DRAFT: 'Draft',
+ SUBMITTED: 'Submitted',
+ UNDER_REVIEW: 'Under Review',
+ UNDER_EVALUATION: 'Under Evaluation',
+ RESUBMIT_REQUIRED: 'Resubmit Required',
+ INSPECTION_PENDING: 'Inspection Pending',
+ INSPECTION_COMPLETED: 'Inspection Completed',
+ APPROVED: 'Approved',
+ REJECTED: 'Rejected',
+ ON_HOLD: 'On Hold',
+ PAYMENT_PENDING: 'Payment Pending',
+ PAID: 'Paid',
+ PAYMENT_CONFIRMED: 'Preparing Certificate',
+ CERTIFICATE_ISSUED: 'Certificate Issued',
+ COMPLETED: 'Completed',
+ },
submittedFrom: 'Submitted from',
submittedTo: 'Submitted to',
clearFilters: 'Clear',
@@ -1066,6 +1083,162 @@ export const en = {
noPermission: 'You do not have permission',
noPublishPermission: 'You cannot publish designs',
},
+
+ seafarerRegistry: {
+ title: 'Seafarer registry',
+ profileCount_one: '{{count}} profile',
+ profileCount_other: '{{count}} profiles',
+ searchPlaceholder: 'Name, ID or seafarer number',
+ emptySearch: 'No profiles match that search.',
+ emptyNone: 'No seafarers registered yet.',
+ columns: {
+ name: 'Name',
+ number: 'Seafarer №',
+ department: 'Department',
+ idNumber: 'ID number',
+ phone: 'Phone',
+ status: 'Status',
+ actions: 'Actions',
+ },
+ notRegistered: 'Not registered',
+ incomplete: 'Incomplete',
+ statusAction: 'Status',
+ statusActionTooltip: 'Suspend / reinstate / close',
+ status: {
+ ACTIVE: 'Active',
+ INACTIVE: 'Inactive',
+ PENDING: 'Pending',
+ SUSPENDED: 'Suspended',
+ },
+ departments: {
+ DECK: 'Deck',
+ ENGINE: 'Engine',
+ CATERING: 'Catering',
+ },
+ recordStatus: {
+ SUBMITTED: 'Submitted',
+ VERIFIED: 'Verified',
+ REJECTED: 'Rejected',
+ },
+ drawer: {
+ seafarerNumber: 'Seafarer number',
+ department: 'Department',
+ status: 'Status',
+ notRegistered: 'NOT REGISTERED',
+ statusReason: 'Status reason: {{reason}}',
+ seaServiceTab: 'Sea Service',
+ medicalTab: 'Medical',
+ noSeaService: 'No sea-service records.',
+ noMedical: 'No medical certificates.',
+ vessel: 'Vessel',
+ rank: 'Rank',
+ period: 'Period',
+ imoPrefix: 'IMO {{number}}',
+ issuer: 'Issuer',
+ validity: 'Validity',
+ fitness: 'Fitness',
+ },
+ modal: {
+ title: 'Change seafarer status',
+ body: '{{number}} — currently {{status}}. The reason is recorded and visible to the seafarer.',
+ newStatus: 'New status',
+ suspend: 'Suspend',
+ close: 'Close',
+ reinstate: 'Reinstate',
+ reason: 'Reason',
+ cancel: 'Cancel',
+ confirm: 'Confirm',
+ updated: 'Seafarer status updated',
+ updateFailed: 'Could not update the status',
+ },
+ },
+
+ paymentConfig: {
+ title: 'Payment configuration',
+ subtitle: 'What each licence costs. Applicants are charged after approval, and the amount is fixed onto the application at that moment.',
+ changeNotice: 'A change applies to applications approved from now on. Anything already approved keeps the amount it was quoted, so an edit here can never alter what an applicant has already been asked to pay.',
+ loadError: 'Could not load licence types',
+ columns: {
+ type: 'Licence type',
+ newApplication: 'New application',
+ renewal: 'Renewal',
+ charged: 'Charged?',
+ },
+ renewalSameTooltip: 'No separate renewal fee — renewal is charged at the new-application rate',
+ renewalSameAsNew: '(same as new)',
+ chargedOnApproval: 'On approval',
+ chargedNotChargedTooltip: 'This licence type ends with an EMA decision and never reaches a payment stage',
+ chargedNotCharged: 'Not charged',
+ edit: 'Edit',
+ gateway: {
+ title: 'Payment gateway',
+ subtitle: 'Set by the payment service environment — not editable here.',
+ provider: 'Telebirr',
+ bypassTooltip: 'ALLOW_PAYMENT_BYPASS is on, so an applicant can settle a fee without paying. It is refused in production.',
+ bypassEnabled: 'Test bypass enabled',
+ bypassOff: 'Test bypass off',
+ },
+ modal: {
+ title: 'Fees — {{type}}',
+ noPaymentStage: 'This licence type concludes with an EMA decision and never reaches a payment stage, so a fee set here stays unused until that changes.',
+ chargeableLabel: 'This licence carries a fee',
+ chargeableDescription: 'Turn off for licence types applicants are never charged for.',
+ newFeeLabel: 'New application fee',
+ sameRateLabel: 'Charge renewal at the same rate',
+ sameRateDescription: 'Turn off to set a separate renewal fee.',
+ renewalFeeLabel: 'Renewal fee',
+ currencyLabel: 'Currency',
+ cancel: 'Cancel',
+ save: 'Save fees',
+ missingNewFee: 'Enter a new-application fee, or turn off "carries a fee".',
+ missingRenewalFee: 'Enter a renewal fee, or charge renewal at the same rate.',
+ updated: '{{type}} fees updated.',
+ saveFailed: 'Could not save the fees',
+ },
+ },
+
+ recordVerification: {
+ title: 'Record verification',
+ subtitle: 'Submitted sea-service records and medical certificates awaiting a ruling. Verified records are frozen; rejections return to the seafarer with your remark.',
+ tabs: {
+ medical: 'Medical ({{count}})',
+ seaService: 'Sea Service ({{count}})',
+ },
+ columns: {
+ seafarer: 'Seafarer',
+ issuer: 'Issuer',
+ validity: 'Validity',
+ fitness: 'Fitness',
+ vessel: 'Vessel',
+ rank: 'Rank',
+ period: 'Period',
+ actions: 'Actions',
+ },
+ fitness: {
+ FIT: 'Fit',
+ FIT_WITH_RESTRICTIONS: 'Fit with restrictions',
+ UNFIT: 'Unfit',
+ },
+ evidence: 'Evidence',
+ verify: 'Verify',
+ reject: 'Reject',
+ evidenceTitleMedical: 'Evidence for {{name}} Medical Certificate',
+ evidenceTitleSeaService: 'Evidence for {{name}} Sea Service Record',
+ certificateVerified: 'Certificate verified',
+ seaServiceVerified: 'Sea-service record verified',
+ certificateRejected: 'Certificate rejected',
+ seaServiceRejected: 'Sea-service record rejected',
+ rulingFailed: 'Could not record the ruling',
+ emptyText: 'Nothing awaiting verification.',
+ rejectMedicalTitle: 'Reject medical certificate',
+ rejectSeaServiceTitle: 'Reject sea-service record',
+ rejectReasonLabel: 'What must the seafarer fix?',
+ cancel: 'Cancel',
+ evidenceModalTitle: 'Evidence Attachments',
+ noAttachments: 'No evidence attachments uploaded for this record.',
+ view: 'View',
+ noUrl: 'No URL',
+ },
};
export type Translations = typeof en;