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 (
+
+ )
+}
+```
+
+## 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()