From 376ea74b364354b1422b7db2bb1f3d2694f2f7df Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Mon, 22 Jun 2026 14:18:18 +0200 Subject: [PATCH] feat(i18n): translate FeedList, PostCard, CommentSheet, FollowButton, MessageThread Adds PostCard, Comments, Follow, Messages, FeedList namespaces to en/de messages. Moves formatRelativeTime/formatDay into components so they can use the t() hook. All hardcoded strings replaced with next-intl calls. Co-Authored-By: Claude Sonnet 4.6 --- messages/de.json | 57 ++++++++++++++++++++ messages/en.json | 57 ++++++++++++++++++++ src/components/feed/CommentSheet.tsx | 38 ++++++------- src/components/feed/FeedList.tsx | 8 +-- src/components/feed/PostCard.tsx | 66 +++++++++++------------ src/components/messages/MessageThread.tsx | 30 ++++++----- src/components/social/FollowButton.tsx | 24 +++++---- 7 files changed, 201 insertions(+), 79 deletions(-) diff --git a/messages/de.json b/messages/de.json index cc079b6..907b80e 100644 --- a/messages/de.json +++ b/messages/de.json @@ -102,5 +102,62 @@ "switch": "Sprache", "en": "English", "de": "Deutsch" + }, + "PostCard": { + "justNow": "gerade eben", + "minutesAgo": "vor {count} Min.", + "hoursAgo": "vor {count} Std.", + "daysAgo": "vor {count} T.", + "editCaption": "Beschreibung bearbeiten", + "deletePost": "Beitrag löschen", + "reportPost": "Beitrag melden", + "blockPet": "{name} blockieren", + "captionPlaceholder": "Beschreibung eingeben…", + "charsRemaining": "Noch {count} Zeichen", + "saveCaption": "Speichern", + "cancel": "Abbrechen", + "more": "mehr", + "less": "weniger", + "deleteTitle": "Beitrag löschen?", + "deleteDesc": "Dieser Beitrag und alle Fotos werden dauerhaft gelöscht. Das kann nicht rückgängig gemacht werden.", + "keepPost": "Behalten", + "yesDelete": "Ja, löschen", + "captionUpdated": "Beschreibung aktualisiert", + "captionUpdateError": "Beschreibung konnte nicht aktualisiert werden. Bitte versuche es erneut.", + "postDeleted": "Beitrag gelöscht", + "deleteError": "Beitrag konnte nicht gelöscht werden. Bitte versuche es erneut." + }, + "Comments": { + "title": "Kommentare", + "justNow": "jetzt", + "empty": "Noch keine Kommentare — sei der Erste!", + "placeholder": "Kommentar hinzufügen…", + "post": "Senden", + "postError": "Kommentar konnte nicht gepostet werden. Bitte versuche es erneut.", + "deleteError": "Kommentar konnte nicht gelöscht werden. Bitte versuche es erneut." + }, + "Follow": { + "follow": "Folgen", + "following": "Gefolgt", + "unfollow": "Entfolgen", + "unfollowTitle": "{name} entfolgen?", + "unfollowDesc": "Du siehst {name}'s Beiträge nicht mehr in deinem Feed.", + "keepFollowing": "Weiter folgen", + "followError": "Etwas ist schiefgelaufen. Bitte versuche es erneut.", + "nowFollowing": "Du folgst jetzt {name}", + "unfollowed": "{name} entfolgt" + }, + "Messages": { + "today": "Heute", + "yesterday": "Gestern", + "empty": "Sag Hallo 👋", + "placeholder": "Nachricht…", + "charsLeft": "Noch {count} Zeichen", + "sendError": "Nachricht konnte nicht gesendet werden. Bitte versuche es erneut." + }, + "FeedList": { + "forYou": "Für dich", + "recent": "Aktuell", + "allCaughtUp": "Du bist auf dem neuesten Stand. Folge mehr Tieren für weitere Beiträge." } } diff --git a/messages/en.json b/messages/en.json index 258d65b..27e56d9 100644 --- a/messages/en.json +++ b/messages/en.json @@ -102,5 +102,62 @@ "switch": "Language", "en": "English", "de": "Deutsch" + }, + "PostCard": { + "justNow": "just now", + "minutesAgo": "{count}m ago", + "hoursAgo": "{count}h ago", + "daysAgo": "{count}d ago", + "editCaption": "Edit caption", + "deletePost": "Delete post", + "reportPost": "Report post", + "blockPet": "Block {name}", + "captionPlaceholder": "Write a caption…", + "charsRemaining": "{count} characters remaining", + "saveCaption": "Save caption", + "cancel": "Cancel", + "more": "more", + "less": "less", + "deleteTitle": "Delete this post?", + "deleteDesc": "This will permanently delete the post and all its photos. This can't be undone.", + "keepPost": "Keep post", + "yesDelete": "Yes, delete", + "captionUpdated": "Caption updated", + "captionUpdateError": "Couldn't update caption. Please try again.", + "postDeleted": "Post deleted", + "deleteError": "Couldn't delete post. Please try again." + }, + "Comments": { + "title": "Comments", + "justNow": "now", + "empty": "No comments yet — be the first!", + "placeholder": "Add a comment…", + "post": "Post", + "postError": "Couldn't post comment. Please try again.", + "deleteError": "Couldn't delete comment. Please try again." + }, + "Follow": { + "follow": "Follow", + "following": "Following", + "unfollow": "Unfollow", + "unfollowTitle": "Unfollow {name}?", + "unfollowDesc": "You'll stop seeing {name}'s posts in your feed.", + "keepFollowing": "Keep following", + "followError": "Something went wrong. Try again.", + "nowFollowing": "You're now following {name}", + "unfollowed": "Unfollowed {name}" + }, + "Messages": { + "today": "Today", + "yesterday": "Yesterday", + "empty": "Say hello 👋", + "placeholder": "Message…", + "charsLeft": "{count} characters left", + "sendError": "Failed to send message. Try again." + }, + "FeedList": { + "forYou": "For you", + "recent": "Recent", + "allCaughtUp": "You're all caught up. Follow more pets to see more posts." } } diff --git a/src/components/feed/CommentSheet.tsx b/src/components/feed/CommentSheet.tsx index 260a69b..344c79c 100644 --- a/src/components/feed/CommentSheet.tsx +++ b/src/components/feed/CommentSheet.tsx @@ -17,18 +17,7 @@ import { SheetTitle, } from "@/components/ui/sheet"; import { getAvatarUrl } from "@/lib/avatar-url"; - -function formatRelativeTime(date: Date | string): string { - const diffMs = Date.now() - new Date(date).getTime(); - const diffMins = Math.floor(diffMs / 60000); - const diffHours = Math.floor(diffMins / 60); - const diffDays = Math.floor(diffHours / 24); - if (diffMins < 1) return "just now"; - if (diffMins < 60) return `${diffMins}m`; - if (diffHours < 24) return `${diffHours}h`; - if (diffDays < 7) return `${diffDays}d`; - return new Date(date).toLocaleDateString(); -} +import { useTranslations } from "next-intl"; interface CommentSheetProps { postId: string; @@ -38,7 +27,20 @@ interface CommentSheetProps { } export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheetProps) { + const t = useTranslations("Comments"); const trpc = useTRPC(); + + function formatRelativeTime(date: Date | string): string { + const diffMs = Date.now() - new Date(date).getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + if (diffMins < 1) return t("justNow"); + if (diffMins < 60) return `${diffMins}m`; + if (diffHours < 24) return `${diffHours}h`; + if (diffDays < 7) return `${diffDays}d`; + return new Date(date).toLocaleDateString(); + } const queryClient = useQueryClient(); const [body, setBody] = useState(""); const textareaRef = useRef(null); @@ -59,7 +61,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet queryKey: [["trpc", "feed", "getFeed"]], }); }, - onError: () => toast.error("Couldn't post comment. Please try again."), + onError: () => toast.error(t("postError")), }) ); @@ -73,7 +75,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet queryKey: [["trpc", "feed", "getFeed"]], }); }, - onError: () => toast.error("Couldn't delete comment. Please try again."), + onError: () => toast.error(t("deleteError")), }) ); @@ -104,7 +106,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet - Comments + {t("title")} @@ -116,7 +118,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet ) : comments.length === 0 ? (

- No comments yet — be the first! + {t("empty")}

) : (
@@ -172,7 +174,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet ref={textareaRef} rows={1} maxLength={500} - placeholder="Add a comment…" + placeholder={t("placeholder")} value={body} onChange={(e) => setBody(e.target.value)} onKeyDown={handleKeyDown} @@ -187,7 +189,7 @@ export function CommentSheet({ postId, petId, open, onOpenChange }: CommentSheet {createMutation.isPending ? ( ) : ( - "Post" + t("post") )}
diff --git a/src/components/feed/FeedList.tsx b/src/components/feed/FeedList.tsx index bee55b1..ee217c2 100644 --- a/src/components/feed/FeedList.tsx +++ b/src/components/feed/FeedList.tsx @@ -11,6 +11,7 @@ import { SkeletonCard } from "@/components/feed/SkeletonCard"; import { EmptyFeed } from "@/components/feed/EmptyFeed"; import { WelcomeCard } from "@/components/feed/WelcomeCard"; import { Sparkles, Clock } from "lucide-react"; +import { useTranslations } from "next-intl"; const AD_INTERVAL = 7; type FeedMode = "chrono" | "algo"; @@ -37,6 +38,7 @@ export function FeedList() { const { activePetId } = useActivePet(); const trpc = useTRPC(); const { mode, changeMode } = useFeedMode(); + const t = useTranslations("FeedList"); const { ref: sentinelRef, inView } = useInView({ threshold: 0 }); @@ -111,7 +113,7 @@ export function FeedList() { }`} > - Für dich + {t("forYou")} @@ -152,7 +154,7 @@ export function FeedList() { {!hasNextPage && !isFetchingNextPage && allPosts.length > 0 && (

- {"You're all caught up. Follow more pets to see more posts."} + {t("allCaughtUp")}

)} diff --git a/src/components/feed/PostCard.tsx b/src/components/feed/PostCard.tsx index c620199..f52a3f3 100644 --- a/src/components/feed/PostCard.tsx +++ b/src/components/feed/PostCard.tsx @@ -47,6 +47,7 @@ import { CommentSheet } from "@/components/feed/CommentSheet"; import { RepostCard } from "@/components/feed/RepostCard"; import { BlockDialog } from "@/components/safety/BlockDialog"; import { ReportSheet } from "@/components/safety/ReportSheet"; +import { useTranslations } from "next-intl"; /** * PostCard — full photo post card with carousel, caption clamp, and options menu. @@ -117,25 +118,25 @@ export type PostCardProps = { variant?: "feed" | "detail"; }; -function formatRelativeTime(date: Date | string): string { - const now = Date.now(); - const diffMs = now - new Date(date).getTime(); - const diffSecs = Math.floor(diffMs / 1000); - const diffMins = Math.floor(diffSecs / 60); - const diffHours = Math.floor(diffMins / 60); - const diffDays = Math.floor(diffHours / 24); - - if (diffSecs < 60) return "just now"; - if (diffMins < 60) return `${diffMins}m ago`; - if (diffHours < 24) return `${diffHours}h ago`; - if (diffDays < 7) return `${diffDays}d ago`; - return new Date(date).toLocaleDateString(); -} - export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) { + const t = useTranslations("PostCard"); const trpc = useTRPC(); const { activePetId } = useActivePet(); + function formatRelativeTime(date: Date | string): string { + const now = Date.now(); + const diffMs = now - new Date(date).getTime(); + const diffSecs = Math.floor(diffMs / 1000); + const diffMins = Math.floor(diffSecs / 60); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + if (diffSecs < 60) return t("justNow"); + if (diffMins < 60) return t("minutesAgo", { count: diffMins }); + if (diffHours < 24) return t("hoursAgo", { count: diffHours }); + if (diffDays < 7) return t("daysAgo", { count: diffDays }); + return new Date(date).toLocaleDateString(); + } + const { pet, images, createdAt } = post; const [currentCaption, setCurrentCaption] = useState(post.caption); @@ -183,10 +184,10 @@ export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) { onSuccess: (updated) => { setCurrentCaption(updated.caption); setIsEditingCaption(false); - toast.success("Caption updated"); + toast.success(t("captionUpdated")); }, onError: () => { - toast.error("Couldn't update caption. Please try again."); + toast.error(t("captionUpdateError")); }, }) ); @@ -195,11 +196,11 @@ export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) { trpc.posts.delete.mutationOptions({ onSuccess: () => { setShowDeleteDialog(false); - toast.success("Post deleted"); + toast.success(t("postDeleted")); onDeleted?.(post.id); }, onError: () => { - toast.error("Couldn't delete post. Please try again."); + toast.error(t("deleteError")); }, }) ); @@ -280,13 +281,13 @@ export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) { }} className="cursor-pointer" > - Edit caption + {t("editCaption")} setShowDeleteDialog(true)} className="cursor-pointer text-destructive focus:text-destructive" > - Delete post + {t("deletePost")} ) : ( @@ -296,14 +297,14 @@ export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) { onClick={() => setShowReportSheet(true)} className="cursor-pointer" > - Report post + {t("reportPost")} setShowBlockDialog(true)} className="cursor-pointer text-destructive focus:text-destructive" > - Block {pet.name} + {t("blockPet", { name: pet.name })} )} @@ -386,7 +387,7 @@ export function PostCard({ post, onDeleted, variant = "feed" }: PostCardProps) {