feat(i18n): Comprehensive internationalization of frontend components and pages
Translate all hardcoded English strings to use i18n translation keys: Components: - TransactionDetailModal: payment details, refunds, technical info - ConnectOnboarding/ConnectOnboardingEmbed: Stripe Connect setup - StripeApiKeysForm: API key management - DomainPurchase: domain registration flow - Sidebar: navigation labels - Schedule/Sidebar, PendingSidebar: scheduler UI - MasqueradeBanner: masquerade status - Dashboard widgets: metrics, capacity, customers, tickets - Marketing: PricingTable, PluginShowcase, BenefitsSection - ConfirmationModal, ServiceList: common UI Pages: - Staff: invitation flow, role management - Customers: form placeholders - Payments: transactions, payouts, billing - BookingSettings: URL and redirect configuration - TrialExpired: upgrade prompts and features - PlatformSettings, PlatformBusinesses: admin UI - HelpApiDocs: API documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { Clock, GripVertical } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface PendingAppointment {
|
||||
id: number;
|
||||
@@ -15,6 +16,7 @@ interface PendingItemProps {
|
||||
}
|
||||
|
||||
const PendingItem: React.FC<PendingItemProps> = ({ appointment }) => {
|
||||
const { t } = useTranslation();
|
||||
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
||||
id: `pending-${appointment.id}`,
|
||||
data: {
|
||||
@@ -43,7 +45,7 @@ const PendingItem: React.FC<PendingItemProps> = ({ appointment }) => {
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-1 text-xs text-gray-400">
|
||||
<Clock size={10} />
|
||||
<span>{appointment.durationMinutes} min</span>
|
||||
<span>{appointment.durationMinutes} {t('scheduler.min')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -54,16 +56,18 @@ interface PendingSidebarProps {
|
||||
}
|
||||
|
||||
const PendingSidebar: React.FC<PendingSidebarProps> = ({ appointments }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="w-64 bg-gray-50 border-r border-gray-200 flex flex-col h-full shrink-0">
|
||||
<div className="p-4 border-b border-gray-200 bg-gray-100">
|
||||
<h3 className="text-xs font-bold text-gray-500 uppercase tracking-wider flex items-center gap-2">
|
||||
<Clock size={12} /> Pending Requests ({appointments.length})
|
||||
<Clock size={12} /> {t('scheduler.pendingRequests')} ({appointments.length})
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-4 overflow-y-auto flex-1">
|
||||
{appointments.length === 0 ? (
|
||||
<div className="text-xs text-gray-400 italic text-center py-4">No pending requests</div>
|
||||
<div className="text-xs text-gray-400 italic text-center py-4">{t('scheduler.noPendingRequests')}</div>
|
||||
) : (
|
||||
appointments.map(apt => (
|
||||
<PendingItem key={apt.id} appointment={apt} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { Clock, GripVertical, Trash2 } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface PendingAppointment {
|
||||
id: number;
|
||||
@@ -22,6 +23,7 @@ interface PendingItemProps {
|
||||
}
|
||||
|
||||
const PendingItem: React.FC<PendingItemProps> = ({ appointment }) => {
|
||||
const { t } = useTranslation();
|
||||
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
||||
id: `pending-${appointment.id}`,
|
||||
data: {
|
||||
@@ -50,7 +52,7 @@ const PendingItem: React.FC<PendingItemProps> = ({ appointment }) => {
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
<Clock size={10} />
|
||||
<span>{appointment.durationMinutes} min</span>
|
||||
<span>{appointment.durationMinutes} {t('scheduler.min')}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -63,11 +65,13 @@ interface SidebarProps {
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ resourceLayouts, pendingAppointments, scrollRef }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 shrink-0 shadow-lg z-20 transition-colors duration-200" style={{ width: 250 }}>
|
||||
{/* Resources Header */}
|
||||
<div className="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 flex items-center px-4 font-semibold text-gray-500 dark:text-gray-400 text-xs uppercase tracking-wider shrink-0 transition-colors duration-200" style={{ height: 48 }}>
|
||||
Resources
|
||||
{t('scheduler.resources')}
|
||||
</div>
|
||||
|
||||
{/* Resources List (Synced Scroll) */}
|
||||
@@ -89,10 +93,10 @@ const Sidebar: React.FC<SidebarProps> = ({ resourceLayouts, pendingAppointments,
|
||||
<div>
|
||||
<p className="font-medium text-sm text-gray-900 dark:text-white">{layout.resourceName}</p>
|
||||
<p className="text-xs text-gray-400 dark:text-gray-500 capitalize flex items-center gap-1">
|
||||
Resource
|
||||
{t('scheduler.resource')}
|
||||
{layout.laneCount > 1 && (
|
||||
<span className="text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/50 px-1 rounded text-[10px]">
|
||||
{layout.laneCount} lanes
|
||||
{layout.laneCount} {t('scheduler.lanes')}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@@ -106,11 +110,11 @@ const Sidebar: React.FC<SidebarProps> = ({ resourceLayouts, pendingAppointments,
|
||||
{/* Pending Requests (Fixed Bottom) */}
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-4 h-80 flex flex-col transition-colors duration-200">
|
||||
<h3 className="text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3 flex items-center gap-2 shrink-0">
|
||||
<Clock size={12} /> Pending Requests ({pendingAppointments.length})
|
||||
<Clock size={12} /> {t('scheduler.pendingRequests')} ({pendingAppointments.length})
|
||||
</h3>
|
||||
<div className="space-y-2 overflow-y-auto flex-1 mb-2">
|
||||
{pendingAppointments.length === 0 ? (
|
||||
<div className="text-xs text-gray-400 italic text-center py-4">No pending requests</div>
|
||||
<div className="text-xs text-gray-400 italic text-center py-4">{t('scheduler.noPendingRequests')}</div>
|
||||
) : (
|
||||
pendingAppointments.map(apt => (
|
||||
<PendingItem key={apt.id} appointment={apt} />
|
||||
@@ -122,7 +126,7 @@ const Sidebar: React.FC<SidebarProps> = ({ resourceLayouts, pendingAppointments,
|
||||
<div className="shrink-0 mt-2 border-t border-gray-200 dark:border-gray-700 pt-2 opacity-50">
|
||||
<div className="flex items-center justify-center gap-2 p-3 rounded-lg border-2 border-dashed border-gray-200 dark:border-gray-700 bg-transparent text-gray-400">
|
||||
<Trash2 size={16} />
|
||||
<span className="text-xs font-medium">Drop here to archive</span>
|
||||
<span className="text-xs font-medium">{t('scheduler.dropToArchive')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user