From 9357fd9ec9bd7f6bc8d6a54eb9cff06fe3b65e60 Mon Sep 17 00:00:00 2001 From: Ivan Mercedes Date: Tue, 7 Jul 2026 19:56:55 -0400 Subject: [PATCH] feat(i18n): complete settings module internationalization --- messages/en.json | 186 ++++++++++++++++-- src/components/settings/settings-overview.tsx | 39 ++-- src/components/settings/settings-rail.tsx | 6 +- src/components/settings/whatsapp-config.tsx | 164 +++++++-------- 4 files changed, 267 insertions(+), 128 deletions(-) diff --git a/messages/en.json b/messages/en.json index 9293ee4..a35721d 100644 --- a/messages/en.json +++ b/messages/en.json @@ -808,13 +808,34 @@ "close_conversation": "Close Conversation" }, "triggers": { - "new_message_received": { "label": "New Message Received", "hint": "Any incoming message" }, - "first_inbound_message": { "label": "First Message from Contact", "hint": "First time this contact ever messages you (works for manually-added contacts too)" }, - "keyword_match": { "label": "Keyword Match", "hint": "Message contains specific keyword(s)" }, - "new_contact_created": { "label": "New Contact Created", "hint": "When a contact is auto-created from an incoming message" }, - "conversation_assigned": { "label": "Conversation Assigned", "hint": "When assigned to an agent" }, - "tag_added": { "label": "Tag Added", "hint": "When a tag is added to a contact" }, - "time_based": { "label": "Time-Based", "hint": "On a recurring schedule" } + "new_message_received": { + "label": "New Message Received", + "hint": "Any incoming message" + }, + "first_inbound_message": { + "label": "First Message from Contact", + "hint": "First time this contact ever messages you (works for manually-added contacts too)" + }, + "keyword_match": { + "label": "Keyword Match", + "hint": "Message contains specific keyword(s)" + }, + "new_contact_created": { + "label": "New Contact Created", + "hint": "When a contact is auto-created from an incoming message" + }, + "conversation_assigned": { + "label": "Conversation Assigned", + "hint": "When assigned to an agent" + }, + "tag_added": { + "label": "Tag Added", + "hint": "When a tag is added to a contact" + }, + "time_based": { + "label": "Time-Based", + "hint": "On a recurring schedule" + } }, "trigger": "Trigger", "triggerType": "Trigger type", @@ -1065,17 +1086,148 @@ "flow": "Flow control" }, "nodes": { - "start": { "label": "Start", "blurb": "Entry point of the flow" }, - "send_message": { "label": "Send message", "blurb": "Sends a WhatsApp text message" }, - "send_buttons": { "label": "Send buttons", "blurb": "Sends quick-reply buttons" }, - "send_list": { "label": "Send list", "blurb": "Sends a tappable list of options" }, - "send_media": { "label": "Send media", "blurb": "Sends an image, video, or document" }, - "collect_input": { "label": "Collect input", "blurb": "Asks a question, saves the reply" }, - "condition": { "label": "If / else", "blurb": "Branches on a rule" }, - "set_tag": { "label": "Tag contact", "blurb": "Adds or removes a contact tag" }, - "handoff": { "label": "Handoff to agent", "blurb": "Hands the conversation to a human" }, - "end": { "label": "End", "blurb": "Ends the flow" } + "start": { + "label": "Start", + "blurb": "Entry point of the flow" + }, + "send_message": { + "label": "Send message", + "blurb": "Sends a WhatsApp text message" + }, + "send_buttons": { + "label": "Send buttons", + "blurb": "Sends quick-reply buttons" + }, + "send_list": { + "label": "Send list", + "blurb": "Sends a tappable list of options" + }, + "send_media": { + "label": "Send media", + "blurb": "Sends an image, video, or document" + }, + "collect_input": { + "label": "Collect input", + "blurb": "Asks a question, saves the reply" + }, + "condition": { + "label": "If / else", + "blurb": "Branches on a rule" + }, + "set_tag": { + "label": "Tag contact", + "blurb": "Adds or removes a contact tag" + }, + "handoff": { + "label": "Handoff to agent", + "blurb": "Hands the conversation to a human" + }, + "end": { + "label": "End", + "blurb": "Ends the flow" + } } } + }, + "Settings": { + "overview": { + "notSetup": "Not set up yet", + "connected": "Connected", + "needsReconnecting": "Needs reconnecting", + "viewTeamMembers": "View team members", + "membersCount": "{count} {count, plural, =1 {member} other {members}}", + "pendingInvites": "{count} pending {count, plural, =1 {invite} other {invites}}", + "manageTemplates": "Manage message templates", + "templatesCount": "{count} {count, plural, =1 {template} other {templates}}", + "pendingReview": "{count} pending review", + "tagsAndFields": "Tags and custom fields", + "tagsCount": "{count} {count, plural, =1 {tag} other {tags}}", + "fieldsCount": "{count} custom {count, plural, =1 {field} other {fields}}", + "appearance": "{mode} mode · {theme} accent", + "yourAccount": "Your account", + "loading": "Loading…" + }, + "whatsapp": { + "title": "WhatsApp connection", + "description": "Connect your Meta WhatsApp Business API. Credentials, webhook, and setup steps all live here.", + "tokenCorrupted": "Stored token can't be decrypted", + "resetting": "Resetting...", + "resetConfig": "Reset Configuration", + "credentialsValid": "Credentials valid", + "notConnected": "Not Connected", + "connectedDesc": "Your access token authenticates with Meta. See Registration status below for whether webhooks are actually wired.", + "notConnectedDesc": "Configure your Meta API credentials below to connect your WhatsApp Business account.", + "registered": "Registered — Meta will deliver events to wacrm", + "notRegistered": "Not registered — Meta will not deliver events", + "verifyWithMeta": "Verify with Meta", + "subscribedSince": "Subscribed since {date}. Click Verify with Meta if events stop arriving.", + "unknownDate": "unknown", + "lastAttemptFailed": "Last attempt failed with: ", + "retryHint": "Enter (or correct) the 2-step PIN below and click Save Configuration to retry.", + "noRegistrationHint": "This number was saved before registration tracking existed, or registration was skipped. Enter the 2-step PIN below and click Save Configuration to subscribe it.", + "diagnosticLastRun": "Diagnostic — last run: ", + "live": "live", + "notLive": "not live", + "apiCredentialsTitle": "API Credentials", + "apiCredentialsDesc": "Enter your Meta WhatsApp Business API credentials.", + "phoneNumberId": "Phone Number ID", + "wabaId": "WhatsApp Business Account ID", + "accessToken": "Permanent Access Token", + "accessTokenPlaceholder": "Enter your access token", + "tokenHidden": "Token is hidden for security. Re-enter it to update configuration.", + "webhookVerifyToken": "Webhook Verify Token", + "webhookVerifyTokenPlaceholder": "Create a custom verify token", + "webhookVerifyTokenHint": "A custom string you create. Must match the token you set in Meta webhook settings.", + "twoStepPin": "Two-step verification PIN", + "optional": "(optional)", + "pinPlaceholder": "6-digit PIN from Meta WhatsApp Manager", + "pinHint": "Needed only to wire inbound messages for a production number. Set it in Meta Business Manager → WhatsApp Accounts → Phone Numbers → Two-step verification, then paste it here so wacrm can subscribe the number — otherwise Meta routes inbound events to whichever app last claimed it (the symptom that hits second numbers under a shared WABA). Meta test numbers have no PIN and are pre-registered — leave this blank for them. Leaving it blank also keeps an existing registration untouched.", + "webhookTitle": "Webhook Configuration", + "webhookDesc": "Use this URL as your webhook callback in the Meta App Dashboard.", + "webhookUrl": "Webhook Callback URL", + "saving": "Saving...", + "saveConfig": "Save Configuration", + "testing": "Testing...", + "testConnection": "Test API Connection", + "setupInstructions": "Setup Instructions", + "setupInstructionsDesc": "Follow these steps to connect your WhatsApp Business API.", + "step1": "Create a Meta App", + "step1_1": "Go to developers.facebook.com", + "step1_2": "Click \"My Apps\" and then \"Create App\"", + "step1_3": "Select \"Business\" as the app type", + "step1_4": "Fill in app details and create", + "step2": "Add WhatsApp Product", + "step2_1": "In your app dashboard, click \"Add Product\"", + "step2_2": "Find \"WhatsApp\" and click \"Set Up\"", + "step2_3": "Follow the setup wizard to link your business", + "step3": "Get API Credentials", + "step3_1": "Go to WhatsApp > API Setup", + "step3_2": "Copy your Phone Number ID", + "step3_3": "Copy your WhatsApp Business Account ID", + "step3_4": "Generate a Permanent Access Token from Business Settings > System Users", + "step4": "Configure Webhooks", + "step4_1": "Go to WhatsApp > Configuration", + "step4_2": "Click \"Edit\" on the Webhook section", + "step4_3": "Paste the Webhook Callback URL from above", + "step4_4": "Enter the same Verify Token you set here", + "step4_5": "Subscribe to \"messages\" webhook field", + "metaDocs": "Meta WhatsApp API Documentation" + }, + "sections": { + "overview": "Overview", + "profile": "Your profile", + "security": "Login & security", + "appearance": "Appearance", + "whatsapp": "WhatsApp", + "templates": "Templates", + "fields": "Fields & tags", + "deals": "Deals & currency", + "members": "Team members", + "api": "API keys" + }, + "groups": { + "account": "Account", + "workspace": "Workspace" + } } } diff --git a/src/components/settings/settings-overview.tsx b/src/components/settings/settings-overview.tsx index 2b61cf8..d7a7831 100644 --- a/src/components/settings/settings-overview.tsx +++ b/src/components/settings/settings-overview.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, type ReactNode } from 'react'; import { ChevronRight, Loader2 } from 'lucide-react'; +import { useTranslations } from 'next-intl'; import { createClient } from '@/lib/supabase/client'; import { useAuth } from '@/hooks/use-auth'; @@ -38,6 +39,8 @@ export function SettingsOverview({ const { user, profile, accountId, accountRole, defaultCurrency, canManageMembers } = useAuth(); const { mode, theme } = useTheme(); + const t = useTranslations('Settings.overview'); + const tSections = useTranslations('Settings.sections'); const [counts, setCounts] = useState(null); const [countsLoading, setCountsLoading] = useState(true); @@ -137,7 +140,7 @@ export function SettingsOverview({ }; }, [user?.id, accountId, canManageMembers]); - const displayName = profile?.full_name || profile?.email || 'Your account'; + const displayName = profile?.full_name || profile?.email || t('yourAccount'); const initial = (profile?.full_name || profile?.email || 'U').charAt(0).toUpperCase(); const roleMeta = accountRole ? ROLE_META[accountRole] : null; const RoleIcon = roleMeta?.icon; @@ -158,14 +161,14 @@ export function SettingsOverview({ section: 'whatsapp', loading: whatsappLoading, subtitle: !whatsapp?.configured ? ( - 'Not set up yet' + t('notSetup') ) : whatsapp.connected ? ( <> - Connected + {t('connected')} ) : ( <> - Needs reconnecting + {t('needsReconnecting')} ), }, @@ -174,12 +177,10 @@ export function SettingsOverview({ loading: countsLoading, subtitle: counts?.members == null - ? 'View team members' - : `${counts.members} member${counts.members === 1 ? '' : 's'}${ + ? t('viewTeamMembers') + : `${t('membersCount', { count: counts.members })}${ counts.pendingInvites - ? ` · ${counts.pendingInvites} pending invite${ - counts.pendingInvites === 1 ? '' : 's' - }` + ? ` · ${t('pendingInvites', { count: counts.pendingInvites })}` : '' }`, }, @@ -188,10 +189,10 @@ export function SettingsOverview({ loading: countsLoading, subtitle: counts?.templates == null - ? 'Manage message templates' - : `${counts.templates} template${counts.templates === 1 ? '' : 's'}${ + ? t('manageTemplates') + : `${t('templatesCount', { count: counts.templates })}${ counts.templatesPending - ? ` · ${counts.templatesPending} pending review` + ? ` · ${t('pendingReview', { count: counts.templatesPending })}` : '' }`, }, @@ -205,15 +206,15 @@ export function SettingsOverview({ loading: countsLoading, subtitle: counts?.tags == null && counts?.customFields == null - ? 'Tags and custom fields' - : `${counts?.tags ?? 0} tag${counts?.tags === 1 ? '' : 's'} · ${ - counts?.customFields ?? 0 - } custom field${counts?.customFields === 1 ? '' : 's'}`, + ? t('tagsAndFields') + : `${t('tagsCount', { count: counts?.tags ?? 0 })} · ${t('fieldsCount', { + count: counts?.customFields ?? 0, + })}`, }, { section: 'appearance', loading: false, - subtitle: `${cap(mode)} mode · ${themeName} accent`, + subtitle: t('appearance', { mode: cap(mode), theme: themeName }), }, ]; @@ -267,12 +268,12 @@ export function SettingsOverview({ - {meta.label} + {tSections(section)} {loading ? ( <> - Loading… + {t('loading')} ) : ( subtitle diff --git a/src/components/settings/settings-rail.tsx b/src/components/settings/settings-rail.tsx index 384ff46..1aa5041 100644 --- a/src/components/settings/settings-rail.tsx +++ b/src/components/settings/settings-rail.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useRef, type ReactNode } from 'react'; +import { useTranslations } from 'next-intl'; import { cn } from '@/lib/utils'; import { @@ -30,6 +31,7 @@ export function SettingsRail({ onSelect: (section: SettingsSection) => void; hints?: Partial>; }) { + const t = useTranslations('Settings'); const activeRef = useRef(null); // When horizontal (mobile), keep the active chip in view. On desktop @@ -64,7 +66,7 @@ export function SettingsRail({ > {label ? (
- {label} + {t(`groups.${group}`)}
) : null} {items.map((s) => { @@ -87,7 +89,7 @@ export function SettingsRail({ )} > - {meta.label} + {t(`sections.${s}`)} {hints?.[s] != null ? (
@@ -388,8 +390,8 @@ export function WhatsAppConfig() { return (
{/* Main config form */} @@ -415,12 +417,12 @@ export function WhatsAppConfig() { {resetting ? ( <> - Resetting... + {t('resetting')} ) : ( <> - Reset Configuration + {t('resetConfig')} )} @@ -438,14 +440,14 @@ export function WhatsAppConfig() { )} - {connectionStatus === 'connected' ? 'Credentials valid' : 'Not Connected'} + {connectionStatus === 'connected' ? t('credentialsValid') : t('notConnected')}
{connectionStatus === 'connected' - ? 'Your access token authenticates with Meta. See Registration status below for whether webhooks are actually wired.' + ? t('connectedDesc') : statusMessage || - 'Configure your Meta API credentials below to connect your WhatsApp Business account.'} + t('notConnectedDesc')} @@ -475,8 +477,8 @@ export function WhatsAppConfig() { } > {isRegistered - ? 'Registered — Meta will deliver events to wacrm' - : 'Not registered — Meta will not deliver events'} + ? t('registered') + : t('notRegistered')}
{isRegistered ? ( - <> - Subscribed since{' '} - {config.registered_at - ? new Date(config.registered_at).toLocaleString() - : 'unknown'} - . Click Verify with Meta if events - stop arriving. - + ) : lastRegistrationError ? ( <> - Last attempt failed with:{' '} + {t('lastAttemptFailed')} "{lastRegistrationError}" - . Enter (or correct) the 2-step PIN below and click - Save Configuration to retry. + . {t('retryHint')} ) : ( - <> - This number was saved before registration tracking - existed, or registration was skipped. Enter the - 2-step PIN below and click Save Configuration to - subscribe it. - + <>{t('noRegistrationHint')} )} {registrationProbe && (

- Diagnostic — last run: {' '} + {t('diagnosticLastRun')} - {registrationProbe.live ? 'live' : 'not live'} + {registrationProbe.live ? t('live') : t('notLive')}

    @@ -560,14 +557,14 @@ export function WhatsAppConfig() { {/* API Credentials */} - API Credentials + {t('apiCredentialsTitle')} - Enter your Meta WhatsApp Business API credentials. + {t('apiCredentialsDesc')}
    - +
    - +
    - +
    { setAccessToken(e.target.value); @@ -615,34 +612,34 @@ export function WhatsAppConfig() {
    {config && !tokenEdited && (

    - Token is hidden for security. Re-enter it to update configuration. + {t('tokenHidden')}

    )}
    - + setVerifyToken(e.target.value)} className="bg-muted border-border text-foreground placeholder:text-muted-foreground" />

    - A custom string you create. Must match the token you set in Meta webhook settings. + {t('webhookVerifyTokenHint')}

    setPin(e.target.value.replace(/\D/g, '').slice(0, 6)) @@ -650,20 +647,7 @@ export function WhatsAppConfig() { className="bg-muted border-border text-foreground placeholder:text-muted-foreground tracking-widest" />

    - Needed only to wire inbound messages - for a production number. Set it in{' '} - - Meta Business Manager → WhatsApp Accounts → Phone - Numbers → Two-step verification - - , then paste it here so wacrm can subscribe the number — - otherwise Meta routes inbound events to whichever app - last claimed it (the symptom that hits second numbers - under a shared WABA).{' '} - Meta test numbers have no - PIN and are pre-registered — leave this blank for them. - Leaving it blank also keeps an existing registration - untouched. +

    @@ -672,14 +656,14 @@ export function WhatsAppConfig() { {/* Webhook URL */} - Webhook Configuration + {t('webhookTitle')} - Use this URL as your webhook callback in the Meta App Dashboard. + {t('webhookDesc')}
    - +
    - Saving... + {t('saving')} ) : ( - 'Save Configuration' + t('saveConfig') )} @@ -743,12 +727,12 @@ export function WhatsAppConfig() { {resetting ? ( <> - Resetting... + {t('resetting')} ) : ( <> - Reset Configuration + {t('resetConfig')} )} @@ -760,9 +744,9 @@ export function WhatsAppConfig() {
    - Setup Instructions + {t('setupInstructions')} - Follow these steps to connect your WhatsApp Business API. + {t('setupInstructionsDesc')} @@ -771,15 +755,15 @@ export function WhatsAppConfig() { 1 - Create a Meta App + {t('step1')}
      -
    1. Go to developers.facebook.com
    2. -
    3. Click "My Apps" and then "Create App"
    4. -
    5. Select "Business" as the app type
    6. -
    7. Fill in app details and create
    8. +
    9. +
    10. {t('step1_2')}
    11. +
    12. {t('step1_3')}
    13. +
    14. {t('step1_4')}
    @@ -788,14 +772,14 @@ export function WhatsAppConfig() { 2 - Add WhatsApp Product + {t('step2')}
      -
    1. In your app dashboard, click "Add Product"
    2. -
    3. Find "WhatsApp" and click "Set Up"
    4. -
    5. Follow the setup wizard to link your business
    6. +
    7. {t('step2_1')}
    8. +
    9. {t('step2_2')}
    10. +
    11. {t('step2_3')}
    @@ -804,15 +788,15 @@ export function WhatsAppConfig() { 3 - Get API Credentials + {t('step3')}
      -
    1. Go to WhatsApp > API Setup
    2. -
    3. Copy your Phone Number ID
    4. -
    5. Copy your WhatsApp Business Account ID
    6. -
    7. Generate a Permanent Access Token from Business Settings > System Users
    8. +
    9. {t('step3_1')}
    10. +
    11. +
    12. +
    @@ -821,16 +805,16 @@ export function WhatsAppConfig() { 4 - Configure Webhooks + {t('step4')}
      -
    1. Go to WhatsApp > Configuration
    2. -
    3. Click "Edit" on the Webhook section
    4. -
    5. Paste the Webhook Callback URL from above
    6. -
    7. Enter the same Verify Token you set here
    8. -
    9. Subscribe to "messages" webhook field
    10. +
    11. {t('step4_1')}
    12. +
    13. {t('step4_2')}
    14. +
    15. +
    16. +
    17. {t('step4_5')}
    @@ -844,7 +828,7 @@ export function WhatsAppConfig() { className="inline-flex items-center gap-1.5 text-sm text-primary hover:text-primary/80 transition-colors" > - Meta WhatsApp API Documentation + {t('metaDocs')}