diff --git a/TRANSLATIONS.md b/TRANSLATIONS.md new file mode 100644 index 0000000..42f36e9 --- /dev/null +++ b/TRANSLATIONS.md @@ -0,0 +1,521 @@ +# Translation System Documentation + +## Overview + +The WorkForce Pro platform now includes a comprehensive internationalization (i18n) system that loads translations from JSON files. This enables the application to support multiple languages seamlessly, with user preferences persisted across sessions. + +## Features + +- ✅ **JSON-based translations** - All translations stored in structured JSON files +- ✅ **Multiple languages** - Support for English, Spanish, and French (easily extensible) +- ✅ **Persistent preferences** - Language selection saved using KV store +- ✅ **Parameter interpolation** - Dynamic values in translation strings +- ✅ **Nested keys** - Organized translation hierarchy with dot notation access +- ✅ **Automatic fallback** - Falls back to English if translation missing +- ✅ **Type-safe** - TypeScript support throughout +- ✅ **Lazy loading** - Translations loaded on-demand +- ✅ **Easy integration** - Simple React hook API + +## File Structure + +``` +/src + /data + /translations + en.json # English translations + es.json # Spanish translations + fr.json # French translations + /hooks + use-translation.ts # Translation hook + /components + LanguageSwitcher.tsx # UI component for language selection + TranslationDemo.tsx # Demonstration component +``` + +## Translation Files + +Translation files are located in `/src/data/translations/` and follow a consistent structure: + +### Structure + +```json +{ + "app": { + "title": "WorkForce Pro", + "subtitle": "Enhanced Back Office Platform" + }, + "navigation": { + "dashboard": "Dashboard", + "timesheets": "Timesheets", + ... + }, + "common": { + "search": "Search", + "filter": "Filter", + ... + }, + "validation": { + "required": "This field is required", + "minLength": "Minimum length is {{min}} characters" + } +} +``` + +### Available Languages + +- **English** (`en`) - Default language +- **Spanish** (`es`) - Español +- **French** (`fr`) - Français + +## Usage + +### Basic Usage + +```typescript +import { useTranslation } from '@/hooks/use-translation' + +function MyComponent() { + const { t } = useTranslation() + + return ( +
+

{t('navigation.dashboard')}

+ +

{t('timesheets.status.approved')}

+
+ ) +} +``` + +### With Parameters + +Translations support parameter interpolation using `{{variable}}` syntax: + +```typescript +const { t } = useTranslation() + +// Translation: "{{count}} unread" +const message = t('notifications.unreadCount', { count: 5 }) +// Result: "5 unread" + +// Translation: "Minimum length is {{min}} characters" +const validation = t('validation.minLength', { min: 8 }) +// Result: "Minimum length is 8 characters" +``` + +### Changing Language + +```typescript +import { useTranslation } from '@/hooks/use-translation' + +function LanguageSelector() { + const { locale, changeLocale, availableLocales } = useTranslation() + + return ( +
+

Current: {locale}

+ + + +
+ ) +} +``` + +### Getting Current Locale (Lightweight) + +If you only need the current locale without translation functionality: + +```typescript +import { useLocale } from '@/hooks/use-translation' + +function MyComponent() { + const locale = useLocale() + + return Current language: {locale} +} +``` + +### Changing Locale (Without Translation) + +If you only need to change the locale: + +```typescript +import { useChangeLocale } from '@/hooks/use-translation' + +function LanguageButton() { + const changeLocale = useChangeLocale() + + return +} +``` + +## Hook API Reference + +### `useTranslation()` + +Main hook for translation functionality. + +**Returns:** +```typescript +{ + t: (key: string, params?: Record) => string, + locale: 'en' | 'es' | 'fr', + changeLocale: (newLocale: 'en' | 'es' | 'fr') => void, + availableLocales: ['en', 'es', 'fr'], + isLoading: boolean, + error: Error | null +} +``` + +**Example:** +```typescript +const { t, locale, changeLocale, isLoading } = useTranslation() + +if (isLoading) { + return
Loading translations...
+} + +return ( +
+

{t('app.title')}

+ +
+) +``` + +### `useLocale()` + +Lightweight hook that only returns the current locale. + +**Returns:** `'en' | 'es' | 'fr'` + +### `useChangeLocale()` + +Hook that only provides locale change functionality. + +**Returns:** `(newLocale: 'en' | 'es' | 'fr') => void` + +## Components + +### LanguageSwitcher + +Dropdown component for selecting language, integrated in the app header. + +```typescript +import { LanguageSwitcher } from '@/components/LanguageSwitcher' + + +``` + +**Features:** +- Displays current language +- Dropdown with all available languages +- Visual indicator for selected language +- Automatically updates on selection + +### TranslationDemo + +Comprehensive demonstration component showing all translation features. Access via: +- Navigation: Tools & Utilities → Translations +- Direct view: `translation-demo` + +## Translation Keys Reference + +### App Level +- `app.title` - Application title +- `app.subtitle` - Application subtitle + +### Navigation +- `navigation.dashboard` - Dashboard +- `navigation.timesheets` - Timesheets +- `navigation.billing` - Billing +- `navigation.payroll` - Payroll +- `navigation.compliance` - Compliance +- `navigation.expenses` - Expenses +- `navigation.reports` - Reports +- Plus 20+ more navigation items + +### Common Actions +- `common.search` - Search +- `common.filter` - Filter +- `common.export` - Export +- `common.save` - Save +- `common.cancel` - Cancel +- `common.submit` - Submit +- `common.delete` - Delete +- Plus 30+ more common terms + +### Status Messages +- `common.success` - Success +- `common.error` - Error +- `common.warning` - Warning +- `common.info` - Information +- `common.loading` - Loading... + +### Timesheets +- `timesheets.title` - Timesheets title +- `timesheets.addTimesheet` - Add timesheet action +- `timesheets.status.draft` - Draft status +- `timesheets.status.submitted` - Submitted status +- `timesheets.status.approved` - Approved status +- `timesheets.status.rejected` - Rejected status +- Plus 20+ more timesheet terms + +### Billing +- `billing.createInvoice` - Create invoice +- `billing.invoiceNumber` - Invoice number +- `billing.status.paid` - Paid status +- `billing.status.overdue` - Overdue status +- Plus 20+ more billing terms + +### Payroll +- `payroll.processPayroll` - Process payroll +- `payroll.grossPay` - Gross pay +- `payroll.netPay` - Net pay +- Plus 15+ more payroll terms + +### Validation +- `validation.required` - Required field message +- `validation.invalidEmail` - Invalid email message +- `validation.minLength` - Minimum length (with {{min}} param) +- `validation.maxLength` - Maximum length (with {{max}} param) +- Plus more validation messages + +### Errors +- `errors.generic` - Generic error message +- `errors.network` - Network error message +- `errors.notFound` - Not found message +- `errors.unauthorized` - Unauthorized message + +## Adding New Languages + +1. Create a new JSON file in `/src/data/translations/`: + ```bash + # Example: German + /src/data/translations/de.json + ``` + +2. Copy the structure from `en.json` and translate all values + +3. Update the `use-translation.ts` hook: + ```typescript + type Locale = 'en' | 'es' | 'fr' | 'de' + const AVAILABLE_LOCALES: Locale[] = ['en', 'es', 'fr', 'de'] + ``` + +4. Update `LanguageSwitcher.tsx`: + ```typescript + const LOCALE_NAMES: Record = { + en: 'English', + es: 'Español', + fr: 'Français', + de: 'Deutsch' + } + ``` + +## Adding New Translation Keys + +1. Add the key to all language files in the appropriate section: + + **en.json:** + ```json + { + "mySection": { + "myKey": "My English text" + } + } + ``` + + **es.json:** + ```json + { + "mySection": { + "myKey": "Mi texto en español" + } + } + ``` + + **fr.json:** + ```json + { + "mySection": { + "myKey": "Mon texte en français" + } + } + ``` + +2. Use in your component: + ```typescript + const { t } = useTranslation() + const text = t('mySection.myKey') + ``` + +## Best Practices + +### 1. Organize by Feature +Group related translations together: +```json +{ + "timesheets": { + "title": "...", + "addTimesheet": "...", + "status": { + "draft": "...", + "approved": "..." + } + } +} +``` + +### 2. Use Descriptive Keys +```typescript +// Good +t('timesheets.status.approved') +t('billing.invoice.overdue') + +// Bad +t('text1') +t('message') +``` + +### 3. Keep Parameters Simple +```json +{ + "greeting": "Hello, {{name}}!", + "itemCount": "{{count}} items selected" +} +``` + +### 4. Provide Context in Comments +```json +{ + "common": { + // Used on all save buttons + "save": "Save", + // Used for destructive actions + "delete": "Delete" + } +} +``` + +### 5. Handle Pluralization +```json +{ + "notifications": { + "unreadCount": "{{count}} unread" + } +} +``` + +Then handle in code: +```typescript +const count = 1 +const text = count === 1 + ? t('notifications.unreadSingular', { count }) + : t('notifications.unreadCount', { count }) +``` + +### 6. Test All Languages +Always verify translations in all supported languages before deployment. + +## Performance Considerations + +- **Lazy Loading**: Translations are loaded on-demand when language changes +- **Caching**: Once loaded, translations are cached in memory +- **Persistent Storage**: Selected language is stored in KV store, not re-fetched +- **Minimal Re-renders**: Hook uses React hooks efficiently to minimize re-renders + +## Troubleshooting + +### Translation Not Showing + +1. Check the key exists in the translation file +2. Verify the file is valid JSON +3. Check browser console for errors +4. Ensure the key path is correct (case-sensitive) + +### Language Not Changing + +1. Verify the locale is in `AVAILABLE_LOCALES` +2. Check browser console for loading errors +3. Clear KV store if needed: `spark.kv.delete('app-locale')` + +### Missing Translation + +If a translation key is missing, the system will: +1. Return the key itself as a fallback +2. Log a warning to console (in development) +3. Attempt to load from default language (English) + +## Examples + +### Full Component Example + +```typescript +import { useTranslation } from '@/hooks/use-translation' +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' + +export function TimesheetApproval({ timesheet }) { + const { t } = useTranslation() + + const handleApprove = () => { + // Approval logic + toast.success(t('timesheets.status.approved')) + } + + const handleReject = () => { + // Rejection logic + toast.error(t('timesheets.status.rejected')) + } + + return ( + +

