fix(i18n): translate milestone type labels

MILESTONE_META in src/lib/milestone-meta.ts held hardcoded English
labels ("Birthday", "Vet Visit", ...) consumed directly by
MilestoneCard and MilestoneForm -- bypassing next-intl entirely, so no
de.json entry could ever have translated them (there was no key to
translate, not a missing one).

Adds a MilestoneTypes namespace to both message catalogs and switches
the two user-facing consumers to useTranslations("MilestoneTypes")
for the label, keeping MILESTONE_META only for the icon lookup. The
admin posts page (p/[secret]/posts) still reads MILESTONE_META.label
directly and is left as-is -- the admin panel is English-only by
existing project convention, not part of the localized surface.
This commit is contained in:
admin committed 2026-08-21 22:33:03 +02:00
1 parent c1951b29b9
commit fa3f5faa58
4 files changed
+46 -6

No files matched your search

+18
View File
@@ -488,6 +488,24 @@
"invalidType": "Lade ein JPG-, PNG- oder WebP-Bild hoch.",
"uploadFailed": "Upload fehlgeschlagen — bitte versuch es erneut."
},
"MilestoneTypes": {
"BIRTHDAY": "Geburtstag",
"ADOPTION_DAY": "Adoptionstag",
"FIRST_OUTING": "Erster Ausflug",
"VET_VISIT": "Tierarztbesuch",
"LITTER": "Neuer Wurf",
"FOLLOWERS_500": "500 Follower",
"FOLLOWERS_1K": "1.000 Follower",
"FOLLOWERS_2_5K": "2.500 Follower",
"FOLLOWERS_5K": "5.000 Follower",
"FOLLOWERS_10K": "10.000 Follower",
"FOLLOWERS_25K": "25.000 Follower",
"FOLLOWERS_50K": "50.000 Follower",
"FOLLOWERS_100K": "100.000 Follower",
"FOLLOWERS_250K": "250.000 Follower",
"FOLLOWERS_500K": "500.000 Follower",
"FOLLOWERS_1M": "1.000.000 Follower"
},
"VideoUploadForm": {
"selectVideo": "Video auswählen",
"formatHint": "MP4, MOV oder WebM · bis 500 MB · max. 3 Minuten",
+18
View File
@@ -488,6 +488,24 @@
"invalidType": "Upload a JPG, PNG, or WebP image.",
"uploadFailed": "Upload failed — please try again."
},
"MilestoneTypes": {
"BIRTHDAY": "Birthday",
"ADOPTION_DAY": "Adoption Day",
"FIRST_OUTING": "First Outing",
"VET_VISIT": "Vet Visit",
"LITTER": "New Litter",
"FOLLOWERS_500": "500 Followers",
"FOLLOWERS_1K": "1,000 Followers",
"FOLLOWERS_2_5K": "2,500 Followers",
"FOLLOWERS_5K": "5,000 Followers",
"FOLLOWERS_10K": "10,000 Followers",
"FOLLOWERS_25K": "25,000 Followers",
"FOLLOWERS_50K": "50,000 Followers",
"FOLLOWERS_100K": "100,000 Followers",
"FOLLOWERS_250K": "250,000 Followers",
"FOLLOWERS_500K": "500,000 Followers",
"FOLLOWERS_1M": "1,000,000 Followers"
},
"VideoUploadForm": {
"selectVideo": "Select a video",
"formatHint": "MP4, MOV or WebM · up to 500 MB · max 3 minutes",
+6 -3
View File
@@ -1,6 +1,7 @@
"use client";
import type { MilestoneType } from "@prisma/client";
import { useTranslations } from "next-intl";
import { MILESTONE_META } from "@/lib/milestone-meta";
import { getAvatarUrl } from "@/lib/avatar-url";
import { formatPetName } from "@/lib/pet-display-name";
@@ -40,12 +41,12 @@ function formatDate(date: Date | string): string {
* Background: bg-orange-50 border border-orange-200 per UI-SPEC accent reservation #8.
*/
export function MilestoneCard({ post }: MilestoneCardProps) {
const t = useTranslations("MilestoneTypes");
const { pet, milestone, images, caption } = post;
if (!milestone) return null;
const meta = MILESTONE_META[milestone.milestoneType];
const Icon = meta.icon;
const Icon = MILESTONE_META[milestone.milestoneType].icon;
const avatarUrl = getAvatarUrl(pet.avatarKey);
const photo = images[0];
@@ -70,7 +71,9 @@ export function MilestoneCard({ post }: MilestoneCardProps) {
{/* Milestone type badge */}
<div className="flex items-center gap-3 px-4 pb-3">
<Icon className="h-8 w-8 text-orange-500 shrink-0" aria-hidden="true" />
<span className="text-base font-semibold text-orange-500">{meta.label}</span>
<span className="text-base font-semibold text-orange-500">
{t(milestone.milestoneType)}
</span>
</div>
{/* Optional photo */}
@@ -40,6 +40,7 @@ type UploadState = "idle" | "uploading" | "ready";
*/
export function MilestoneForm({ onClose }: MilestoneFormProps) {
const t = useTranslations("MilestoneForm");
const tType = useTranslations("MilestoneTypes");
const trpc = useTRPC();
const router = useRouter();
const queryClient = useQueryClient();
@@ -166,10 +167,10 @@ export function MilestoneForm({ onClose }: MilestoneFormProps) {
</label>
<Popover open={pickerOpen} onOpenChange={setPickerOpen}>
<PopoverTrigger className="flex items-center justify-between w-full min-h-[44px] px-3 py-2 rounded-md border border-input bg-background text-sm hover:bg-accent hover:text-accent-foreground transition-colors">
{selectedMeta ? (
{selectedType && selectedMeta ? (
<span className="flex items-center gap-2 text-foreground">
<selectedMeta.icon className="h-4 w-4 text-orange-500 shrink-0" aria-hidden="true" />
{selectedMeta.label}
{tType(selectedType)}
</span>
) : (
<span className="text-muted-foreground">{t("selectPlaceholder")}</span>
@@ -191,7 +192,7 @@ export function MilestoneForm({ onClose }: MilestoneFormProps) {
}}
>
<Icon className="h-5 w-5 text-orange-500 shrink-0" aria-hidden="true" />
{meta.label}
{tType(type)}
</button>
);
})}