feat(i18n): translate pet-profile area (Edit Profile, Health, stats, dropdowns, ReportSheet, BlockDialog)

The entire /pets area (My Pets grid, pet profile page, followers/following
lists, the profile options dropdown, ReportSheet, BlockDialog) had zero
i18n despite ProfileTabs right next to it being fully translated — user
reported still seeing English after switching the app to German.

Adds 7 new message namespaces (PetsPage, PetProfile, FollowersPage,
FollowingPage, ProfileActions, ReportSheet, BlockDialog) with full EN/DE
parity, plus bio/adoptionStory placeholder keys to the existing PetForm
namespace (two placeholders were hardcoded English template literals
despite the rest of that form already being translated).

Server Components (pets/page.tsx, pets/[petId]/page.tsx,
followers/following pages) use getTranslations from next-intl/server
instead of the useTranslations client hook.
This commit is contained in:
admin committed 2026-07-23 15:02:37 +02:00
1 parent 4ba6bbc0c3
commit b7719397e6
11 files changed
+176 -47

No files matched your search

+2 -2
View File
@@ -312,7 +312,7 @@ export function PetForm({
<Textarea
id="bio"
maxLength={150}
placeholder={`Tell us about ${petName || "your pet"}…`}
placeholder={petName ? t("bioPlaceholder", { petName }) : t("bioPlaceholderFallback")}
aria-invalid={!!errors.bio}
{...register("bio")}
/>
@@ -330,7 +330,7 @@ export function PetForm({
<Textarea
id="adoption-story"
maxLength={500}
placeholder={`How did ${petName || "your pet"} join your family?`}
placeholder={petName ? t("adoptionStoryPlaceholder", { petName }) : t("adoptionStoryPlaceholderFallback")}
aria-invalid={!!errors.adoptionStory}
{...register("adoptionStory")}
/>
+8 -7
View File
@@ -1,6 +1,7 @@
"use client";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
import {
AlertDialog,
@@ -43,19 +44,20 @@ export function BlockDialog({
blockedPetId,
blockedPetName,
}: BlockDialogProps) {
const t = useTranslations("BlockDialog");
const trpc = useTRPC();
const queryClient = useQueryClient();
const blockMutation = useMutation(
trpc.blocks.create.mutationOptions({
onSuccess: () => {
toast.success(`You've blocked ${blockedPetName}`);
toast.success(t("successToast", { petName: blockedPetName }));
onOpenChange(false);
// Invalidate any cached block lists so feed hydration picks up the new block
queryClient.invalidateQueries({ queryKey: ["follows"] });
},
onError: () => {
toast.error("Couldn't block this pet. Please try again.");
toast.error(t("errorToast"));
},
})
);
@@ -68,20 +70,19 @@ export function BlockDialog({
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Block {blockedPetName}?</AlertDialogTitle>
<AlertDialogTitle>{t("title", { petName: blockedPetName })}</AlertDialogTitle>
<AlertDialogDescription>
{blockedPetName}&apos;s posts and profile will be hidden from you.
They won&apos;t know you blocked them.
{t("description", { petName: blockedPetName })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t block</AlertDialogCancel>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={handleBlock}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
disabled={blockMutation.isPending}
>
Block
{t("confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
+18 -16
View File
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
import { ReportReason } from "@prisma/client";
import {
@@ -41,14 +42,6 @@ interface ReportSheetProps {
targetPetId?: string;
}
const REPORT_REASONS: { value: ReportReason; label: string }[] = [
{ value: ReportReason.SPAM, label: "Spam" },
{ value: ReportReason.HARASSMENT, label: "Harassment or bullying" },
{ value: ReportReason.INAPPROPRIATE, label: "Inappropriate content" },
{ value: ReportReason.MISINFORMATION, label: "False or misleading information" },
{ value: ReportReason.OTHER, label: "Something else" },
];
export function ReportSheet({
open,
onOpenChange,
@@ -56,21 +49,30 @@ export function ReportSheet({
targetPostId,
targetPetId,
}: ReportSheetProps) {
const t = useTranslations("ReportSheet");
const trpc = useTRPC();
const [selectedReason, setSelectedReason] = useState<ReportReason | "">("");
const [notes, setNotes] = useState("");
const REPORT_REASONS: { value: ReportReason; label: string }[] = [
{ value: ReportReason.SPAM, label: t("reasonSpam") },
{ value: ReportReason.HARASSMENT, label: t("reasonHarassment") },
{ value: ReportReason.INAPPROPRIATE, label: t("reasonInappropriate") },
{ value: ReportReason.MISINFORMATION, label: t("reasonMisinformation") },
{ value: ReportReason.OTHER, label: t("reasonOther") },
];
const reportMutation = useMutation(
trpc.reports.create.mutationOptions({
onSuccess: () => {
toast.success("Report submitted. We'll review it shortly.");
toast.success(t("successToast"));
onOpenChange(false);
// Reset form state
setSelectedReason("");
setNotes("");
},
onError: () => {
toast.error("Couldn't submit your report. Please try again.");
toast.error(t("errorToast"));
},
})
);
@@ -97,13 +99,13 @@ export function ReportSheet({
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="bottom" className="sm:side-right sm:max-w-[400px]">
<SheetHeader>
<SheetTitle className="text-xl font-semibold">Report</SheetTitle>
<SheetTitle className="text-xl font-semibold">{t("title")}</SheetTitle>
</SheetHeader>
<div className="mt-4 space-y-4">
<div>
<p className="text-sm font-medium mb-3">
Why are you reporting this?
{t("reasonQuestion")}
</p>
<RadioGroup
value={selectedReason}
@@ -126,7 +128,7 @@ export function ReportSheet({
<div>
<Textarea
placeholder="Add details (optional)"
placeholder={t("notesPlaceholder")}
value={notes}
onChange={(e) => setNotes(e.target.value)}
maxLength={500}
@@ -135,7 +137,7 @@ export function ReportSheet({
/>
{notes.length > 450 && (
<p className="text-xs text-muted-foreground mt-1 text-right">
{500 - notes.length} characters remaining
{t("charactersRemaining", { count: 500 - notes.length })}
</p>
)}
</div>
@@ -146,14 +148,14 @@ export function ReportSheet({
disabled={!selectedReason || reportMutation.isPending}
className="w-full min-h-[44px] bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Submit Report
{t("submit")}
</Button>
<button
type="button"
onClick={handleCancel}
className="w-full text-sm text-muted-foreground hover:text-foreground py-2"
>
Don&apos;t report
{t("cancel")}
</button>
</div>
</div>