diff --git a/frontend/src/app/[lang]/dashboard/apps/page.tsx b/frontend/src/app/[lang]/dashboard/apps/page.tsx index b45ce89..5422d71 100644 --- a/frontend/src/app/[lang]/dashboard/apps/page.tsx +++ b/frontend/src/app/[lang]/dashboard/apps/page.tsx @@ -1,7 +1,9 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; -import Link from 'next/link'; +import { Link } from '@/i18n/Link'; +import { useT } from '@/i18n/I18nProvider'; +import type { Dictionary } from '@/i18n/dictionaries/fa'; import api from '@/lib/api'; import type { Application } from '@/types'; import { Rocket, Package, Hexagon, Database, Box, AlertTriangle, Clock } from 'lucide-react'; @@ -33,28 +35,37 @@ const lifecycleColors: Record = { deleted: 'text-gray-500 bg-gray-100', }; -const lifecycleLabels: Record = { - active: 'Active', - suspended: 'Suspended — Unpaid', - pending_deletion: 'Pending Deletion', - deleted: 'Deleted', -}; +type AppsDict = Dictionary['dashboard']['apps']; -function formatExpiry(expiresAt?: string): { text: string; urgent: boolean } { +function statusLabel(status: string, t: Dictionary): string { + return (t.dashboard.status as Record)[status] ?? status; +} + +function lifecycleLabel(lifecycle: string, a: AppsDict): string { + return (a.lifecycle as Record)[lifecycle] ?? lifecycle; +} + +function formatExpiry(expiresAt: string | undefined, a: AppsDict): { text: string; urgent: boolean } { if (!expiresAt) return { text: '—', urgent: false }; const now = new Date(); const exp = new Date(expiresAt); const diff = exp.getTime() - now.getTime(); - if (diff <= 0) return { text: 'Expired', urgent: true }; + if (diff <= 0) return { text: a.expired, urgent: true }; const hours = Math.floor(diff / 3600000); const days = Math.floor(hours / 24); - if (days > 0) return { text: `${days}d ${hours % 24}h remaining`, urgent: days < 3 }; - if (hours > 0) return { text: `${hours}h remaining`, urgent: hours < 6 }; + if (days > 0) + return { + text: a.daysHours.replace('{d}', String(days)).replace('{h}', String(hours % 24)), + urgent: days < 3, + }; + if (hours > 0) return { text: a.hours.replace('{h}', String(hours)), urgent: hours < 6 }; const mins = Math.floor(diff / 60000); - return { text: `${mins}m remaining`, urgent: true }; + return { text: a.mins.replace('{m}', String(mins)), urgent: true }; } export default function AppsPage() { + const t = useT(); + const a = t.dashboard.apps; const confirm = useConfirm(); const { data: appsRaw = [], isLoading } = useQuery({ @@ -65,8 +76,8 @@ export default function AppsPage() { const { deleteApplication, isDeleting, isAnyDeleting } = useApplicationDelete({ invalidateKeys: [['applications', 'application'], ['applications']], - successMessage: 'Application deleted', - successWithCreditMessage: 'Application deleted. Your prepaid resources are shown on the dashboard.', + successMessage: a.deleted, + successWithCreditMessage: a.deletedWithCredit, }); if (isLoading) { @@ -96,21 +107,21 @@ export default function AppsPage() {
-

My Applications

-

{apps.length} application{apps.length !== 1 ? 's' : ''}

+

{a.title}

+

{a.count.replace('{n}', String(apps.length))}

- New Application + {a.newApplication}
{apps.length === 0 ? (
-

No applications yet

-

Deploy your first application to get started.

+

{a.noApplications}

+

{a.emptyDesc}

- Deploy your first app + {a.deployFirst}
) : ( @@ -119,19 +130,19 @@ export default function AppsPage() { - - - - - - + + + + + + {apps.map((app) => { const latestStatus = app.deployments?.[0]?.status || 'pending'; const lifecycle = app.lifecycleStatus || 'active'; - const expiry = formatExpiry(app.planExpiresAt); + const expiry = formatExpiry(app.planExpiresAt, a); const rowDeleting = isDeleting(app.id); return ( -
ApplicationRuntimeStatusService StatusExpiryActions{a.colApplication}{a.colRuntime}{a.colStatus}{a.colServiceStatus}{a.colExpiry}{a.colActions}
{app.runtime} - {latestStatus} + {statusLabel(latestStatus, t)} {lifecycle === 'suspended' && } - {lifecycleLabels[lifecycle] || lifecycle} + {lifecycleLabel(lifecycle, a)} @@ -163,22 +174,22 @@ export default function AppsPage() { {expiry.text} ) : ( - No plan + {a.noPlan} )} +
- View + {a.view}
- {latestStatus} + {statusLabel(latestStatus, t)}
{lifecycle === 'suspended' && } - {lifecycleLabels[lifecycle] || lifecycle} + {lifecycleLabel(lifecycle, a)} {app.planExpiresAt && ( @@ -242,7 +253,7 @@ export default function AppsPage() { {app.databaseType} - {app.replicas} replica{app.replicas > 1 ? 's' : ''} + {app.replicas} {app.replicas > 1 ? t.dashboard.home.replicaPlural : t.dashboard.home.replica}
diff --git a/frontend/src/app/[lang]/dashboard/services/page.tsx b/frontend/src/app/[lang]/dashboard/services/page.tsx index 8863135..c3b4973 100644 --- a/frontend/src/app/[lang]/dashboard/services/page.tsx +++ b/frontend/src/app/[lang]/dashboard/services/page.tsx @@ -1,7 +1,9 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; -import Link from 'next/link'; +import { Link } from '@/i18n/Link'; +import { useT } from '@/i18n/I18nProvider'; +import type { Dictionary } from '@/i18n/dictionaries/fa'; import api from '@/lib/api'; import type { Application } from '@/types'; import { managedServiceTypeLabel } from '@/lib/optional-service-defaults'; @@ -23,12 +25,15 @@ const lifecycleColors: Record = { deleted: 'text-gray-500 bg-gray-100', }; -const lifecycleLabels: Record = { - active: 'Active', - suspended: 'Suspended — Unpaid', - pending_deletion: 'Pending Deletion', - deleted: 'Deleted', -}; +type AppsDict = Dictionary['dashboard']['apps']; + +function statusLabel(status: string, t: Dictionary): string { + return (t.dashboard.status as Record)[status] ?? status; +} + +function lifecycleLabel(lifecycle: string, a: AppsDict): string { + return (a.lifecycle as Record)[lifecycle] ?? lifecycle; +} function serviceSubtitle(app: Application): string { if (app.productType === 'managed_database') { @@ -43,19 +48,26 @@ function serviceSubtitle(app: Application): string { return app.databaseType !== 'none' ? app.databaseType : '—'; } -function formatExpiry(expiresAt?: string): { text: string; urgent: boolean } { +function formatExpiry(expiresAt: string | undefined, a: AppsDict): { text: string; urgent: boolean } { if (!expiresAt) return { text: '—', urgent: false }; const diff = new Date(expiresAt).getTime() - Date.now(); - if (diff <= 0) return { text: 'Expired', urgent: true }; + if (diff <= 0) return { text: a.expired, urgent: true }; const hours = Math.floor(diff / 3600000); const days = Math.floor(hours / 24); - if (days > 0) return { text: `${days}d ${hours % 24}h remaining`, urgent: days < 3 }; - if (hours > 0) return { text: `${hours}h remaining`, urgent: hours < 6 }; + if (days > 0) + return { + text: a.daysHours.replace('{d}', String(days)).replace('{h}', String(hours % 24)), + urgent: days < 3, + }; + if (hours > 0) return { text: a.hours.replace('{h}', String(hours)), urgent: hours < 6 }; const mins = Math.floor(diff / 60000); - return { text: `${mins}m remaining`, urgent: true }; + return { text: a.mins.replace('{m}', String(mins)), urgent: true }; } export default function ServicesPage() { + const t = useT(); + const s = t.dashboard.services; + const appsDict = t.dashboard.apps; const confirm = useConfirm(); const { data: servicesRaw = [], isLoading } = useQuery({ @@ -66,8 +78,8 @@ export default function ServicesPage() { const { deleteApplication, isDeleting, isAnyDeleting } = useApplicationDelete({ invalidateKeys: [['applications', 'managed']], - successMessage: 'Service deleted', - successWithCreditMessage: 'Service deleted. Prepaid resources are on your dashboard.', + successMessage: s.deleted, + successWithCreditMessage: s.deletedWithCredit, }); if (isLoading) { @@ -96,32 +108,28 @@ export default function ServicesPage() {
-

Databases & Services

-

- Standalone databases, Redis, and RabbitMQ — {services.length} service{services.length !== 1 ? 's' : ''} -

+

{s.title}

+

{s.subtitle.replace('{n}', String(services.length))}

- New Service + {s.newService}
{services.length === 0 ? (
-

No managed services yet

-

- Provision a database, Redis, or RabbitMQ without deploying a full application. -

+

{s.noServices}

+

{s.emptyDesc}

- Create your first service + {s.createFirst}
) : (
{services.map((svc) => { const lifecycle = svc.lifecycleStatus || 'active'; - const expiry = formatExpiry(svc.planExpiresAt); + const expiry = formatExpiry(svc.planExpiresAt, appsDict); const latestStatus = svc.deployments?.[0]?.status || 'pending'; const cardDeleting = isDeleting(svc.id); return ( @@ -150,7 +158,7 @@ export default function ServicesPage() {
- {latestStatus} + {statusLabel(latestStatus, t)} {lifecycle === 'suspended' && } - {lifecycleLabels[lifecycle] || lifecycle} - - - {expiry.text} + {lifecycleLabel(lifecycle, appsDict)} + {svc.planExpiresAt && ( + + + {expiry.text} + + )}