feature: localization's first implmentation done. all frontend hardcoded text is now localized.

This commit is contained in:
2026-06-20 14:51:43 +03:30
parent 284fbd08aa
commit b2f4dfa4ca
52 changed files with 3306 additions and 1041 deletions

View File

@@ -1,6 +1,7 @@
'use client';
import { useRef, useState } from 'react';
import { useTranslations } from 'next-intl';
import { Loader2, Paperclip, Send } from 'lucide-react';
import { DialogCloseButton } from '@/components/ui/shared/DialogCloseButton';
import { Button } from '@/components/ui/shared/Button';
@@ -32,6 +33,14 @@ function caseKey(c: PastTreatmentCase): string {
const caseActionIconClass =
'inline-flex items-center justify-center rounded-[var(--radius-sm)] p-1.5 text-text-secondary transition-colors hover:bg-background-card/80 hover:text-text-primary disabled:cursor-not-allowed disabled:opacity-40';
const TREATMENT_TYPE_KEYS = {
consultation: 'typeConsultation',
filling: 'typeFilling',
endo: 'typeEndo',
visit: 'typeVisit',
hygiene: 'typeHygiene',
} as const;
export function TreatmentPreviewDialog({
open,
onClose,
@@ -45,6 +54,7 @@ export function TreatmentPreviewDialog({
getCaseOrgIds,
onToggleCaseOrg,
}: TreatmentPreviewDialogProps) {
const t = useTranslations('treatment');
const [expandedSendCaseId, setExpandedSendCaseId] = useState<string | null>(null);
const fileInputsRef = useRef<Record<string, HTMLInputElement | null>>({});
@@ -64,10 +74,10 @@ export function TreatmentPreviewDialog({
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h2 id="treatment-preview-title" className="text-lg font-semibold text-text-primary pr-2">
Treatment preview
{t('previewDialogTitle')}
</h2>
<p className="text-xs text-text-muted mt-0.5">
Review cases, attachments, and send destinations.
{t('previewDialogSubtitle')}
</p>
</div>
<DialogCloseButton onClick={onClose} />
@@ -80,10 +90,12 @@ export function TreatmentPreviewDialog({
{new Date(treatment.treatmentAt).toLocaleDateString()}
</span>
</div>
<p className="text-xs text-text-secondary capitalize">Status: {treatment.status}</p>
<p className="text-xs text-text-secondary capitalize">
{t('statusLabel')} {treatment.status}
</p>
{treatment.cases.length === 0 ? (
<p className="text-sm text-text-muted">No cases in this treatment.</p>
<p className="text-sm text-text-muted">{t('noCases')}</p>
) : (
<div className="space-y-2">
{treatment.cases.map((c, idx) => {
@@ -98,6 +110,8 @@ export function TreatmentPreviewDialog({
const comment = c.notes?.trim() ?? '';
const attachBusy = uploadBusyCaseId === key;
const sendBusy = sendBusyCaseId === key;
const typeKey = TREATMENT_TYPE_KEYS[c.treatmentType as keyof typeof TREATMENT_TYPE_KEYS];
const typeLabel = typeKey ? t(typeKey) : c.treatmentType;
return (
<div
@@ -107,7 +121,9 @@ export function TreatmentPreviewDialog({
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-x-4 gap-y-1">
<div className="min-w-0 space-y-0.5">
<div className="flex items-center gap-2">
<p className="text-xs font-medium text-text-primary">Case {idx + 1}</p>
<p className="text-xs font-medium text-text-primary">
{t('caseLabel', { n: idx + 1 })}
</p>
{actionsEnabled && (
<div className="flex items-center gap-0.5">
<input
@@ -129,8 +145,8 @@ export function TreatmentPreviewDialog({
type="button"
className={caseActionIconClass}
disabled={attachBusy}
aria-label="Attach files"
title="Attach files"
aria-label={t('attachFilesShort')}
title={t('attachFilesShort')}
onClick={() => fileInputsRef.current[key]?.click()}
>
{attachBusy ? (
@@ -145,8 +161,8 @@ export function TreatmentPreviewDialog({
sendExpanded ? 'bg-primary-soft text-primary' : ''
}`}
disabled={sendBusy}
aria-label="Send this case"
title="Send this case"
aria-label={t('sendCase')}
title={t('sendCase')}
aria-expanded={sendExpanded}
onClick={() =>
setExpandedSendCaseId((prev) => (prev === key ? null : key))
@@ -162,18 +178,18 @@ export function TreatmentPreviewDialog({
)}
</div>
<p className="text-[11px] text-text-secondary capitalize">
Type: {c.treatmentType}
{t('typeLabel')} {typeLabel}
</p>
<p className="text-[11px] text-text-secondary">
Teeth:{' '}
{c.teeth.length ? [...c.teeth].sort().join(', ') : 'None selected'}
{t('teethLabel')}{' '}
{c.teeth.length ? [...c.teeth].sort().join(', ') : t('teethNone')}
</p>
{comment ? (
<p className="text-[11px] text-text-muted line-clamp-2" title={comment}>
Comments: {comment}
{t('commentsLabel')} {comment}
</p>
) : (
<p className="text-[11px] text-text-muted">Comments: </p>
<p className="text-[11px] text-text-muted">{t('commentsEmpty')}</p>
)}
</div>
@@ -186,7 +202,7 @@ export function TreatmentPreviewDialog({
/>
)}
<p className="text-[10px] uppercase tracking-wide text-text-muted">
Attachments
{t('attachments')}
</p>
<TreatmentLatestAttachmentPreview attachment={latestAttachment} />
</div>
@@ -195,10 +211,10 @@ export function TreatmentPreviewDialog({
{sendExpanded && editable && !sent && (
<div className="mt-2 space-y-2 border-t border-border/40 pt-2">
<p className="text-xs font-medium text-text-secondary">
Send to linked organizations
{t('sendToLinkedOrgs')}
</p>
{activeOrgs.length === 0 ? (
<p className="text-xs text-text-muted">No active linked organizations.</p>
<p className="text-xs text-text-muted">{t('noActiveOrgs')}</p>
) : (
<div className="flex flex-col gap-1.5">
{activeOrgs.map((o) => (
@@ -219,7 +235,7 @@ export function TreatmentPreviewDialog({
isLoading={sendBusy}
onClick={() => void onSend?.(key, selectedOrgIds)}
>
Confirm send
{t('confirmSend')}
</Button>
</div>
)}