diff --git a/messages/en.json b/messages/en.json index 939d20a..79d156c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -124,5 +124,130 @@ "emptyState": { "title": "Not enough data yet" } + }, + "Inbox": { + "page": { + "whatsappNotConnected": "WhatsApp® is not connected. Go to Settings to connect your account." + }, + "conversationList": { + "searchPlaceholder": "Search conversations...", + "filterAll": "All", + "filterUnread": "Unread", + "filterOpen": "Open", + "filterPending": "Pending", + "filterClosed": "Closed", + "tags": "Tags", + "company": "Company", + "allCompanies": "All companies", + "clearAll": "Clear all", + "noConversations": "No conversations found", + "noMessagesYet": "No messages yet", + "unknown": "Unknown" + }, + "messageThread": { + "backToConversations": "Back to conversations", + "hideContactPanel": "Hide contact panel", + "showContactPanel": "Show contact panel", + "hideContact": "Hide contact", + "showContact": "Show contact", + "refresh": "Refresh", + "refreshConversation": "Refresh conversation", + "status": "Status", + "statusOpen": "Open", + "statusPending": "Pending", + "statusClosed": "Closed", + "assign": "Assign", + "assigned": "Assigned", + "unassign": "Unassign", + "noTeammates": "No teammates available", + "me": " (me)", + "noMessagesYet": "No messages yet", + "sendTemplateHint": "Send a template to start the conversation", + "selectConversation": "Select a conversation", + "selectConversationHint": "Choose a conversation from the left to start messaging", + "today": "Today", + "yesterday": "Yesterday" + }, + "sessionTimer": { + "expired": "Expired", + "xhRemaining": "{hours}h remaining", + "xmRemaining": "{minutes}m remaining", + "noCustomerMessages": "No customer messages" + }, + "composer": { + "draftHint": "Tap the ✨ to draft a reply with AI — you can edit it before sending", + "sessionExpiredHint": "24-hour session expired. Use a template to re-engage.", + "templates": "Templates", + "readOnlyPlaceholder": "Read-only — viewers can browse but not reply", + "sessionExpiredPlaceholder": "Session expired - use a template", + "typeMessagePlaceholder": "Type a message... (Shift+Enter for new line)", + "readOnlyTitle": "Read-only — your role can't send messages", + "recording": "Recording… {current} / {max}", + "cancel": "Cancel", + "stopAndAttach": "Stop and attach", + "attachMedia": "Attach media", + "photo": "Photo", + "video": "Video", + "document": "Document", + "voiceNote": "Voice note", + "sendTemplate": "Send template", + "draftWithAI": "Draft a reply with AI", + "addCaption": "Add a caption…", + "removeAttachment": "Remove attachment" + }, + "bubble": { + "photo": "Photo", + "video": "Video", + "audio": "Audio", + "unavailable": "{label} unavailable", + "document": "Document", + "locationShared": "Location shared", + "template": "Template", + "buttonReply": "Button reply", + "interactiveReply": "[Interactive reply]", + "unsupported": "[Unsupported message type]" + }, + "actions": { + "reply": "Reply", + "copyText": "Copy text", + "delete": "Delete", + "react": "React", + "reactWith": "React with {emoji}", + "nothingToCopy": "Nothing to copy", + "copied": "Copied", + "copyFailed": "Copy failed" + }, + "replyQuote": { + "photo": "[Photo]", + "video": "[Video]", + "document": "[Document]", + "audio": "[Audio]", + "voiceMessage": "[Voice message]", + "location": "[Location]", + "template": "[Template]", + "message": "[Message]", + "cancelReply": "Cancel reply" + }, + "templatePicker": { + "sendTemplate": "Send template", + "fillPlaceholders": "Fill in the placeholders to render this template. Meta requires every variable to be set.", + "pickTemplate": "Pick an approved WhatsApp template to send to this contact.", + "noApprovedTemplates": "No approved templates", + "noApprovedTemplatesHint": "Approve a template in Meta WhatsApp Manager, then sync it from Settings → Templates.", + "preview": "Preview", + "headerValuePlaceholder": "Value for the header variable", + "bodyValuePlaceholder": "Value for {val}", + "urlSuffixValuePlaceholder": "URL suffix value", + "finalUrl": "Final URL: {url}", + "back": "Back", + "cancel": "Cancel", + "send": "Send template" + }, + "sidebar": { + "contactInfo": "Contact info", + "tags": "Tags", + "notes": "Notes", + "deals": "Deals" + } } } diff --git a/src/app/(dashboard)/inbox/page.tsx b/src/app/(dashboard)/inbox/page.tsx index 70480ec..b32e74b 100644 --- a/src/app/(dashboard)/inbox/page.tsx +++ b/src/app/(dashboard)/inbox/page.tsx @@ -2,6 +2,7 @@ import { useState, useCallback, useEffect, useRef } from "react"; import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { createClient } from "@/lib/supabase/client"; import { CONVERSATION_SELECT, @@ -21,6 +22,7 @@ import { cn } from "@/lib/utils"; const CONTACT_PANEL_STORAGE_KEY = "wacrm:inbox:contact-panel-open"; export default function InboxPage() { + const t = useTranslations("Inbox.page"); const router = useRouter(); const searchParams = useSearchParams(); /** @@ -556,7 +558,7 @@ export default function InboxPage() {

- WhatsApp® is not connected. Go to Settings to connect your account. + {t("whatsappNotConnected")}

)} diff --git a/src/components/inbox/contact-sidebar.tsx b/src/components/inbox/contact-sidebar.tsx index cbaa4ee..8249839 100644 --- a/src/components/inbox/contact-sidebar.tsx +++ b/src/components/inbox/contact-sidebar.tsx @@ -19,12 +19,16 @@ import { import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { format } from "date-fns"; +import { useTranslations } from "next-intl"; interface ContactSidebarProps { contact: Contact | null; } export function ContactSidebar({ contact }: ContactSidebarProps) { + const tSidebar = useTranslations("Inbox.sidebar"); + const tThread = useTranslations("Inbox.messageThread"); + const { accountId } = useAuth(); const [copied, setCopied] = useState(false); const [deals, setDeals] = useState([]); @@ -118,7 +122,7 @@ export function ContactSidebar({ contact }: ContactSidebarProps) { if (!contact) { return (
-

Select a conversation

+

{tThread("selectConversation")}

); } @@ -181,7 +185,7 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
- Tags + {tSidebar("tags")}
{tags.length === 0 ? ( @@ -210,7 +214,7 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
- Active Deals + {tSidebar("deals")}
{deals.length === 0 ? ( @@ -254,7 +258,7 @@ export function ContactSidebar({ contact }: ContactSidebarProps) {
- Notes + {tSidebar("notes")}
diff --git a/src/components/inbox/conversation-list.tsx b/src/components/inbox/conversation-list.tsx index d3dafd2..8706e80 100644 --- a/src/components/inbox/conversation-list.tsx +++ b/src/components/inbox/conversation-list.tsx @@ -11,6 +11,7 @@ import { cn } from "@/lib/utils"; import type { Conversation, ConversationStatus, Tag } from "@/types"; import { Search, ChevronDown, X } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; +import { useTranslations } from "next-intl"; import { Input } from "@/components/ui/input"; import { DropdownMenu, @@ -41,15 +42,9 @@ const STATUS_COLORS: Record = { closed: "bg-muted-foreground", }; -type InboxFilter = ConversationStatus | "all" | "unread"; -const FILTER_OPTIONS: { label: string; value: InboxFilter }[] = [ - { label: "All", value: "all" }, - { label: "Unread", value: "unread" }, - { label: "Open", value: "open" }, - { label: "Pending", value: "pending" }, - { label: "Closed", value: "closed" }, -]; + +type InboxFilter = ConversationStatus | "all" | "unread"; export function ConversationList({ activeConversationId, @@ -58,6 +53,16 @@ export function ConversationList({ onConversationsLoaded, resyncToken = 0, }: ConversationListProps) { + const t = useTranslations("Inbox.conversationList"); + + const FILTER_OPTIONS: { label: string; value: InboxFilter }[] = useMemo(() => [ + { label: t("filterAll"), value: "all" }, + { label: t("filterUnread"), value: "unread" }, + { label: t("filterOpen"), value: "open" }, + { label: t("filterPending"), value: "pending" }, + { label: t("filterClosed"), value: "closed" }, + ], [t]); + const [search, setSearch] = useState(""); const [filter, setFilter] = useState("all"); const [loading, setLoading] = useState(true); @@ -226,7 +231,7 @@ export function ConversationList({
@@ -234,7 +239,7 @@ export function ConversationList({
- {activeFilter?.label ?? "All"} + {activeFilter?.label ?? t("filterAll")} - Tags + {t("tags")} {selectedTagIds.length > 0 && ( {selectedTagIds.length} @@ -310,7 +315,7 @@ export function ConversationList({ : "text-muted-foreground hover:text-foreground" )} > - {selectedCompany ?? "Company"} + {selectedCompany ?? t("company")} - All companies + {t("allCompanies")} {companies.map((co) => ( - {tag?.name ?? "Tag"} + {tag?.name ?? t("tags")} ); @@ -379,7 +384,7 @@ export function ConversationList({ onClick={clearContactFilters} className="px-1 text-[11px] text-muted-foreground hover:text-foreground" > - Clear all + {t("clearAll")}
)} @@ -398,7 +403,7 @@ export function ConversationList({
) : filtered.length === 0 ? (
-

No conversations found

+

{t("noConversations")}

) : (
@@ -408,6 +413,7 @@ export function ConversationList({ conversation={conv} isActive={conv.id === activeConversationId} onSelect={handleSelect} + t={t} /> ))}
@@ -421,15 +427,17 @@ interface ConversationItemProps { conversation: Conversation; isActive: boolean; onSelect: (conversation: Conversation) => void; + t: ReturnType; } function ConversationItem({ conversation, isActive, onSelect, + t, }: ConversationItemProps) { const contact = conversation.contact; - const displayName = contact?.name || contact?.phone || "Unknown"; + const displayName = contact?.name || contact?.phone || t("unknown"); const initials = displayName.charAt(0).toUpperCase(); const handleClick = useCallback(() => { @@ -473,7 +481,7 @@ function ConversationItem({

- {conversation.last_message_text || "No messages yet"} + {conversation.last_message_text || t("noMessagesYet")}

{conversation.unread_count > 0 && ( diff --git a/src/components/inbox/message-actions.tsx b/src/components/inbox/message-actions.tsx index fcd4ed2..240d03b 100644 --- a/src/components/inbox/message-actions.tsx +++ b/src/components/inbox/message-actions.tsx @@ -10,6 +10,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import type { Message } from "@/types"; +import { useTranslations } from "next-intl"; // WhatsApp's own quick-reaction bar starts with these six. Picking the same // set keeps the affordance familiar without pulling in a 300KB emoji library. @@ -33,6 +34,8 @@ export function MessageActions({ onReact, children, }: MessageActionsProps) { + const t = useTranslations("Inbox.actions"); + // Touch devices have no hover. Long-press fires `contextmenu`; we capture // it, suppress the native menu, and pin the toolbar open until the user // interacts elsewhere. @@ -50,14 +53,14 @@ export function MessageActions({ const handleCopy = async () => { const text = message.content_text ?? ""; if (!text) { - toast.error("Nothing to copy"); + toast.error(t("nothingToCopy")); return; } try { await navigator.clipboard.writeText(text); - toast.success("Copied"); + toast.success(t("copied")); } catch { - toast.error("Copy failed"); + toast.error(t("copyFailed")); } setTouchOpen(false); }; @@ -104,7 +107,7 @@ export function MessageActions({ @@ -118,7 +121,7 @@ export function MessageActions({ type="button" onClick={() => handlePickEmoji(e)} className="flex h-8 w-8 items-center justify-center rounded-full text-lg leading-none transition-transform hover:scale-125 hover:bg-muted" - aria-label={`React with ${e}`} + aria-label={t("reactWith", { emoji: e })} > {e} @@ -129,7 +132,7 @@ export function MessageActions({ type="button" onClick={handleReply} className="flex h-5 w-5 items-center justify-center rounded-full text-popover-foreground hover:bg-muted hover:text-foreground" - aria-label="Reply" + aria-label={t("reply")} > @@ -137,7 +140,7 @@ export function MessageActions({ type="button" onClick={handleCopy} className="flex h-5 w-5 items-center justify-center rounded-full text-popover-foreground hover:bg-muted hover:text-foreground" - aria-label="Copy" + aria-label={t("copyText")} > diff --git a/src/components/inbox/message-bubble.tsx b/src/components/inbox/message-bubble.tsx index 510f25c..6cedbfb 100644 --- a/src/components/inbox/message-bubble.tsx +++ b/src/components/inbox/message-bubble.tsx @@ -17,6 +17,7 @@ import { import { format } from "date-fns"; import { ReplyQuote } from "./reply-quote"; import { MessageReactions } from "./message-reactions"; +import { useTranslations } from "next-intl"; interface MessageBubbleProps { message: Message; @@ -44,11 +45,11 @@ function StatusIcon({ status }: { status: Message["status"] }) { } } -function MediaUnavailable({ label }: { label: string }) { +function MediaUnavailable({ label, t }: { label: string, t: ReturnType }) { return (
- {label} unavailable + {t("unavailable", { label })}
); } @@ -116,7 +117,7 @@ function MediaImage({ url, alt }: { url: string; alt: string }) { ); } -function MessageContent({ message }: { message: Message }) { +function MessageContent({ message, t }: { message: Message, t: ReturnType }) { switch (message.content_type) { case "text": return ( @@ -131,7 +132,7 @@ function MessageContent({ message }: { message: Message }) { {message.media_url ? ( ) : ( - + )} {message.content_text && (

@@ -151,7 +152,7 @@ function MessageContent({ message }: { message: Message }) { className="max-h-64 max-w-60 rounded-lg" /> ) : ( - + )} {message.content_text && (

@@ -167,14 +168,14 @@ function MessageContent({ message }: { message: Message }) { {message.media_url ? (

); case "document": if (!message.media_url) { - return ; + return ; } return ( - {message.content_text || "Document"} + {message.content_text || t("document")} ); @@ -195,7 +196,7 @@ function MessageContent({ message }: { message: Message }) {
- Template + {t("template")} {message.content_text && (

@@ -209,7 +210,7 @@ function MessageContent({ message }: { message: Message }) { return (

- {message.content_text || "Location shared"} + {message.content_text || t("locationShared")}
); @@ -223,10 +224,10 @@ function MessageContent({ message }: { message: Message }) {
- Button reply + {t("buttonReply")}

- {message.content_text || "[Interactive reply]"} + {message.content_text || t("interactiveReply")}

); @@ -235,7 +236,7 @@ function MessageContent({ message }: { message: Message }) { default: return (

- {message.content_text || "[Unsupported message type]"} + {message.content_text || t("unsupported")}

); } @@ -248,6 +249,8 @@ export function MessageBubble({ currentUserId, onToggleReaction, }: MessageBubbleProps) { + const t = useTranslations("Inbox.bubble"); + const isAgent = message.sender_type === "agent" || message.sender_type === "bot"; const time = format(new Date(message.created_at), "HH:mm"); @@ -275,7 +278,7 @@ export function MessageBubble({ onPrimary={isAgent} /> )} - +

- 24-hour session expired. Use a template to re-engage. + {t("sessionExpiredHint")}

)} @@ -491,27 +494,27 @@ export function MessageComposer({ onCaptionChange={setCaption} onDiscard={discardDraft} onSend={sendDraft} + t={t} /> ) : recording ? ( // Recording bar — replaces the composer while the mic is live.
- Recording… {formatDuration(recordSeconds)} /{" "} - {formatDuration(MAX_RECORDING_SECONDS)} + {t("recording", { current: formatDuration(recordSeconds), max: formatDuration(MAX_RECORDING_SECONDS) })} @@ -524,10 +527,10 @@ export function MessageComposer({ disabled={inputsDisabled || busy} title={ readOnly - ? "Read-only — your role can't send messages" + ? t("readOnlyTitle") : inputsDisabled ? undefined - : "Attach media" + : t("attachMedia") } className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50" > @@ -540,19 +543,19 @@ export function MessageComposer({ imageInputRef.current?.click()}> - Photo + {t("photo")} videoInputRef.current?.click()}> documentInputRef.current?.click()}> - Document + {t("document")} void startRecording()}> - Voice note + {t("voiceNote")} @@ -562,7 +565,7 @@ export function MessageComposer({ size="sm" canAct={!readOnly} gateReason="send messages" - title={readOnly ? undefined : "Send template"} + title={readOnly ? undefined : t("sendTemplate")} className="h-9 w-9 shrink-0 p-0 text-muted-foreground hover:text-foreground" onClick={onOpenTemplates} > @@ -575,7 +578,7 @@ export function MessageComposer({ canAct={!readOnly} gateReason="send messages" disabled={drafting} - title={readOnly ? undefined : "Draft a reply with AI"} + title={readOnly ? undefined : t("draftWithAI")} className="h-9 w-9 shrink-0 p-0 text-muted-foreground hover:text-primary" onClick={handleDraft} > @@ -593,17 +596,17 @@ export function MessageComposer({ onKeyDown={handleKeyDown} placeholder={ readOnly - ? "Read-only — viewers can browse but not reply" + ? t("readOnlyPlaceholder") : sessionExpired - ? "Session expired - use a template" - : "Type a message... (Shift+Enter for new line)" + ? t("sessionExpiredPlaceholder") + : t("typeMessagePlaceholder") } disabled={sessionExpired || readOnly} rows={1} // Textarea keeps its own inline title — the GatedButton // wrapping pattern doesn't apply to non-button inputs. // The placeholder text also surfaces the read-only state. - title={readOnly ? "Read-only — your role can't send messages" : undefined} + title={readOnly ? t("readOnlyTitle") : undefined} className={cn( "flex-1 resize-none rounded-xl border border-border bg-muted px-4 py-2.5 text-sm text-foreground placeholder-muted-foreground outline-none transition-colors focus:border-primary/50", (sessionExpired || readOnly) && "cursor-not-allowed opacity-50" @@ -628,7 +631,7 @@ export function MessageComposer({ under the textarea left edge. */} {!draft && !recording && (

- Tap the ✨ to draft a reply with AI — you can edit it before sending + {t("draftHint")}

)}
@@ -648,6 +651,7 @@ function MediaDraftPreview({ onCaptionChange, onDiscard, onSend, + t, }: { draft: MediaDraft; busy: boolean; @@ -655,6 +659,7 @@ function MediaDraftPreview({ onCaptionChange: (caption: string) => void; onDiscard: () => void; onSend: () => void; + t: ReturnType; }) { return (
@@ -684,7 +689,7 @@ function MediaDraftPreview({

- Select a conversation + {t("selectConversation")}

- Choose a conversation from the left to start messaging + {t("selectConversationHint")}

); @@ -805,8 +811,8 @@ export function MessageThread({ const assignedAgentId = conversation.assigned_agent_id ?? null; const currentAssignee = profiles.find((p) => p.user_id === assignedAgentId); const assignLabel = assignedAgentId - ? (currentAssignee?.full_name ?? "Assigned") - : "Assign"; + ? (currentAssignee?.full_name ?? t("assigned")) + : t("assign"); return ( // `min-w-0` is load-bearing: the page already puts min-w-0 on the @@ -828,7 +834,7 @@ export function MessageThread({
) : messages.length === 0 ? (
-

No messages yet

+

{t("noMessagesYet")}

- Send a template to start the conversation + {t("sendTemplateHint")}

) : ( @@ -1017,7 +1023,7 @@ export function MessageThread({ {/* Date separator */}
- {formatDateSeparator(group.date)} + {formatDateSeparator(group.date, t)}
{/* Messages */} @@ -1028,8 +1034,11 @@ export function MessageThread({ : null; const reply = parent ? { - authorLabel: authorLabelFor(parent), - preview: buildReplyPreview(parent), + authorLabel: + parent.sender_type === "agent" || parent.sender_type === "bot" + ? t("me") + : contact?.name || contact?.phone || "Unknown", + preview: buildReplyPreview(parent, tQuote), } : null; const msgReactions = reactionsByMessageId.get(msg.id); diff --git a/src/components/inbox/reply-quote.tsx b/src/components/inbox/reply-quote.tsx index c7dadd3..20dd195 100644 --- a/src/components/inbox/reply-quote.tsx +++ b/src/components/inbox/reply-quote.tsx @@ -3,6 +3,7 @@ import { X } from "lucide-react"; import { cn } from "@/lib/utils"; import type { Message } from "@/types"; +import { useTranslations } from "next-intl"; interface ReplyQuoteProps { /** Sender label of the quoted message: "You" for our own messages, @@ -26,6 +27,7 @@ export function ReplyQuote({ onDismiss, onPrimary = false, }: ReplyQuoteProps) { + const t = useTranslations("Inbox.replyQuote"); const isChip = !!onDismiss; return (
@@ -74,22 +76,22 @@ export function ReplyQuote({ } /** Build the one-line preview text shown inside a reply quote. */ -export function buildReplyPreview(message: Message): string { +export function buildReplyPreview(message: Message, t: ReturnType): string { if (message.content_text) return message.content_text; switch (message.content_type) { case "image": - return "[Image]"; + return t("photo"); case "video": - return "[Video]"; + return t("video"); case "audio": - return "[Audio]"; + return t("audio"); case "document": - return "[Document]"; + return t("document"); case "location": - return "[Location]"; + return t("location"); case "template": - return "[Template]"; + return t("template"); default: - return "[Message]"; + return t("message"); } } diff --git a/src/components/inbox/template-picker.tsx b/src/components/inbox/template-picker.tsx index 9a0b754..4715763 100644 --- a/src/components/inbox/template-picker.tsx +++ b/src/components/inbox/template-picker.tsx @@ -22,6 +22,7 @@ import { Loader2, } from "lucide-react"; import { extractVariableIndices } from "@/lib/whatsapp/template-validators"; +import { useTranslations } from "next-intl"; export interface TemplateSendValues { body: string[]; @@ -78,6 +79,8 @@ export function TemplatePicker({ onOpenChange, onSelect, }: TemplatePickerProps) { + const t = useTranslations("Inbox.templatePicker"); + const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState(null); @@ -190,12 +193,12 @@ export function TemplatePicker({ - {selected ? selected.name : "Send template"} + {selected ? selected.name : t("sendTemplate")} {selected - ? "Fill in the placeholders to render this template. Meta requires every variable to be set." - : "Pick an approved WhatsApp template to send to this contact."} + ? t("fillPlaceholders") + : t("pickTemplate")} @@ -207,10 +210,9 @@ export function TemplatePicker({
) : templates.length === 0 ? (
-

No approved templates

+

{t("noApprovedTemplates")}

- Approve a template in Meta WhatsApp Manager, then sync it - from Settings → Templates. + {t("noApprovedTemplatesHint")}

) : ( @@ -249,7 +251,7 @@ export function TemplatePicker({ ) : (
-

Preview

+

{t("preview")}

{renderBodyPreview(selected.body_text, params)}

@@ -267,7 +269,7 @@ export function TemplatePicker({ setHeaderText(e.target.value)} - placeholder="Value for the header variable" + placeholder={t("headerValuePlaceholder")} className="border-border bg-muted text-foreground placeholder:text-muted-foreground" />
@@ -282,7 +284,7 @@ export function TemplatePicker({ next[i] = e.target.value; setParams(next); }} - placeholder={`Value for {{${v}}}`} + placeholder={t("bodyValuePlaceholder", { val: `{{${v}}}` })} className="border-border bg-muted text-foreground placeholder:text-muted-foreground" />
@@ -300,11 +302,11 @@ export function TemplatePicker({ [slot.index]: e.target.value, })) } - placeholder="URL suffix value" + placeholder={t("urlSuffixValuePlaceholder")} className="border-border bg-muted text-foreground placeholder:text-muted-foreground" />

- Final URL: {slot.url.replace(/\{\{1\}\}/g, buttonParams[slot.index] || "{{1}}")} + {t("finalUrl", { url: slot.url.replace(/\{\{1\}\}/g, buttonParams[slot.index] || "{{1}}") })}

))} @@ -320,14 +322,14 @@ export function TemplatePicker({ className="border-border text-popover-foreground hover:bg-muted" > - Back + {t("back")} ) : ( @@ -336,7 +338,7 @@ export function TemplatePicker({ onClick={() => handleOpenChange(false)} className="border-border text-popover-foreground hover:bg-muted" > - Cancel + {t("cancel")} )}