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:
1 parent
4ba6bbc0c3
commit
b7719397e6
11 files changed
+176
-47
No files matched your search
@@ -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")}
|
||||
/>
|
||||
|
||||
@@ -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}'s posts and profile will be hidden from you.
|
||||
They won't know you blocked them.
|
||||
{t("description", { petName: blockedPetName })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Don'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>
|
||||
|
||||
@@ -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't report
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user