mirror of
https://github.com/johndoe6345789/workforce-pay-bill-p.git
synced 2026-04-24 13:24:57 +00:00
Generated by Spark: load translations from json
This commit is contained in:
521
TRANSLATIONS.md
Normal file
521
TRANSLATIONS.md
Normal file
@@ -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 (
|
||||
<div>
|
||||
<h1>{t('navigation.dashboard')}</h1>
|
||||
<button>{t('common.save')}</button>
|
||||
<p>{t('timesheets.status.approved')}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<div>
|
||||
<p>Current: {locale}</p>
|
||||
<button onClick={() => changeLocale('es')}>Español</button>
|
||||
<button onClick={() => changeLocale('fr')}>Français</button>
|
||||
<button onClick={() => changeLocale('en')}>English</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 <span>Current language: {locale}</span>
|
||||
}
|
||||
```
|
||||
|
||||
### 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 <button onClick={() => changeLocale('es')}>Switch to Spanish</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Hook API Reference
|
||||
|
||||
### `useTranslation()`
|
||||
|
||||
Main hook for translation functionality.
|
||||
|
||||
**Returns:**
|
||||
```typescript
|
||||
{
|
||||
t: (key: string, params?: Record<string, string | number>) => 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 <div>Loading translations...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{t('app.title')}</h1>
|
||||
<button onClick={() => changeLocale('es')}>Español</button>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
### `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'
|
||||
|
||||
<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<string, string> = {
|
||||
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 (
|
||||
<Card>
|
||||
<h2>{t('timesheets.title')}</h2>
|
||||
<p>{t('timesheets.hoursWorked')}: {timesheet.hours}</p>
|
||||
<div>
|
||||
<Button onClick={handleApprove}>
|
||||
{t('timesheets.approveTimesheet')}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleReject}>
|
||||
{t('timesheets.rejectTimesheet')}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<form>
|
||||
<input
|
||||
{...register('email', {
|
||||
required: t('validation.required'),
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: t('validation.invalidEmail')
|
||||
}
|
||||
})}
|
||||
/>
|
||||
{errors.email && <span>{errors.email.message}</span>}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
20
src/App.tsx
20
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() {
|
||||
<div className="border-b border-border bg-card flex-shrink-0 z-10">
|
||||
<div className="flex items-center justify-between px-6 py-3">
|
||||
<div className="flex-1" />
|
||||
<NotificationCenter
|
||||
notifications={notifications}
|
||||
unreadCount={unreadCount}
|
||||
onMarkAsRead={markAsRead}
|
||||
onMarkAllAsRead={markAllAsRead}
|
||||
onDelete={deleteNotification}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<LanguageSwitcher />
|
||||
<NotificationCenter
|
||||
notifications={notifications}
|
||||
unreadCount={unreadCount}
|
||||
onMarkAsRead={markAsRead}
|
||||
onMarkAllAsRead={markAllAsRead}
|
||||
onDelete={deleteNotification}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
42
src/components/LanguageSwitcher.tsx
Normal file
42
src/components/LanguageSwitcher.tsx
Normal file
@@ -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<string, string> = {
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
fr: 'Français'
|
||||
}
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const { locale, changeLocale, availableLocales } = useTranslation()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="gap-2">
|
||||
<Globe size={18} />
|
||||
<span className="hidden sm:inline">{LOCALE_NAMES[locale || 'en']}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
{availableLocales.map((loc) => (
|
||||
<DropdownMenuItem
|
||||
key={loc}
|
||||
onClick={() => changeLocale(loc)}
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
<span>{LOCALE_NAMES[loc]}</span>
|
||||
{locale === loc && <Check size={16} weight="bold" />}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
314
src/components/TranslationDemo.tsx
Normal file
314
src/components/TranslationDemo.tsx
Normal file
@@ -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 (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">{t('common.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Card className="max-w-md">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle size={24} className="text-destructive" />
|
||||
<CardTitle>{t('common.error')}</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground">{t('errors.loadingFailed')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-3xl font-semibold tracking-tight flex items-center gap-3">
|
||||
<Translate size={32} weight="duotone" className="text-primary" />
|
||||
Translation System Demo
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Comprehensive i18n with JSON-based translations
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Globe size={20} />
|
||||
Current Language
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Active locale: <Badge variant="secondary">{locale}</Badge>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
{availableLocales.map((loc) => (
|
||||
<Button
|
||||
key={loc}
|
||||
variant={locale === loc ? 'default' : 'outline'}
|
||||
onClick={() => changeLocale(loc)}
|
||||
className="uppercase"
|
||||
>
|
||||
{loc}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Navigation Translations</CardTitle>
|
||||
<CardDescription>Menu items in current language</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<TranslationItem label="Dashboard" translationKey="navigation.dashboard" />
|
||||
<TranslationItem label="Timesheets" translationKey="navigation.timesheets" />
|
||||
<TranslationItem label="Billing" translationKey="navigation.billing" />
|
||||
<TranslationItem label="Payroll" translationKey="navigation.payroll" />
|
||||
<TranslationItem label="Compliance" translationKey="navigation.compliance" />
|
||||
<TranslationItem label="Expenses" translationKey="navigation.expenses" />
|
||||
<TranslationItem label="Reports" translationKey="navigation.reports" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Common Actions</CardTitle>
|
||||
<CardDescription>Frequently used terms</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<TranslationItem label="Search" translationKey="common.search" />
|
||||
<TranslationItem label="Filter" translationKey="common.filter" />
|
||||
<TranslationItem label="Export" translationKey="common.export" />
|
||||
<TranslationItem label="Save" translationKey="common.save" />
|
||||
<TranslationItem label="Cancel" translationKey="common.cancel" />
|
||||
<TranslationItem label="Submit" translationKey="common.submit" />
|
||||
<TranslationItem label="Delete" translationKey="common.delete" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Status Messages</CardTitle>
|
||||
<CardDescription>System statuses and alerts</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle size={20} className="text-success" />
|
||||
<span className="text-sm">{t('common.success')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Warning size={20} className="text-warning" />
|
||||
<span className="text-sm">{t('common.warning')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle size={20} className="text-destructive" />
|
||||
<span className="text-sm">{t('common.error')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Info size={20} className="text-info" />
|
||||
<span className="text-sm">{t('common.info')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Timesheet Statuses</CardTitle>
|
||||
<CardDescription>Document lifecycle states</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Draft</span>
|
||||
<Badge variant="secondary">{t('timesheets.status.draft')}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Submitted</span>
|
||||
<Badge variant="secondary">{t('timesheets.status.submitted')}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Approved</span>
|
||||
<Badge variant="secondary">{t('timesheets.status.approved')}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Rejected</span>
|
||||
<Badge variant="secondary">{t('timesheets.status.rejected')}</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Processed</span>
|
||||
<Badge variant="secondary">{t('timesheets.status.processed')}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Parameterized Translations</CardTitle>
|
||||
<CardDescription>Dynamic values in translations</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-2">Unread count example:</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('notifications.unreadCount', { count: 5 })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('notifications.unreadCount', { count: 12 })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('notifications.unreadCount', { count: 0 })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-2">Change from period example:</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('metrics.changeFromLastPeriod', { change: '+15' })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('metrics.changeFromLastPeriod', { change: '-8' })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('metrics.changeFromLastPeriod', { change: '+3.5' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-2">Validation messages:</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-destructive">
|
||||
{t('validation.minLength', { min: 8 })}
|
||||
</p>
|
||||
<p className="text-sm text-destructive">
|
||||
{t('validation.maxLength', { max: 50 })}
|
||||
</p>
|
||||
<p className="text-sm text-destructive">
|
||||
{t('validation.minValue', { min: 0 })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-info/50 bg-info/5">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Info size={20} className="text-info" />
|
||||
Usage Instructions
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-1">In any component:</p>
|
||||
<pre className="text-xs bg-muted p-3 rounded-md overflow-x-auto">
|
||||
<code>{`import { useTranslation } from '@/hooks/use-translation'
|
||||
|
||||
function MyComponent() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return <h1>{t('navigation.dashboard')}</h1>
|
||||
}`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-1">With parameters:</p>
|
||||
<pre className="text-xs bg-muted p-3 rounded-md overflow-x-auto">
|
||||
<code>{`const unreadText = t('notifications.unreadCount', { count: 5 })
|
||||
const changeText = t('metrics.changeFromLastPeriod', { change: '+12' })`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-1">Change language:</p>
|
||||
<pre className="text-xs bg-muted p-3 rounded-md overflow-x-auto">
|
||||
<code>{`const { changeLocale } = useTranslation()
|
||||
changeLocale('es') // Switch to Spanish
|
||||
changeLocale('fr') // Switch to French`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">Translation files location:</p>
|
||||
<ul className="text-xs space-y-1 text-muted-foreground">
|
||||
<li>• <code className="bg-muted px-1 py-0.5 rounded">/src/data/translations/en.json</code> - English</li>
|
||||
<li>• <code className="bg-muted px-1 py-0.5 rounded">/src/data/translations/es.json</code> - Spanish</li>
|
||||
<li>• <code className="bg-muted px-1 py-0.5 rounded">/src/data/translations/fr.json</code> - French</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-1">Features:</p>
|
||||
<ul className="text-xs space-y-1 text-muted-foreground">
|
||||
<li>✓ Persistent language preference (saved to KV store)</li>
|
||||
<li>✓ Automatic fallback to English if translation missing</li>
|
||||
<li>✓ Parameter interpolation with {`{{variable}}`} syntax</li>
|
||||
<li>✓ Nested translation keys with dot notation</li>
|
||||
<li>✓ Language switcher in header</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TranslationItem({ label, translationKey }: { label: string; translationKey: string }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-sm text-muted-foreground">{label}</span>
|
||||
<span className="text-sm font-medium">{t(translationKey)}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 <DataAdminView />
|
||||
|
||||
case 'translation-demo':
|
||||
return <TranslationDemo />
|
||||
|
||||
default:
|
||||
return <DashboardView metrics={metrics} />
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<NavItem
|
||||
icon={<Translate size={20} />}
|
||||
label="Translations"
|
||||
active={currentView === 'translation-demo'}
|
||||
onClick={() => setCurrentView('translation-demo')}
|
||||
view="translation-demo"
|
||||
/>
|
||||
</NavGroup>
|
||||
)
|
||||
}
|
||||
|
||||
354
src/data/translations/en.json
Normal file
354
src/data/translations/en.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
354
src/data/translations/es.json
Normal file
354
src/data/translations/es.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
354
src/data/translations/fr.json
Normal file
354
src/data/translations/fr.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
98
src/hooks/use-translation.ts
Normal file
98
src/hooks/use-translation.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useKV } from '@github/spark/hooks'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
type Translations = Record<string, any>
|
||||
type Locale = 'en' | 'es' | 'fr'
|
||||
|
||||
const AVAILABLE_LOCALES: Locale[] = ['en', 'es', 'fr']
|
||||
const DEFAULT_LOCALE: Locale = 'en'
|
||||
|
||||
export function useTranslation() {
|
||||
const [locale, setLocale] = useKV<Locale>('app-locale', DEFAULT_LOCALE)
|
||||
const [translations, setTranslations] = useState<Translations>({})
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<Error | null>(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, string | number>): 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<Locale>('app-locale', DEFAULT_LOCALE)
|
||||
return locale
|
||||
}
|
||||
|
||||
export function useChangeLocale() {
|
||||
const [, setLocale] = useKV<Locale>('app-locale', DEFAULT_LOCALE)
|
||||
|
||||
return useCallback((newLocale: Locale) => {
|
||||
if (AVAILABLE_LOCALES.includes(newLocale)) {
|
||||
setLocale(newLocale)
|
||||
}
|
||||
}, [setLocale])
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const viewPreloadMap: Record<View, () => Promise<any>> = {
|
||||
'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<View>()
|
||||
|
||||
Reference in New Issue
Block a user