{t('timesheets.title')}

+

{t('timesheets.hoursWorked')}: {timesheet.hours}

+
+ + +
+
+ ) +} +``` + +### Form Validation Example + +```typescript +import { useTranslation } from '@/hooks/use-translation' +import { useForm } from 'react-hook-form' + +export function MyForm() { + const { t } = useTranslation() + const { register, formState: { errors } } = useForm() + + return ( +
+ + {errors.email && {errors.email.message}} +
+ ) +} +``` + +## Summary + +The translation system provides a robust, scalable solution for internationalization in the WorkForce Pro platform. With JSON-based storage, easy parameter interpolation, and persistent user preferences, it enables seamless multi-language support while maintaining clean, maintainable code. + +For a live demonstration of all features, navigate to **Tools & Utilities → Translations** in the application. diff --git a/src/App.tsx b/src/App.tsx index bbda259..458b72d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,8 +7,9 @@ import { useViewPreload } from '@/hooks/use-view-preload' import { Sidebar } from '@/components/navigation' import { NotificationCenter } from '@/components/NotificationCenter' import { ViewRouter } from '@/components/ViewRouter' +import { LanguageSwitcher } from '@/components/LanguageSwitcher' -export type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' | 'data-admin' +export type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' | 'data-admin' | 'translation-demo' function App() { useSampleData() @@ -60,13 +61,16 @@ function App() {
- +
+ + +
diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..564ba23 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,42 @@ +import { Check, Globe } from '@phosphor-icons/react' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { useTranslation } from '@/hooks/use-translation' + +const LOCALE_NAMES: Record = { + en: 'English', + es: 'Español', + fr: 'Français' +} + +export function LanguageSwitcher() { + const { locale, changeLocale, availableLocales } = useTranslation() + + return ( + + + + + + {availableLocales.map((loc) => ( + changeLocale(loc)} + className="flex items-center justify-between cursor-pointer" + > + {LOCALE_NAMES[loc]} + {locale === loc && } + + ))} + + + ) +} diff --git a/src/components/TranslationDemo.tsx b/src/components/TranslationDemo.tsx new file mode 100644 index 0000000..1c81adf --- /dev/null +++ b/src/components/TranslationDemo.tsx @@ -0,0 +1,314 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { useTranslation } from '@/hooks/use-translation' +import { + Globe, + CheckCircle, + Warning, + XCircle, + Info, + Translate +} from '@phosphor-icons/react' +import { Separator } from '@/components/ui/separator' + +export function TranslationDemo() { + const { t, locale, changeLocale, availableLocales, isLoading, error } = useTranslation() + + if (isLoading) { + return ( +
+
+
+

{t('common.loading')}

+
+
+ ) + } + + if (error) { + return ( +
+ + +
+ + {t('common.error')} +
+
+ +

{t('errors.loadingFailed')}

+
+
+
+ ) + } + + return ( +
+
+

+ + Translation System Demo +

+

+ Comprehensive i18n with JSON-based translations +

+
+ + + + + + Current Language + + + Active locale: {locale} + + + +
+ {availableLocales.map((loc) => ( + + ))} +
+
+
+ +
+ + + Navigation Translations + Menu items in current language + + +
+ + + + + + + +
+
+
+ + + + Common Actions + Frequently used terms + + +
+ + + + + + + +
+
+
+ + + + Status Messages + System statuses and alerts + + +
+
+ + {t('common.success')} +
+
+ + {t('common.warning')} +
+
+ + {t('common.error')} +
+
+ + {t('common.info')} +
+
+
+
+ + + + Timesheet Statuses + Document lifecycle states + + +
+
+ Draft + {t('timesheets.status.draft')} +
+
+ Submitted + {t('timesheets.status.submitted')} +
+
+ Approved + {t('timesheets.status.approved')} +
+
+ Rejected + {t('timesheets.status.rejected')} +
+
+ Processed + {t('timesheets.status.processed')} +
+
+
+
+
+ + + + Parameterized Translations + Dynamic values in translations + + +
+

Unread count example:

+
+

+ {t('notifications.unreadCount', { count: 5 })} +

+

+ {t('notifications.unreadCount', { count: 12 })} +

+

+ {t('notifications.unreadCount', { count: 0 })} +

+
+
+ + + +
+

Change from period example:

+
+

+ {t('metrics.changeFromLastPeriod', { change: '+15' })} +

+

+ {t('metrics.changeFromLastPeriod', { change: '-8' })} +

+

+ {t('metrics.changeFromLastPeriod', { change: '+3.5' })} +

+
+
+ + + +
+

Validation messages:

+
+

+ {t('validation.minLength', { min: 8 })} +

+

+ {t('validation.maxLength', { max: 50 })} +

+

+ {t('validation.minValue', { min: 0 })} +

+
+
+
+
+ + + + + + Usage Instructions + + + +
+

In any component:

+
+              {`import { useTranslation } from '@/hooks/use-translation'
+
+function MyComponent() {
+  const { t } = useTranslation()
+  
+  return 

{t('navigation.dashboard')}

+}`}
+
+
+ +
+

With parameters:

+
+              {`const unreadText = t('notifications.unreadCount', { count: 5 })
+const changeText = t('metrics.changeFromLastPeriod', { change: '+12' })`}
+            
+
+ +
+

Change language:

+
+              {`const { changeLocale } = useTranslation()
+changeLocale('es') // Switch to Spanish
+changeLocale('fr') // Switch to French`}
+            
+
+ + + +
+

