diff --git a/messages/de.json b/messages/de.json
index 92405f8..9f76ae5 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -69,7 +69,11 @@
"selectSpeciesFirst": "Wähle zuerst eine Tierart aus",
"noBreedsFound": "Keine Rassen gefunden.",
"bio": "Bio",
+ "bioPlaceholder": "Erzähl uns von {petName}…",
+ "bioPlaceholderFallback": "Erzähl uns von deinem Tier…",
"adoptionStory": "Adoptionsgeschichte",
+ "adoptionStoryPlaceholder": "Wie kam {petName} zu deiner Familie?",
+ "adoptionStoryPlaceholderFallback": "Wie kam dein Tier zu deiner Familie?",
"birthday": "Geburtstag",
"birthdayHint": "Für Geburtstags-Erinnerungen",
"adoptedAt": "Adoptionsdatum",
@@ -425,5 +429,58 @@
},
"RepostCard": {
"pawedBackBy": "Paw-Back von"
+ },
+ "PetsPage": {
+ "title": "Meine Tiere",
+ "addPet": "Tier anlegen",
+ "emptyTitle": "Füge dein erstes Tier hinzu",
+ "emptyDesc": "Gib deinem Tier ein eigenes Profil auf PawFeed."
+ },
+ "PetProfile": {
+ "editProfile": "Profil bearbeiten",
+ "health": "Gesundheit",
+ "managedBy": "Verwaltet von @{username}",
+ "statsPosts": "Posts",
+ "statsFollowers": "Follower",
+ "statsFollowing": "Folgt",
+ "adoptionStory": "Adoptionsgeschichte"
+ },
+ "FollowersPage": {
+ "title": "{count} Follower",
+ "empty": "{petName} hat noch keine Follower."
+ },
+ "FollowingPage": {
+ "title": "Folgt {count}",
+ "empty": "{petName} folgt noch niemandem."
+ },
+ "ProfileActions": {
+ "message": "Nachricht",
+ "cannotStartConversation": "Unterhaltung konnte nicht gestartet werden.",
+ "moreOptions": "Weitere Optionen",
+ "reportProfile": "Profil melden",
+ "blockPet": "{petName} blockieren"
+ },
+ "ReportSheet": {
+ "title": "Melden",
+ "reasonQuestion": "Warum meldest du das?",
+ "reasonSpam": "Spam",
+ "reasonHarassment": "Belästigung oder Mobbing",
+ "reasonInappropriate": "Unangemessene Inhalte",
+ "reasonMisinformation": "Falsche oder irreführende Informationen",
+ "reasonOther": "Etwas anderes",
+ "notesPlaceholder": "Details hinzufügen (optional)",
+ "charactersRemaining": "Noch {count} Zeichen",
+ "submit": "Meldung absenden",
+ "cancel": "Nicht melden",
+ "successToast": "Meldung übermittelt. Wir prüfen sie in Kürze.",
+ "errorToast": "Deine Meldung konnte nicht übermittelt werden. Bitte versuch es erneut."
+ },
+ "BlockDialog": {
+ "title": "{petName} blockieren?",
+ "description": "{petName}s Posts und Profil werden für dich ausgeblendet. Niemand erfährt, dass du blockiert hast.",
+ "cancel": "Nicht blockieren",
+ "confirm": "Blockieren",
+ "successToast": "Du hast {petName} blockiert",
+ "errorToast": "Tier konnte nicht blockiert werden. Bitte versuch es erneut."
}
}
diff --git a/messages/en.json b/messages/en.json
index f9e86c0..6c0b5d9 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -69,7 +69,11 @@
"selectSpeciesFirst": "Select a species first",
"noBreedsFound": "No breeds found.",
"bio": "Bio",
+ "bioPlaceholder": "Tell us about {petName}…",
+ "bioPlaceholderFallback": "Tell us about your pet…",
"adoptionStory": "Adoption story",
+ "adoptionStoryPlaceholder": "How did {petName} join your family?",
+ "adoptionStoryPlaceholderFallback": "How did your pet join your family?",
"birthday": "Birthday",
"birthdayHint": "For birthday reminders",
"adoptedAt": "Adoption date",
@@ -425,5 +429,58 @@
},
"RepostCard": {
"pawedBackBy": "Pawed-back by"
+ },
+ "PetsPage": {
+ "title": "My Pets",
+ "addPet": "Add Pet",
+ "emptyTitle": "Add your first pet",
+ "emptyDesc": "Give your pet their own profile on PawFeed."
+ },
+ "PetProfile": {
+ "editProfile": "Edit profile",
+ "health": "Health",
+ "managedBy": "Managed by @{username}",
+ "statsPosts": "posts",
+ "statsFollowers": "followers",
+ "statsFollowing": "following",
+ "adoptionStory": "Adoption story"
+ },
+ "FollowersPage": {
+ "title": "{count} Followers",
+ "empty": "{petName} doesn't have any followers yet."
+ },
+ "FollowingPage": {
+ "title": "Following {count}",
+ "empty": "{petName} isn't following anyone yet."
+ },
+ "ProfileActions": {
+ "message": "Message",
+ "cannotStartConversation": "Cannot start conversation.",
+ "moreOptions": "More options",
+ "reportProfile": "Report profile",
+ "blockPet": "Block {petName}"
+ },
+ "ReportSheet": {
+ "title": "Report",
+ "reasonQuestion": "Why are you reporting this?",
+ "reasonSpam": "Spam",
+ "reasonHarassment": "Harassment or bullying",
+ "reasonInappropriate": "Inappropriate content",
+ "reasonMisinformation": "False or misleading information",
+ "reasonOther": "Something else",
+ "notesPlaceholder": "Add details (optional)",
+ "charactersRemaining": "{count} characters remaining",
+ "submit": "Submit Report",
+ "cancel": "Don't report",
+ "successToast": "Report submitted. We'll review it shortly.",
+ "errorToast": "Couldn't submit your report. Please try again."
+ },
+ "BlockDialog": {
+ "title": "Block {petName}?",
+ "description": "{petName}'s posts and profile will be hidden from you. They won't know you blocked them.",
+ "cancel": "Don't block",
+ "confirm": "Block",
+ "successToast": "You've blocked {petName}",
+ "errorToast": "Couldn't block this pet. Please try again."
}
}
diff --git a/src/app/(app)/pets/PetGrid.tsx b/src/app/(app)/pets/PetGrid.tsx
index ea13c9b..5c1880c 100644
--- a/src/app/(app)/pets/PetGrid.tsx
+++ b/src/app/(app)/pets/PetGrid.tsx
@@ -2,6 +2,7 @@
import Link from "next/link";
import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
import { PetCard } from "@/components/pet/PetCard";
import { Button } from "@/components/ui/button";
import { useActivePet } from "@/context/ActivePetContext";
@@ -24,21 +25,22 @@ interface PetGridProps {
}
export function PetGrid({ pets }: PetGridProps) {
+ const t = useTranslations("PetsPage");
const { activePetId, setActivePet } = useActivePet();
const router = useRouter();
if (pets.length === 0) {
return (
-
Add your first pet
+
{t("emptyTitle")}
- Give your pet their own profile on PawFeed.
+ {t("emptyDesc")}
);
@@ -63,7 +65,7 @@ export function PetGrid({ pets }: PetGridProps) {
className="flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-muted p-6 text-sm text-muted-foreground hover:border-muted-foreground/50 transition-colors min-h-[180px]"
>
+
- Add Pet
+ {t("addPet")}
);
diff --git a/src/app/(app)/pets/[petId]/ProfileActions.tsx b/src/app/(app)/pets/[petId]/ProfileActions.tsx
index 2b24b2e..e79b762 100644
--- a/src/app/(app)/pets/[petId]/ProfileActions.tsx
+++ b/src/app/(app)/pets/[petId]/ProfileActions.tsx
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
import { MoreHorizontal, MessageCircle, Loader2 } from "lucide-react";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
@@ -40,6 +41,7 @@ interface ProfileActionsProps {
}
export function ProfileActions({ petId, petName, ownerId }: ProfileActionsProps) {
+ const t = useTranslations("ProfileActions");
const { activePetId } = useActivePet();
const router = useRouter();
const trpc = useTRPC();
@@ -49,7 +51,7 @@ export function ProfileActions({ petId, petName, ownerId }: ProfileActionsProps)
const getOrCreateMutation = useMutation(
trpc.conversations.getOrCreate.mutationOptions({
onSuccess: (conv) => router.push(`/messages/${conv.id}`),
- onError: (err) => toast.error(err.message ?? "Cannot start conversation."),
+ onError: (err) => toast.error(err.message ?? t("cannotStartConversation")),
})
);
@@ -88,7 +90,7 @@ export function ProfileActions({ petId, petName, ownerId }: ProfileActionsProps)
) : (
)}
- Message
+ {t("message")}
)}
@@ -98,7 +100,7 @@ export function ProfileActions({ petId, petName, ownerId }: ProfileActionsProps)
@@ -107,14 +109,14 @@ export function ProfileActions({ petId, petName, ownerId }: ProfileActionsProps)
onClick={() => setReportOpen(true)}
className="cursor-pointer"
>
- Report profile
+ {t("reportProfile")}
setBlockOpen(true)}
className="cursor-pointer text-destructive focus:text-destructive"
>
- Block {petName}
+ {t("blockPet", { petName })}
diff --git a/src/app/(app)/pets/[petId]/followers/page.tsx b/src/app/(app)/pets/[petId]/followers/page.tsx
index 3cc26dc..db4d924 100644
--- a/src/app/(app)/pets/[petId]/followers/page.tsx
+++ b/src/app/(app)/pets/[petId]/followers/page.tsx
@@ -1,4 +1,5 @@
import { notFound } from "next/navigation";
+import { getTranslations } from "next-intl/server";
import { createTRPCCaller } from "@/trpc/server";
import { ScrollArea } from "@/components/ui/scroll-area";
import { FollowersList } from "@/app/(app)/pets/[petId]/followers/FollowersList";
@@ -20,6 +21,7 @@ interface PageProps {
export default async function FollowersPage({ params }: PageProps) {
const { petId } = await params;
+ const t = await getTranslations("FollowersPage");
const trpc = await createTRPCCaller();
let pet;
@@ -36,12 +38,12 @@ export default async function FollowersPage({ params }: PageProps) {
return (
- {followers.length} Followers
+ {t("title", { count: followers.length })}
{followers.length === 0 ? (
- {pet.name} doesn't have any followers yet.
+ {t("empty", { petName: pet.name })}
) : (
diff --git a/src/app/(app)/pets/[petId]/following/page.tsx b/src/app/(app)/pets/[petId]/following/page.tsx
index fd22c61..77a68a2 100644
--- a/src/app/(app)/pets/[petId]/following/page.tsx
+++ b/src/app/(app)/pets/[petId]/following/page.tsx
@@ -1,4 +1,5 @@
import { notFound } from "next/navigation";
+import { getTranslations } from "next-intl/server";
import { createTRPCCaller } from "@/trpc/server";
import { ScrollArea } from "@/components/ui/scroll-area";
import { FollowingList } from "@/app/(app)/pets/[petId]/following/FollowingList";
@@ -20,6 +21,7 @@ interface PageProps {
export default async function FollowingPage({ params }: PageProps) {
const { petId } = await params;
+ const t = await getTranslations("FollowingPage");
const trpc = await createTRPCCaller();
let pet;
@@ -36,12 +38,12 @@ export default async function FollowingPage({ params }: PageProps) {
return (
- Following {following.length}
+ {t("title", { count: following.length })}
{following.length === 0 ? (
- {pet.name} isn't following anyone yet.
+ {t("empty", { petName: pet.name })}
) : (
diff --git a/src/app/(app)/pets/[petId]/page.tsx b/src/app/(app)/pets/[petId]/page.tsx
index 4610468..27d36f8 100644
--- a/src/app/(app)/pets/[petId]/page.tsx
+++ b/src/app/(app)/pets/[petId]/page.tsx
@@ -1,6 +1,7 @@
import { notFound } from "next/navigation";
import Link from "next/link";
import { currentUser } from "@clerk/nextjs/server";
+import { getTranslations } from "next-intl/server";
import { createTRPCCaller } from "@/trpc/server";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
@@ -28,6 +29,7 @@ function getInitials(name: string): string {
export default async function PetProfilePage({ params }: PageProps) {
const { petId } = await params;
+ const t = await getTranslations("PetProfile");
const trpc = await createTRPCCaller();
let pet;
@@ -74,7 +76,7 @@ export default async function PetProfilePage({ params }: PageProps) {
{/* Owner attribution — only shown on own pet */}
{isOwnPet && ownerUsername && (
- Managed by @{ownerUsername}
+ {t("managedBy", { username: ownerUsername })}
)}
@@ -85,13 +87,13 @@ export default async function PetProfilePage({ params }: PageProps) {
href={`/pets/${pet.id}/edit`}
className={buttonVariants({ variant: "outline" })}
>
- Edit profile
+ {t("editProfile")}
- Health
+ {t("health")}
)}
@@ -101,21 +103,21 @@ export default async function PetProfilePage({ params }: PageProps) {
0
- posts
+ {t("statsPosts")}
{followers.length}
-
followers
+
{t("statsFollowers")}
{following.length}
-
following
+
{t("statsFollowing")}
@@ -135,7 +137,7 @@ export default async function PetProfilePage({ params }: PageProps) {
<>
- Adoption story
+ {t("adoptionStory")}
{pet.adoptionStory}
>
diff --git a/src/app/(app)/pets/page.tsx b/src/app/(app)/pets/page.tsx
index 7b2d9e2..2e0ec38 100644
--- a/src/app/(app)/pets/page.tsx
+++ b/src/app/(app)/pets/page.tsx
@@ -1,4 +1,5 @@
import Link from "next/link";
+import { getTranslations } from "next-intl/server";
import { createTRPCCaller } from "@/trpc/server";
import { buttonVariants } from "@/components/ui/button";
import { PetGrid } from "./PetGrid";
@@ -15,6 +16,7 @@ export default async function PetsPage({
}: {
searchParams: Promise<{ deleteAccount?: string }>;
}) {
+ const t = await getTranslations("PetsPage");
const trpc = await createTRPCCaller();
const pets = await trpc.pets.list();
const { deleteAccount } = await searchParams;
@@ -22,10 +24,10 @@ export default async function PetsPage({
return (
-
My Pets
+ {t("title")}
{/* base-nova Button doesn't support asChild — use Link with button styles */}
- Add Pet
+ {t("addPet")}
diff --git a/src/components/pet/PetForm.tsx b/src/components/pet/PetForm.tsx
index 4e52316..3f91e0e 100644
--- a/src/components/pet/PetForm.tsx
+++ b/src/components/pet/PetForm.tsx
@@ -312,7 +312,7 @@ export function PetForm({
@@ -330,7 +330,7 @@ export function PetForm({
diff --git a/src/components/safety/BlockDialog.tsx b/src/components/safety/BlockDialog.tsx
index b55d9bd..8353858 100644
--- a/src/components/safety/BlockDialog.tsx
+++ b/src/components/safety/BlockDialog.tsx
@@ -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({
- Block {blockedPetName}?
+ {t("title", { petName: blockedPetName })}
- {blockedPetName}'s posts and profile will be hidden from you.
- They won't know you blocked them.
+ {t("description", { petName: blockedPetName })}
- Don't block
+ {t("cancel")}
- Block
+ {t("confirm")}
diff --git a/src/components/safety/ReportSheet.tsx b/src/components/safety/ReportSheet.tsx
index dcb907e..3744e43 100644
--- a/src/components/safety/ReportSheet.tsx
+++ b/src/components/safety/ReportSheet.tsx
@@ -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("");
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({
- Report
+ {t("title")}
- Why are you reporting this?
+ {t("reasonQuestion")}
@@ -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")}