fix(web): add proper Vietnamese diacritics to all dashboard and listing pages

Vietnamese text throughout the frontend was missing accent marks (diacritics),
using plain ASCII instead of proper Unicode characters. Fixed all user-visible
text across dashboard, analytics, listings, search, and chart components.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 13:21:37 +07:00
parent 47c34f129e
commit 36c1e3b39a
7 changed files with 158 additions and 158 deletions

View File

@@ -17,7 +17,7 @@ const ListingMap = dynamic(
ssr: false,
loading: () => (
<div className="flex h-[300px] items-center justify-center rounded-lg bg-muted">
<p className="text-sm text-muted-foreground">Dang tai ban do...</p>
<p className="text-sm text-muted-foreground">Đang ti bn đ...</p>
</div>
),
},
@@ -45,7 +45,7 @@ export default function PublicListingDetailPage() {
listingsApi
.getById(id)
.then(setListing)
.catch((err) => setError(err instanceof Error ? err.message : 'Khong tai duoc tin dang'))
.catch((err) => setError(err instanceof Error ? err.message : 'Không ti được tin đăng'))
.finally(() => setLoading(false));
}, [id]);
@@ -74,9 +74,9 @@ export default function PublicListingDetailPage() {
<svg className="h-12 w-12 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<p className="text-destructive">{error || 'Khong tim thay tin dang'}</p>
<p className="text-destructive">{error || 'Không tìm thy tin đăng'}</p>
<Link href="/search">
<Button variant="outline">Quay lai tim kiem</Button>
<Button variant="outline">Quay li tìm kiếm</Button>
</Link>
</div>
);
@@ -90,9 +90,9 @@ export default function PublicListingDetailPage() {
<div className="mx-auto max-w-6xl px-4 py-6">
{/* Breadcrumb */}
<nav className="mb-4 flex items-center gap-1.5 text-sm text-muted-foreground">
<Link href="/" className="hover:text-foreground">Trang chu</Link>
<Link href="/" className="hover:text-foreground">Trang ch</Link>
<span>/</span>
<Link href="/search" className="hover:text-foreground">Tim kiem</Link>
<Link href="/search" className="hover:text-foreground">Tìm kiếm</Link>
<span>/</span>
<span className="truncate text-foreground">{property.title}</span>
</nav>
@@ -121,12 +121,12 @@ export default function PublicListingDetailPage() {
<p className="text-2xl font-bold text-primary md:text-3xl">{formatPrice(listing.priceVND)} VND</p>
{listing.pricePerM2 != null && (
<p className="text-sm text-muted-foreground">
~{listing.pricePerM2.toLocaleString('vi-VN')} VND/m2
~{listing.pricePerM2.toLocaleString('vi-VN')} VND/m²
</p>
)}
{listing.rentPriceMonthly && (
<p className="text-sm text-muted-foreground">
Thue: {formatPrice(listing.rentPriceMonthly)}/thang
Thuê: {formatPrice(listing.rentPriceMonthly)}/tháng
</p>
)}
</div>
@@ -137,18 +137,18 @@ export default function PublicListingDetailPage() {
{/* Quick specs bar */}
<div className="my-6 flex flex-wrap gap-4 rounded-lg border bg-card p-4">
<QuickStat icon="area" label="Dien tich" value={`${property.areaM2} m\u00B2`} />
<QuickStat icon="area" label="Din tích" value={`${property.areaM2} m\u00B2`} />
{property.bedrooms != null && (
<QuickStat icon="bed" label="Phong ngu" value={`${property.bedrooms}`} />
<QuickStat icon="bed" label="Phòng ng" value={`${property.bedrooms}`} />
)}
{property.bathrooms != null && (
<QuickStat icon="bath" label="Phong tam" value={`${property.bathrooms}`} />
<QuickStat icon="bath" label="Phòng tm" value={`${property.bathrooms}`} />
)}
{property.floors != null && (
<QuickStat icon="floors" label="So tang" value={`${property.floors}`} />
<QuickStat icon="floors" label="S tng" value={`${property.floors}`} />
)}
{property.direction && (
<QuickStat icon="compass" label="Huong" value={getLabel(DIRECTIONS, property.direction) || ''} />
<QuickStat icon="compass" label="Hướng" value={getLabel(DIRECTIONS, property.direction) || ''} />
)}
</div>
@@ -158,7 +158,7 @@ export default function PublicListingDetailPage() {
{/* Description */}
<Card>
<CardHeader>
<CardTitle>Mo ta</CardTitle>
<CardTitle>Mô t</CardTitle>
</CardHeader>
<CardContent>
<p className="whitespace-pre-wrap text-sm leading-relaxed">{property.description}</p>
@@ -168,19 +168,19 @@ export default function PublicListingDetailPage() {
{/* Details */}
<Card>
<CardHeader>
<CardTitle>Thong tin chi tiet</CardTitle>
<CardTitle>Thông tin chi tiết</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
<InfoItem label="Loai BDS" value={propertyTypeLabel || '---'} />
<InfoItem label="Dien tich" value={`${property.areaM2} m\u00B2`} />
<InfoItem label="Phong ngu" value={property.bedrooms != null ? `${property.bedrooms}` : '---'} />
<InfoItem label="Phong tam" value={property.bathrooms != null ? `${property.bathrooms}` : '---'} />
<InfoItem label="So tang" value={property.floors != null ? `${property.floors}` : '---'} />
<InfoItem label="Huong" value={getLabel(DIRECTIONS, property.direction) || '---'} />
<InfoItem label="Nam xay" value={property.yearBuilt ? `${property.yearBuilt}` : '---'} />
<InfoItem label="Phap ly" value={property.legalStatus || '---'} />
<InfoItem label="Du an" value={property.projectName || '---'} />
<InfoItem label="Loi BĐS" value={propertyTypeLabel || '---'} />
<InfoItem label="Din tích" value={`${property.areaM2} m\u00B2`} />
<InfoItem label="Phòng ng" value={property.bedrooms != null ? `${property.bedrooms}` : '---'} />
<InfoItem label="Phòng tm" value={property.bathrooms != null ? `${property.bathrooms}` : '---'} />
<InfoItem label="S tng" value={property.floors != null ? `${property.floors}` : '---'} />
<InfoItem label="Hướng" value={getLabel(DIRECTIONS, property.direction) || '---'} />
<InfoItem label="Năm xây" value={property.yearBuilt ? `${property.yearBuilt}` : '---'} />
<InfoItem label="Pháp lý" value={property.legalStatus || '---'} />
<InfoItem label="Dự án" value={property.projectName || '---'} />
</div>
</CardContent>
</Card>
@@ -189,7 +189,7 @@ export default function PublicListingDetailPage() {
{property.amenities && property.amenities.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Tien ich</CardTitle>
<CardTitle>Tin ích</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
@@ -206,7 +206,7 @@ export default function PublicListingDetailPage() {
{/* Map */}
<Card>
<CardHeader>
<CardTitle>Vi tri tren ban do</CardTitle>
<CardTitle>V trí trên bn đ</CardTitle>
</CardHeader>
<CardContent>
<ListingMap
@@ -222,7 +222,7 @@ export default function PublicListingDetailPage() {
{/* Contact card */}
<Card className="sticky top-20">
<CardHeader>
<CardTitle>Lien he</CardTitle>
<CardTitle>Liên h</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-3">
@@ -242,22 +242,22 @@ export default function PublicListingDetailPage() {
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
Goi ngay
Gi ngay
</Button>
</a>
<Button variant="outline" className="w-full gap-2">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
Nhan tin
Nhn tin
</Button>
{agent && (
<div className="border-t pt-3">
<p className="text-xs text-muted-foreground">Moi gioi</p>
<p className="text-xs text-muted-foreground">Môi gii</p>
{agent.agency && <p className="text-sm font-medium">{agent.agency}</p>}
{listing.commissionPct != null && (
<p className="text-xs text-muted-foreground">Hoa hong: {listing.commissionPct}%</p>
<p className="text-xs text-muted-foreground">Hoa hng: {listing.commissionPct}%</p>
)}
</div>
)}
@@ -270,20 +270,20 @@ export default function PublicListingDetailPage() {
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<p className="text-lg font-bold">{listing.viewCount}</p>
<p className="text-xs text-muted-foreground">Luot xem</p>
<p className="text-xs text-muted-foreground">Lượt xem</p>
</div>
<div>
<p className="text-lg font-bold">{listing.saveCount}</p>
<p className="text-xs text-muted-foreground">Luot luu</p>
<p className="text-xs text-muted-foreground">Lượt lưu</p>
</div>
<div>
<p className="text-lg font-bold">{listing.inquiryCount}</p>
<p className="text-xs text-muted-foreground">Lien he</p>
<p className="text-xs text-muted-foreground">Liên h</p>
</div>
</div>
{listing.publishedAt && (
<p className="mt-3 border-t pt-3 text-center text-xs text-muted-foreground">
Dang ngay {new Date(listing.publishedAt).toLocaleDateString('vi-VN')}
Đăng ngày {new Date(listing.publishedAt).toLocaleDateString('vi-VN')}
</p>
)}
</CardContent>