Translation files location:

+
    +
  • /src/data/translations/en.json - English
  • +
  • /src/data/translations/es.json - Spanish
  • +
  • /src/data/translations/fr.json - French
  • +
+
+ + + +
+

Features:

+
    +
  • ✓ Persistent language preference (saved to KV store)
  • +
  • ✓ Automatic fallback to English if translation missing
  • +
  • ✓ Parameter interpolation with {`{{variable}}`} syntax
  • +
  • ✓ Nested translation keys with dot notation
  • +
  • ✓ Language switcher in header
  • +
+
+
+
+
+ ) +} + +function TranslationItem({ label, translationKey }: { label: string; translationKey: string }) { + const { t } = useTranslation() + + return ( +
+ {label} + {t(translationKey)} +
+ ) +} diff --git a/src/components/ViewRouter.tsx b/src/components/ViewRouter.tsx index eb6ddd5..d85b7d5 100644 --- a/src/components/ViewRouter.tsx +++ b/src/components/ViewRouter.tsx @@ -40,6 +40,7 @@ const RoadmapView = lazy(() => import('@/components/roadmap-view').then(m => ({ const ComponentShowcase = lazy(() => import('@/components/ComponentShowcase').then(m => ({ default: m.ComponentShowcase }))) const BusinessLogicDemo = lazy(() => import('@/components/BusinessLogicDemo').then(m => ({ default: m.BusinessLogicDemo }))) const DataAdminView = lazy(() => import('@/components/views/data-admin-view').then(m => ({ default: m.DataAdminView }))) +const TranslationDemo = lazy(() => import('@/components/TranslationDemo').then(m => ({ default: m.TranslationDemo }))) interface ViewRouterProps { currentView: View @@ -250,6 +251,9 @@ export function ViewRouter({ case 'data-admin': return + case 'translation-demo': + return + default: return } diff --git a/src/components/nav/nav-sections.tsx b/src/components/nav/nav-sections.tsx index 380bd26..d63c33f 100644 --- a/src/components/nav/nav-sections.tsx +++ b/src/components/nav/nav-sections.tsx @@ -15,7 +15,8 @@ import { UploadSimple, FileText, UserPlus, - CalendarBlank + CalendarBlank, + Translate } from '@phosphor-icons/react' import { NavItem } from './NavItem' import { NavGroup } from './NavGroup' @@ -225,6 +226,13 @@ export function ToolsNav({ currentView, setCurrentView, expandedGroups, toggleGr onClick={() => setCurrentView('audit-trail')} view="audit-trail" /> + } + label="Translations" + active={currentView === 'translation-demo'} + onClick={() => setCurrentView('translation-demo')} + view="translation-demo" + /> ) } diff --git a/src/data/translations/en.json b/src/data/translations/en.json new file mode 100644 index 0000000..82e6c6e --- /dev/null +++ b/src/data/translations/en.json @@ -0,0 +1,354 @@ +{ + "app": { + "title": "WorkForce Pro", + "subtitle": "Enhanced Back Office Platform" + }, + "navigation": { + "dashboard": "Dashboard", + "timesheets": "Timesheets", + "billing": "Billing", + "payroll": "Payroll", + "compliance": "Compliance", + "expenses": "Expenses", + "reports": "Reports", + "settings": "Settings", + "currency": "Currency", + "emailTemplates": "Email Templates", + "invoiceTemplates": "Invoice Templates", + "qrScanner": "QR Scanner", + "missingTimesheets": "Missing Timesheets", + "purchaseOrders": "Purchase Orders", + "onboarding": "Onboarding", + "auditTrail": "Audit Trail", + "notificationRules": "Notification Rules", + "batchImport": "Batch Import", + "rateTemplates": "Rate Templates", + "customReports": "Custom Reports", + "holidayPay": "Holiday Pay", + "contractValidation": "Contract Validation", + "shiftPatterns": "Shift Patterns", + "queryGuide": "Query Guide", + "componentShowcase": "Component Showcase", + "businessLogicDemo": "Business Logic Demo", + "dataAdmin": "Data Admin", + "roadmap": "Roadmap" + }, + "common": { + "search": "Search", + "filter": "Filter", + "export": "Export", + "import": "Import", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "save": "Save", + "cancel": "Cancel", + "submit": "Submit", + "confirm": "Confirm", + "close": "Close", + "back": "Back", + "next": "Next", + "previous": "Previous", + "loading": "Loading...", + "noData": "No data available", + "error": "Error", + "success": "Success", + "warning": "Warning", + "info": "Information", + "actions": "Actions", + "status": "Status", + "date": "Date", + "time": "Time", + "total": "Total", + "subtotal": "Subtotal", + "view": "View", + "download": "Download", + "upload": "Upload", + "refresh": "Refresh", + "clear": "Clear", + "apply": "Apply", + "reset": "Reset", + "selectAll": "Select All", + "deselectAll": "Deselect All", + "yes": "Yes", + "no": "No", + "or": "or", + "and": "and", + "of": "of", + "to": "to", + "from": "from" + }, + "dashboard": { + "title": "Dashboard", + "subtitle": "Overview of key metrics and activities", + "welcomeBack": "Welcome back", + "recentActivity": "Recent Activity", + "quickActions": "Quick Actions", + "notifications": "Notifications", + "pendingApprovals": "Pending Approvals", + "upcomingDeadlines": "Upcoming Deadlines" + }, + "timesheets": { + "title": "Timesheets", + "subtitle": "Manage and approve timesheet entries", + "addTimesheet": "Add Timesheet", + "editTimesheet": "Edit Timesheet", + "approveTimesheet": "Approve Timesheet", + "rejectTimesheet": "Reject Timesheet", + "submitTimesheet": "Submit Timesheet", + "worker": "Worker", + "client": "Client", + "placement": "Placement", + "weekEnding": "Week Ending", + "hours": "Hours", + "rate": "Rate", + "amount": "Amount", + "status": { + "draft": "Draft", + "submitted": "Submitted", + "approved": "Approved", + "rejected": "Rejected", + "processed": "Processed" + }, + "hoursWorked": "Hours Worked", + "overtimeHours": "Overtime Hours", + "totalHours": "Total Hours", + "regularHours": "Regular Hours", + "nightShift": "Night Shift", + "weekendShift": "Weekend Shift", + "adjustments": "Adjustments", + "comments": "Comments" + }, + "billing": { + "title": "Billing", + "subtitle": "Manage invoices and billing", + "createInvoice": "Create Invoice", + "invoiceNumber": "Invoice Number", + "invoiceDate": "Invoice Date", + "dueDate": "Due Date", + "client": "Client", + "amount": "Amount", + "paid": "Paid", + "unpaid": "Unpaid", + "overdue": "Overdue", + "partiallyPaid": "Partially Paid", + "status": { + "draft": "Draft", + "sent": "Sent", + "paid": "Paid", + "overdue": "Overdue", + "cancelled": "Cancelled" + }, + "generateInvoice": "Generate Invoice", + "sendInvoice": "Send Invoice", + "downloadInvoice": "Download Invoice", + "creditNote": "Credit Note", + "purchaseOrder": "Purchase Order", + "lineItems": "Line Items", + "subtotal": "Subtotal", + "tax": "Tax", + "total": "Total", + "paymentTerms": "Payment Terms", + "notes": "Notes" + }, + "payroll": { + "title": "Payroll", + "subtitle": "Process payroll runs", + "createPayroll": "Create Payroll", + "processPayroll": "Process Payroll", + "payrollRun": "Payroll Run", + "payPeriod": "Pay Period", + "payDate": "Pay Date", + "totalPay": "Total Pay", + "grossPay": "Gross Pay", + "netPay": "Net Pay", + "deductions": "Deductions", + "taxes": "Taxes", + "status": { + "draft": "Draft", + "processing": "Processing", + "approved": "Approved", + "paid": "Paid" + }, + "employeeCount": "Employee Count", + "oneClickPayroll": "One-Click Payroll", + "holidayPay": "Holiday Pay", + "statutoryPay": "Statutory Pay" + }, + "compliance": { + "title": "Compliance", + "subtitle": "Manage compliance documents and checks", + "document": "Document", + "expiryDate": "Expiry Date", + "status": { + "valid": "Valid", + "expiring": "Expiring Soon", + "expired": "Expired", + "missing": "Missing" + }, + "uploadDocument": "Upload Document", + "reviewDocument": "Review Document", + "approveDocument": "Approve Document", + "rightToWork": "Right to Work", + "dbsCheck": "DBS Check", + "qualification": "Qualification", + "insurance": "Insurance", + "contract": "Contract" + }, + "expenses": { + "title": "Expenses", + "subtitle": "Manage expense claims", + "addExpense": "Add Expense", + "expenseType": "Expense Type", + "amount": "Amount", + "date": "Date", + "receipt": "Receipt", + "status": { + "draft": "Draft", + "submitted": "Submitted", + "approved": "Approved", + "rejected": "Rejected", + "paid": "Paid" + }, + "approveExpense": "Approve Expense", + "rejectExpense": "Reject Expense", + "mileage": "Mileage", + "accommodation": "Accommodation", + "meals": "Meals", + "travel": "Travel", + "other": "Other" + }, + "reports": { + "title": "Reports", + "subtitle": "Generate and view reports", + "customReport": "Custom Report", + "runReport": "Run Report", + "scheduleReport": "Schedule Report", + "exportReport": "Export Report", + "reportType": "Report Type", + "dateRange": "Date Range", + "filters": "Filters", + "financialReport": "Financial Report", + "operationalReport": "Operational Report", + "complianceReport": "Compliance Report", + "marginAnalysis": "Margin Analysis", + "missingTimesheets": "Missing Timesheets", + "auditTrail": "Audit Trail" + }, + "notifications": { + "title": "Notifications", + "markAsRead": "Mark as Read", + "markAllAsRead": "Mark All as Read", + "delete": "Delete", + "noNotifications": "No notifications", + "unreadCount": "{{count}} unread", + "types": { + "info": "Information", + "success": "Success", + "warning": "Warning", + "error": "Error" + } + }, + "filters": { + "title": "Filters", + "apply": "Apply Filters", + "clear": "Clear Filters", + "status": "Status", + "dateRange": "Date Range", + "client": "Client", + "worker": "Worker", + "type": "Type", + "advanced": "Advanced Filters", + "queryLanguage": "Query Language" + }, + "validation": { + "required": "This field is required", + "invalidEmail": "Invalid email address", + "invalidDate": "Invalid date", + "invalidNumber": "Invalid number", + "minLength": "Minimum length is {{min}} characters", + "maxLength": "Maximum length is {{max}} characters", + "minValue": "Minimum value is {{min}}", + "maxValue": "Maximum value is {{max}}" + }, + "errors": { + "generic": "An error occurred. Please try again.", + "network": "Network error. Please check your connection.", + "notFound": "Resource not found", + "unauthorized": "You are not authorized to perform this action", + "serverError": "Server error. Please try again later.", + "loadingFailed": "Failed to load data" + }, + "currency": { + "title": "Currency Management", + "addCurrency": "Add Currency", + "code": "Currency Code", + "symbol": "Symbol", + "exchangeRate": "Exchange Rate", + "baseCurrency": "Base Currency" + }, + "templates": { + "email": "Email Templates", + "invoice": "Invoice Templates", + "rate": "Rate Templates", + "createTemplate": "Create Template", + "editTemplate": "Edit Template", + "deleteTemplate": "Delete Template" + }, + "onboarding": { + "title": "Onboarding", + "workflow": "Onboarding Workflow", + "step": "Step", + "complete": "Complete", + "pending": "Pending", + "assignTasks": "Assign Tasks" + }, + "auditTrail": { + "title": "Audit Trail", + "action": "Action", + "user": "User", + "timestamp": "Timestamp", + "details": "Details", + "view": "View Details" + }, + "batchImport": { + "title": "Batch Import", + "uploadFile": "Upload File", + "selectFile": "Select File", + "import": "Import", + "preview": "Preview", + "mapping": "Field Mapping", + "validate": "Validate" + }, + "shiftPatterns": { + "title": "Shift Patterns", + "createPattern": "Create Pattern", + "patternName": "Pattern Name", + "dayShift": "Day Shift", + "nightShift": "Night Shift", + "weekendShift": "Weekend Shift", + "rotatingPattern": "Rotating Pattern" + }, + "metrics": { + "totalRevenue": "Total Revenue", + "grossMargin": "Gross Margin", + "activeWorkers": "Active Workers", + "pendingTimesheets": "Pending Timesheets", + "unpaidInvoices": "Unpaid Invoices", + "complianceRate": "Compliance Rate", + "changeFromLastPeriod": "{{change}}% from last period" + }, + "statuses": { + "active": "Active", + "inactive": "Inactive", + "pending": "Pending", + "approved": "Approved", + "rejected": "Rejected", + "draft": "Draft", + "submitted": "Submitted", + "processed": "Processed", + "paid": "Paid", + "unpaid": "Unpaid", + "overdue": "Overdue" + } +} diff --git a/src/data/translations/es.json b/src/data/translations/es.json new file mode 100644 index 0000000..1701ee6 --- /dev/null +++ b/src/data/translations/es.json @@ -0,0 +1,354 @@ +{ + "app": { + "title": "WorkForce Pro", + "subtitle": "Plataforma de Back Office Mejorada" + }, + "navigation": { + "dashboard": "Panel de Control", + "timesheets": "Hojas de Tiempo", + "billing": "Facturación", + "payroll": "Nómina", + "compliance": "Cumplimiento", + "expenses": "Gastos", + "reports": "Informes", + "settings": "Configuración", + "currency": "Moneda", + "emailTemplates": "Plantillas de Correo", + "invoiceTemplates": "Plantillas de Factura", + "qrScanner": "Escáner QR", + "missingTimesheets": "Hojas de Tiempo Faltantes", + "purchaseOrders": "Órdenes de Compra", + "onboarding": "Incorporación", + "auditTrail": "Registro de Auditoría", + "notificationRules": "Reglas de Notificación", + "batchImport": "Importación por Lotes", + "rateTemplates": "Plantillas de Tarifas", + "customReports": "Informes Personalizados", + "holidayPay": "Pago de Vacaciones", + "contractValidation": "Validación de Contratos", + "shiftPatterns": "Patrones de Turnos", + "queryGuide": "Guía de Consultas", + "componentShowcase": "Componentes", + "businessLogicDemo": "Demo de Lógica", + "dataAdmin": "Admin de Datos", + "roadmap": "Hoja de Ruta" + }, + "common": { + "search": "Buscar", + "filter": "Filtrar", + "export": "Exportar", + "import": "Importar", + "add": "Agregar", + "edit": "Editar", + "delete": "Eliminar", + "save": "Guardar", + "cancel": "Cancelar", + "submit": "Enviar", + "confirm": "Confirmar", + "close": "Cerrar", + "back": "Atrás", + "next": "Siguiente", + "previous": "Anterior", + "loading": "Cargando...", + "noData": "No hay datos disponibles", + "error": "Error", + "success": "Éxito", + "warning": "Advertencia", + "info": "Información", + "actions": "Acciones", + "status": "Estado", + "date": "Fecha", + "time": "Hora", + "total": "Total", + "subtotal": "Subtotal", + "view": "Ver", + "download": "Descargar", + "upload": "Subir", + "refresh": "Actualizar", + "clear": "Limpiar", + "apply": "Aplicar", + "reset": "Restablecer", + "selectAll": "Seleccionar Todo", + "deselectAll": "Deseleccionar Todo", + "yes": "Sí", + "no": "No", + "or": "o", + "and": "y", + "of": "de", + "to": "a", + "from": "desde" + }, + "dashboard": { + "title": "Panel de Control", + "subtitle": "Resumen de métricas y actividades clave", + "welcomeBack": "Bienvenido de nuevo", + "recentActivity": "Actividad Reciente", + "quickActions": "Acciones Rápidas", + "notifications": "Notificaciones", + "pendingApprovals": "Aprobaciones Pendientes", + "upcomingDeadlines": "Plazos Próximos" + }, + "timesheets": { + "title": "Hojas de Tiempo", + "subtitle": "Gestionar y aprobar entradas de hojas de tiempo", + "addTimesheet": "Agregar Hoja de Tiempo", + "editTimesheet": "Editar Hoja de Tiempo", + "approveTimesheet": "Aprobar Hoja de Tiempo", + "rejectTimesheet": "Rechazar Hoja de Tiempo", + "submitTimesheet": "Enviar Hoja de Tiempo", + "worker": "Trabajador", + "client": "Cliente", + "placement": "Colocación", + "weekEnding": "Semana que Termina", + "hours": "Horas", + "rate": "Tarifa", + "amount": "Monto", + "status": { + "draft": "Borrador", + "submitted": "Enviado", + "approved": "Aprobado", + "rejected": "Rechazado", + "processed": "Procesado" + }, + "hoursWorked": "Horas Trabajadas", + "overtimeHours": "Horas Extras", + "totalHours": "Horas Totales", + "regularHours": "Horas Regulares", + "nightShift": "Turno Nocturno", + "weekendShift": "Turno de Fin de Semana", + "adjustments": "Ajustes", + "comments": "Comentarios" + }, + "billing": { + "title": "Facturación", + "subtitle": "Gestionar facturas y facturación", + "createInvoice": "Crear Factura", + "invoiceNumber": "Número de Factura", + "invoiceDate": "Fecha de Factura", + "dueDate": "Fecha de Vencimiento", + "client": "Cliente", + "amount": "Monto", + "paid": "Pagado", + "unpaid": "No Pagado", + "overdue": "Vencido", + "partiallyPaid": "Parcialmente Pagado", + "status": { + "draft": "Borrador", + "sent": "Enviado", + "paid": "Pagado", + "overdue": "Vencido", + "cancelled": "Cancelado" + }, + "generateInvoice": "Generar Factura", + "sendInvoice": "Enviar Factura", + "downloadInvoice": "Descargar Factura", + "creditNote": "Nota de Crédito", + "purchaseOrder": "Orden de Compra", + "lineItems": "Artículos de Línea", + "subtotal": "Subtotal", + "tax": "Impuesto", + "total": "Total", + "paymentTerms": "Términos de Pago", + "notes": "Notas" + }, + "payroll": { + "title": "Nómina", + "subtitle": "Procesar ejecuciones de nómina", + "createPayroll": "Crear Nómina", + "processPayroll": "Procesar Nómina", + "payrollRun": "Ejecución de Nómina", + "payPeriod": "Período de Pago", + "payDate": "Fecha de Pago", + "totalPay": "Pago Total", + "grossPay": "Pago Bruto", + "netPay": "Pago Neto", + "deductions": "Deducciones", + "taxes": "Impuestos", + "status": { + "draft": "Borrador", + "processing": "Procesando", + "approved": "Aprobado", + "paid": "Pagado" + }, + "employeeCount": "Cantidad de Empleados", + "oneClickPayroll": "Nómina de Un Clic", + "holidayPay": "Pago de Vacaciones", + "statutoryPay": "Pago Estatutario" + }, + "compliance": { + "title": "Cumplimiento", + "subtitle": "Gestionar documentos y controles de cumplimiento", + "document": "Documento", + "expiryDate": "Fecha de Vencimiento", + "status": { + "valid": "Válido", + "expiring": "Próximo a Vencer", + "expired": "Vencido", + "missing": "Faltante" + }, + "uploadDocument": "Subir Documento", + "reviewDocument": "Revisar Documento", + "approveDocument": "Aprobar Documento", + "rightToWork": "Derecho a Trabajar", + "dbsCheck": "Verificación DBS", + "qualification": "Calificación", + "insurance": "Seguro", + "contract": "Contrato" + }, + "expenses": { + "title": "Gastos", + "subtitle": "Gestionar reclamos de gastos", + "addExpense": "Agregar Gasto", + "expenseType": "Tipo de Gasto", + "amount": "Monto", + "date": "Fecha", + "receipt": "Recibo", + "status": { + "draft": "Borrador", + "submitted": "Enviado", + "approved": "Aprobado", + "rejected": "Rechazado", + "paid": "Pagado" + }, + "approveExpense": "Aprobar Gasto", + "rejectExpense": "Rechazar Gasto", + "mileage": "Kilometraje", + "accommodation": "Alojamiento", + "meals": "Comidas", + "travel": "Viaje", + "other": "Otro" + }, + "reports": { + "title": "Informes", + "subtitle": "Generar y ver informes", + "customReport": "Informe Personalizado", + "runReport": "Ejecutar Informe", + "scheduleReport": "Programar Informe", + "exportReport": "Exportar Informe", + "reportType": "Tipo de Informe", + "dateRange": "Rango de Fechas", + "filters": "Filtros", + "financialReport": "Informe Financiero", + "operationalReport": "Informe Operativo", + "complianceReport": "Informe de Cumplimiento", + "marginAnalysis": "Análisis de Margen", + "missingTimesheets": "Hojas de Tiempo Faltantes", + "auditTrail": "Registro de Auditoría" + }, + "notifications": { + "title": "Notificaciones", + "markAsRead": "Marcar como Leído", + "markAllAsRead": "Marcar Todo como Leído", + "delete": "Eliminar", + "noNotifications": "Sin notificaciones", + "unreadCount": "{{count}} sin leer", + "types": { + "info": "Información", + "success": "Éxito", + "warning": "Advertencia", + "error": "Error" + } + }, + "filters": { + "title": "Filtros", + "apply": "Aplicar Filtros", + "clear": "Limpiar Filtros", + "status": "Estado", + "dateRange": "Rango de Fechas", + "client": "Cliente", + "worker": "Trabajador", + "type": "Tipo", + "advanced": "Filtros Avanzados", + "queryLanguage": "Lenguaje de Consulta" + }, + "validation": { + "required": "Este campo es obligatorio", + "invalidEmail": "Dirección de correo electrónico no válida", + "invalidDate": "Fecha no válida", + "invalidNumber": "Número no válido", + "minLength": "La longitud mínima es {{min}} caracteres", + "maxLength": "La longitud máxima es {{max}} caracteres", + "minValue": "El valor mínimo es {{min}}", + "maxValue": "El valor máximo es {{max}}" + }, + "errors": { + "generic": "Ocurrió un error. Por favor, inténtelo de nuevo.", + "network": "Error de red. Por favor, verifique su conexión.", + "notFound": "Recurso no encontrado", + "unauthorized": "No está autorizado para realizar esta acción", + "serverError": "Error del servidor. Por favor, inténtelo más tarde.", + "loadingFailed": "Error al cargar datos" + }, + "currency": { + "title": "Gestión de Monedas", + "addCurrency": "Agregar Moneda", + "code": "Código de Moneda", + "symbol": "Símbolo", + "exchangeRate": "Tipo de Cambio", + "baseCurrency": "Moneda Base" + }, + "templates": { + "email": "Plantillas de Correo", + "invoice": "Plantillas de Factura", + "rate": "Plantillas de Tarifas", + "createTemplate": "Crear Plantilla", + "editTemplate": "Editar Plantilla", + "deleteTemplate": "Eliminar Plantilla" + }, + "onboarding": { + "title": "Incorporación", + "workflow": "Flujo de Incorporación", + "step": "Paso", + "complete": "Completo", + "pending": "Pendiente", + "assignTasks": "Asignar Tareas" + }, + "auditTrail": { + "title": "Registro de Auditoría", + "action": "Acción", + "user": "Usuario", + "timestamp": "Marca de Tiempo", + "details": "Detalles", + "view": "Ver Detalles" + }, + "batchImport": { + "title": "Importación por Lotes", + "uploadFile": "Subir Archivo", + "selectFile": "Seleccionar Archivo", + "import": "Importar", + "preview": "Vista Previa", + "mapping": "Mapeo de Campos", + "validate": "Validar" + }, + "shiftPatterns": { + "title": "Patrones de Turnos", + "createPattern": "Crear Patrón", + "patternName": "Nombre del Patrón", + "dayShift": "Turno Diurno", + "nightShift": "Turno Nocturno", + "weekendShift": "Turno de Fin de Semana", + "rotatingPattern": "Patrón Rotativo" + }, + "metrics": { + "totalRevenue": "Ingresos Totales", + "grossMargin": "Margen Bruto", + "activeWorkers": "Trabajadores Activos", + "pendingTimesheets": "Hojas de Tiempo Pendientes", + "unpaidInvoices": "Facturas No Pagadas", + "complianceRate": "Tasa de Cumplimiento", + "changeFromLastPeriod": "{{change}}% desde el último período" + }, + "statuses": { + "active": "Activo", + "inactive": "Inactivo", + "pending": "Pendiente", + "approved": "Aprobado", + "rejected": "Rechazado", + "draft": "Borrador", + "submitted": "Enviado", + "processed": "Procesado", + "paid": "Pagado", + "unpaid": "No Pagado", + "overdue": "Vencido" + } +} diff --git a/src/data/translations/fr.json b/src/data/translations/fr.json new file mode 100644 index 0000000..19fa030 --- /dev/null +++ b/src/data/translations/fr.json @@ -0,0 +1,354 @@ +{ + "app": { + "title": "WorkForce Pro", + "subtitle": "Plateforme de Back Office Améliorée" + }, + "navigation": { + "dashboard": "Tableau de Bord", + "timesheets": "Feuilles de Temps", + "billing": "Facturation", + "payroll": "Paie", + "compliance": "Conformité", + "expenses": "Dépenses", + "reports": "Rapports", + "settings": "Paramètres", + "currency": "Devise", + "emailTemplates": "Modèles de Courriel", + "invoiceTemplates": "Modèles de Facture", + "qrScanner": "Scanner QR", + "missingTimesheets": "Feuilles de Temps Manquantes", + "purchaseOrders": "Bons de Commande", + "onboarding": "Intégration", + "auditTrail": "Piste d'Audit", + "notificationRules": "Règles de Notification", + "batchImport": "Importation par Lots", + "rateTemplates": "Modèles de Tarifs", + "customReports": "Rapports Personnalisés", + "holidayPay": "Paie de Vacances", + "contractValidation": "Validation de Contrat", + "shiftPatterns": "Modèles de Quarts", + "queryGuide": "Guide de Requête", + "componentShowcase": "Composants", + "businessLogicDemo": "Démo de Logique", + "dataAdmin": "Admin de Données", + "roadmap": "Feuille de Route" + }, + "common": { + "search": "Rechercher", + "filter": "Filtrer", + "export": "Exporter", + "import": "Importer", + "add": "Ajouter", + "edit": "Modifier", + "delete": "Supprimer", + "save": "Enregistrer", + "cancel": "Annuler", + "submit": "Soumettre", + "confirm": "Confirmer", + "close": "Fermer", + "back": "Retour", + "next": "Suivant", + "previous": "Précédent", + "loading": "Chargement...", + "noData": "Aucune donnée disponible", + "error": "Erreur", + "success": "Succès", + "warning": "Avertissement", + "info": "Information", + "actions": "Actions", + "status": "Statut", + "date": "Date", + "time": "Heure", + "total": "Total", + "subtotal": "Sous-total", + "view": "Voir", + "download": "Télécharger", + "upload": "Téléverser", + "refresh": "Actualiser", + "clear": "Effacer", + "apply": "Appliquer", + "reset": "Réinitialiser", + "selectAll": "Tout Sélectionner", + "deselectAll": "Tout Désélectionner", + "yes": "Oui", + "no": "Non", + "or": "ou", + "and": "et", + "of": "de", + "to": "à", + "from": "de" + }, + "dashboard": { + "title": "Tableau de Bord", + "subtitle": "Aperçu des métriques et activités clés", + "welcomeBack": "Bon retour", + "recentActivity": "Activité Récente", + "quickActions": "Actions Rapides", + "notifications": "Notifications", + "pendingApprovals": "Approbations en Attente", + "upcomingDeadlines": "Échéances à Venir" + }, + "timesheets": { + "title": "Feuilles de Temps", + "subtitle": "Gérer et approuver les entrées de feuilles de temps", + "addTimesheet": "Ajouter une Feuille de Temps", + "editTimesheet": "Modifier une Feuille de Temps", + "approveTimesheet": "Approuver une Feuille de Temps", + "rejectTimesheet": "Rejeter une Feuille de Temps", + "submitTimesheet": "Soumettre une Feuille de Temps", + "worker": "Travailleur", + "client": "Client", + "placement": "Placement", + "weekEnding": "Semaine se Terminant", + "hours": "Heures", + "rate": "Tarif", + "amount": "Montant", + "status": { + "draft": "Brouillon", + "submitted": "Soumis", + "approved": "Approuvé", + "rejected": "Rejeté", + "processed": "Traité" + }, + "hoursWorked": "Heures Travaillées", + "overtimeHours": "Heures Supplémentaires", + "totalHours": "Heures Totales", + "regularHours": "Heures Régulières", + "nightShift": "Quart de Nuit", + "weekendShift": "Quart de Fin de Semaine", + "adjustments": "Ajustements", + "comments": "Commentaires" + }, + "billing": { + "title": "Facturation", + "subtitle": "Gérer les factures et la facturation", + "createInvoice": "Créer une Facture", + "invoiceNumber": "Numéro de Facture", + "invoiceDate": "Date de Facture", + "dueDate": "Date d'Échéance", + "client": "Client", + "amount": "Montant", + "paid": "Payé", + "unpaid": "Non Payé", + "overdue": "En Retard", + "partiallyPaid": "Partiellement Payé", + "status": { + "draft": "Brouillon", + "sent": "Envoyé", + "paid": "Payé", + "overdue": "En Retard", + "cancelled": "Annulé" + }, + "generateInvoice": "Générer une Facture", + "sendInvoice": "Envoyer une Facture", + "downloadInvoice": "Télécharger une Facture", + "creditNote": "Note de Crédit", + "purchaseOrder": "Bon de Commande", + "lineItems": "Articles de Ligne", + "subtotal": "Sous-total", + "tax": "Taxe", + "total": "Total", + "paymentTerms": "Conditions de Paiement", + "notes": "Notes" + }, + "payroll": { + "title": "Paie", + "subtitle": "Traiter les exécutions de paie", + "createPayroll": "Créer une Paie", + "processPayroll": "Traiter une Paie", + "payrollRun": "Exécution de Paie", + "payPeriod": "Période de Paie", + "payDate": "Date de Paiement", + "totalPay": "Paie Totale", + "grossPay": "Paie Brute", + "netPay": "Paie Nette", + "deductions": "Déductions", + "taxes": "Taxes", + "status": { + "draft": "Brouillon", + "processing": "En Traitement", + "approved": "Approuvé", + "paid": "Payé" + }, + "employeeCount": "Nombre d'Employés", + "oneClickPayroll": "Paie en Un Clic", + "holidayPay": "Paie de Vacances", + "statutoryPay": "Paie Statutaire" + }, + "compliance": { + "title": "Conformité", + "subtitle": "Gérer les documents et contrôles de conformité", + "document": "Document", + "expiryDate": "Date d'Expiration", + "status": { + "valid": "Valide", + "expiring": "Expire Bientôt", + "expired": "Expiré", + "missing": "Manquant" + }, + "uploadDocument": "Téléverser un Document", + "reviewDocument": "Examiner un Document", + "approveDocument": "Approuver un Document", + "rightToWork": "Droit de Travailler", + "dbsCheck": "Vérification DBS", + "qualification": "Qualification", + "insurance": "Assurance", + "contract": "Contrat" + }, + "expenses": { + "title": "Dépenses", + "subtitle": "Gérer les réclamations de dépenses", + "addExpense": "Ajouter une Dépense", + "expenseType": "Type de Dépense", + "amount": "Montant", + "date": "Date", + "receipt": "Reçu", + "status": { + "draft": "Brouillon", + "submitted": "Soumis", + "approved": "Approuvé", + "rejected": "Rejeté", + "paid": "Payé" + }, + "approveExpense": "Approuver une Dépense", + "rejectExpense": "Rejeter une Dépense", + "mileage": "Kilométrage", + "accommodation": "Hébergement", + "meals": "Repas", + "travel": "Voyage", + "other": "Autre" + }, + "reports": { + "title": "Rapports", + "subtitle": "Générer et consulter des rapports", + "customReport": "Rapport Personnalisé", + "runReport": "Exécuter un Rapport", + "scheduleReport": "Planifier un Rapport", + "exportReport": "Exporter un Rapport", + "reportType": "Type de Rapport", + "dateRange": "Plage de Dates", + "filters": "Filtres", + "financialReport": "Rapport Financier", + "operationalReport": "Rapport Opérationnel", + "complianceReport": "Rapport de Conformité", + "marginAnalysis": "Analyse de Marge", + "missingTimesheets": "Feuilles de Temps Manquantes", + "auditTrail": "Piste d'Audit" + }, + "notifications": { + "title": "Notifications", + "markAsRead": "Marquer comme Lu", + "markAllAsRead": "Tout Marquer comme Lu", + "delete": "Supprimer", + "noNotifications": "Aucune notification", + "unreadCount": "{{count}} non lus", + "types": { + "info": "Information", + "success": "Succès", + "warning": "Avertissement", + "error": "Erreur" + } + }, + "filters": { + "title": "Filtres", + "apply": "Appliquer les Filtres", + "clear": "Effacer les Filtres", + "status": "Statut", + "dateRange": "Plage de Dates", + "client": "Client", + "worker": "Travailleur", + "type": "Type", + "advanced": "Filtres Avancés", + "queryLanguage": "Langage de Requête" + }, + "validation": { + "required": "Ce champ est obligatoire", + "invalidEmail": "Adresse e-mail non valide", + "invalidDate": "Date non valide", + "invalidNumber": "Nombre non valide", + "minLength": "La longueur minimale est de {{min}} caractères", + "maxLength": "La longueur maximale est de {{max}} caractères", + "minValue": "La valeur minimale est {{min}}", + "maxValue": "La valeur maximale est {{max}}" + }, + "errors": { + "generic": "Une erreur s'est produite. Veuillez réessayer.", + "network": "Erreur réseau. Veuillez vérifier votre connexion.", + "notFound": "Ressource introuvable", + "unauthorized": "Vous n'êtes pas autorisé à effectuer cette action", + "serverError": "Erreur du serveur. Veuillez réessayer plus tard.", + "loadingFailed": "Échec du chargement des données" + }, + "currency": { + "title": "Gestion des Devises", + "addCurrency": "Ajouter une Devise", + "code": "Code de Devise", + "symbol": "Symbole", + "exchangeRate": "Taux de Change", + "baseCurrency": "Devise de Base" + }, + "templates": { + "email": "Modèles de Courriel", + "invoice": "Modèles de Facture", + "rate": "Modèles de Tarifs", + "createTemplate": "Créer un Modèle", + "editTemplate": "Modifier un Modèle", + "deleteTemplate": "Supprimer un Modèle" + }, + "onboarding": { + "title": "Intégration", + "workflow": "Flux d'Intégration", + "step": "Étape", + "complete": "Complet", + "pending": "En Attente", + "assignTasks": "Assigner des Tâches" + }, + "auditTrail": { + "title": "Piste d'Audit", + "action": "Action", + "user": "Utilisateur", + "timestamp": "Horodatage", + "details": "Détails", + "view": "Voir les Détails" + }, + "batchImport": { + "title": "Importation par Lots", + "uploadFile": "Téléverser un Fichier", + "selectFile": "Sélectionner un Fichier", + "import": "Importer", + "preview": "Aperçu", + "mapping": "Mappage de Champs", + "validate": "Valider" + }, + "shiftPatterns": { + "title": "Modèles de Quarts", + "createPattern": "Créer un Modèle", + "patternName": "Nom du Modèle", + "dayShift": "Quart de Jour", + "nightShift": "Quart de Nuit", + "weekendShift": "Quart de Fin de Semaine", + "rotatingPattern": "Modèle Rotatif" + }, + "metrics": { + "totalRevenue": "Revenus Totaux", + "grossMargin": "Marge Brute", + "activeWorkers": "Travailleurs Actifs", + "pendingTimesheets": "Feuilles de Temps en Attente", + "unpaidInvoices": "Factures Non Payées", + "complianceRate": "Taux de Conformité", + "changeFromLastPeriod": "{{change}}% depuis la dernière période" + }, + "statuses": { + "active": "Actif", + "inactive": "Inactif", + "pending": "En Attente", + "approved": "Approuvé", + "rejected": "Rejeté", + "draft": "Brouillon", + "submitted": "Soumis", + "processed": "Traité", + "paid": "Payé", + "unpaid": "Non Payé", + "overdue": "En Retard" + } +} diff --git a/src/hooks/use-translation.ts b/src/hooks/use-translation.ts new file mode 100644 index 0000000..e86d836 --- /dev/null +++ b/src/hooks/use-translation.ts @@ -0,0 +1,98 @@ +import { useKV } from '@github/spark/hooks' +import { useState, useEffect, useCallback } from 'react' + +type Translations = Record +type Locale = 'en' | 'es' | 'fr' + +const AVAILABLE_LOCALES: Locale[] = ['en', 'es', 'fr'] +const DEFAULT_LOCALE: Locale = 'en' + +export function useTranslation() { + const [locale, setLocale] = useKV('app-locale', DEFAULT_LOCALE) + const [translations, setTranslations] = useState({}) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const loadTranslations = async () => { + try { + setIsLoading(true) + setError(null) + + const response = await import(`@/data/translations/${locale}.json`) + setTranslations(response.default || response) + } catch (err) { + console.error(`Failed to load translations for locale: ${locale}`, err) + setError(err as Error) + + if (locale !== DEFAULT_LOCALE) { + try { + const fallbackResponse = await import(`@/data/translations/${DEFAULT_LOCALE}.json`) + setTranslations(fallbackResponse.default || fallbackResponse) + } catch (fallbackErr) { + console.error('Failed to load fallback translations', fallbackErr) + } + } + } finally { + setIsLoading(false) + } + } + + loadTranslations() + }, [locale]) + + const t = useCallback((key: string, params?: Record): string => { + const keys = key.split('.') + let value: any = translations + + for (const k of keys) { + if (value && typeof value === 'object' && k in value) { + value = value[k] + } else { + return key + } + } + + if (typeof value !== 'string') { + return key + } + + if (params) { + return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => { + return params[paramKey]?.toString() || match + }) + } + + return value + }, [translations]) + + const changeLocale = useCallback((newLocale: Locale) => { + if (AVAILABLE_LOCALES.includes(newLocale)) { + setLocale(newLocale) + } + }, [setLocale]) + + return { + t, + locale, + changeLocale, + availableLocales: AVAILABLE_LOCALES, + isLoading, + error + } +} + +export function useLocale() { + const [locale] = useKV('app-locale', DEFAULT_LOCALE) + return locale +} + +export function useChangeLocale() { + const [, setLocale] = useKV('app-locale', DEFAULT_LOCALE) + + return useCallback((newLocale: Locale) => { + if (AVAILABLE_LOCALES.includes(newLocale)) { + setLocale(newLocale) + } + }, [setLocale]) +} diff --git a/src/lib/view-preloader.ts b/src/lib/view-preloader.ts index c0cf24a..eaa124e 100644 --- a/src/lib/view-preloader.ts +++ b/src/lib/view-preloader.ts @@ -28,6 +28,7 @@ const viewPreloadMap: Record Promise> = { 'component-showcase': () => import('@/components/ComponentShowcase'), 'business-logic-demo': () => import('@/components/BusinessLogicDemo'), 'data-admin': () => import('@/components/views/data-admin-view'), + 'translation-demo': () => import('@/components/TranslationDemo'), } const preloadedViews = new Set()