@jsarc/intl 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +596 -0
- package/config.ts +36 -0
- package/core/TranslationService.ts +153 -0
- package/core/types.ts +20 -0
- package/hooks/useTranslation.tsx +48 -0
- package/index.ts +4 -0
- package/js/config.js +14 -0
- package/js/core/TranslationService.js +104 -0
- package/js/core/types.js +1 -0
- package/js/hooks/useTranslation.jsx +40 -0
- package/js/index.js +1 -0
- package/js/providers/IntlProvider.jsx +106 -0
- package/js/utils/loaders.js +64 -0
- package/js/utils/mergeTranslations.js +16 -0
- package/js/utils.js +1 -0
- package/package.json +22 -0
- package/providers/IntlProvider.tsx +138 -0
- package/tsconfig.json +27 -0
- package/utils/loaders.ts +79 -0
- package/utils/mergeTranslations.ts +15 -0
- package/utils.ts +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
# @jsarc/intl
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
**@jsarc/intl** est un système de gestion d'internationalisation (i18n) modulaire et performant pour les applications React avec TypeScript/JavaScript. Il fournit une gestion avancée des traductions, un chargement dynamique des modules, et une intégration transparente avec l'écosystème Arc.
|
|
9
|
+
|
|
10
|
+
## ✨ Fonctionnalités Principales
|
|
11
|
+
|
|
12
|
+
### 🌍 Gestion Multi-Langue Avancée
|
|
13
|
+
- **Support de plusieurs locales** avec détection automatique
|
|
14
|
+
- **Chargement dynamique** des fichiers de traduction
|
|
15
|
+
- **Gestion des pluriels** basée sur les règles Intl
|
|
16
|
+
- **Interpolation de variables** dans les traductions
|
|
17
|
+
|
|
18
|
+
### 📦 Architecture Modulaire
|
|
19
|
+
- **Traductions par module** avec isolation complète
|
|
20
|
+
- **Chargement à la demande** des traductions des modules
|
|
21
|
+
- **Fusion intelligente** des traductions hiérarchiques
|
|
22
|
+
- **Support des namespaces** pour une organisation claire
|
|
23
|
+
|
|
24
|
+
### ⚡ Performance Optimisée
|
|
25
|
+
- **Chargement paresseux** des fichiers de traduction
|
|
26
|
+
- **Mémoire cache** des traductions chargées
|
|
27
|
+
- **Minimal bundle size** grâce au code splitting
|
|
28
|
+
- **Hot reload** pendant le développement
|
|
29
|
+
|
|
30
|
+
### 🔧 Intégration Facile
|
|
31
|
+
- **Provider React** simple à configurer
|
|
32
|
+
- **Hooks personnalisés** pour une utilisation intuitive
|
|
33
|
+
- **Compatibilité totale** avec TypeScript
|
|
34
|
+
- **Intégration avec @jsarc/cooks** pour la persistance
|
|
35
|
+
|
|
36
|
+
## 📦 Installation
|
|
37
|
+
|
|
38
|
+
### Via npm/yarn/pnpm
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @jsarc/intl @jsarc/cooks react
|
|
42
|
+
# ou
|
|
43
|
+
yarn add @jsarc/intl @jsarc/cooks react
|
|
44
|
+
# ou
|
|
45
|
+
pnpm add @jsarc/intl @jsarc/cooks react
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Dépendances requises
|
|
49
|
+
- React 19+
|
|
50
|
+
- @jsarc/cooks 1.0.0+
|
|
51
|
+
- TypeScript 5.0+ (recommandé)
|
|
52
|
+
- Vite (pour le chargement dynamique)
|
|
53
|
+
|
|
54
|
+
## 🚀 Démarrage Rapide
|
|
55
|
+
|
|
56
|
+
### Structure de projet recommandée
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
src/
|
|
60
|
+
├── locales/
|
|
61
|
+
│ ├── en.json # Traductions anglaises de base
|
|
62
|
+
│ └── fr.json # Traductions françaises de base
|
|
63
|
+
├── modules/
|
|
64
|
+
│ ├── admin/
|
|
65
|
+
│ │ └── locales/
|
|
66
|
+
│ │ ├── en.json # Traductions admin anglaises
|
|
67
|
+
│ │ └── fr.json # Traductions admin françaises
|
|
68
|
+
│ └── dashboard/
|
|
69
|
+
│ └── locales/
|
|
70
|
+
│ ├── en.json # Traductions dashboard anglaises
|
|
71
|
+
│ └── fr.json # Traductions dashboard françaises
|
|
72
|
+
└── main.tsx
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Configuration de base
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// main.tsx
|
|
79
|
+
import React from 'react';
|
|
80
|
+
import ReactDOM from 'react-dom/client';
|
|
81
|
+
import { ArcIntlProvider } from '@jsarc/intl';
|
|
82
|
+
|
|
83
|
+
const App = () => {
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<h1>Mon Application Internationalisée</h1>
|
|
87
|
+
{/* Votre contenu ici */}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
93
|
+
<React.StrictMode>
|
|
94
|
+
<ArcIntlProvider
|
|
95
|
+
translations={{
|
|
96
|
+
base: {
|
|
97
|
+
en: async () => (await import('./locales/en.json')).default,
|
|
98
|
+
fr: async () => (await import('./locales/fr.json')).default
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
supportedLocales={['en', 'fr', 'es']}
|
|
102
|
+
>
|
|
103
|
+
<App />
|
|
104
|
+
</ArcIntlProvider>
|
|
105
|
+
</React.StrictMode>
|
|
106
|
+
);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Fichiers de traduction
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
// locales/en.json
|
|
113
|
+
{
|
|
114
|
+
"common": {
|
|
115
|
+
"welcome": "Welcome to our application",
|
|
116
|
+
"login": "Sign in",
|
|
117
|
+
"logout": "Sign out",
|
|
118
|
+
"save": "Save changes"
|
|
119
|
+
},
|
|
120
|
+
"errors": {
|
|
121
|
+
"required": "This field is required",
|
|
122
|
+
"email": "Please enter a valid email address"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// modules/admin/locales/en.json
|
|
127
|
+
{
|
|
128
|
+
"admin": {
|
|
129
|
+
"dashboard": {
|
|
130
|
+
"title": "Admin Dashboard",
|
|
131
|
+
"users": "Manage Users",
|
|
132
|
+
"settings": "System Settings"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 📚 Documentation API
|
|
139
|
+
|
|
140
|
+
### Hook useTranslation
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { useTranslation } from '@jsarc/intl';
|
|
144
|
+
|
|
145
|
+
const MyComponent = () => {
|
|
146
|
+
const {
|
|
147
|
+
t, // Fonction de traduction principale
|
|
148
|
+
changeLocale, // Changer la locale
|
|
149
|
+
currentLocale, // Locale actuelle
|
|
150
|
+
isLoading // État de chargement
|
|
151
|
+
} = useTranslation('admin'); // Optionnel: nom du module
|
|
152
|
+
|
|
153
|
+
// Exemple d'utilisation
|
|
154
|
+
const handleChangeLanguage = () => {
|
|
155
|
+
changeLocale(currentLocale === 'en' ? 'fr' : 'en');
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div>
|
|
160
|
+
<h1>{t('admin.dashboard.title')}</h1>
|
|
161
|
+
<p>{t('common.welcome')}</p>
|
|
162
|
+
<button onClick={handleChangeLanguage}>
|
|
163
|
+
Switch to {currentLocale === 'en' ? 'French' : 'English'}
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### ArcIntlProvider
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { ArcIntlProvider } from '@jsarc/intl';
|
|
174
|
+
|
|
175
|
+
// Configuration complète avec modules
|
|
176
|
+
<ArcIntlProvider
|
|
177
|
+
translations={{
|
|
178
|
+
base: {
|
|
179
|
+
en: async () => (await import('./locales/en.json')).default,
|
|
180
|
+
fr: async () => (await import('./locales/fr.json')).default
|
|
181
|
+
},
|
|
182
|
+
modules: {
|
|
183
|
+
admin: {
|
|
184
|
+
en: async () => (await import('./modules/admin/locales/en.json')).default,
|
|
185
|
+
fr: async () => (await import('./modules/admin/locales/fr.json')).default
|
|
186
|
+
},
|
|
187
|
+
dashboard: {
|
|
188
|
+
en: async () => (await import('./modules/dashboard/locales/en.json')).default,
|
|
189
|
+
fr: async () => (await import('./modules/dashboard/locales/fr.json')).default
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}}
|
|
193
|
+
supportedLocales={['en', 'fr', 'es']}
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
</ArcIntlProvider>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## 🔧 Utilisation Avancée
|
|
200
|
+
|
|
201
|
+
### Traductions avec paramètres
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// locales/en.json
|
|
205
|
+
{
|
|
206
|
+
"greeting": "Hello, {name}!",
|
|
207
|
+
"items_count": "You have {count} item{count, plural, one {} other {s}}"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Utilisation dans le composant
|
|
211
|
+
const Greeting = ({ userName, itemCount }) => {
|
|
212
|
+
const { t } = useTranslation();
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div>
|
|
216
|
+
<p>{t('greeting', { name: userName })}</p>
|
|
217
|
+
<p>{t('items_count', { count: itemCount })}</p>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Pluriels et contextes
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// locales/en.json
|
|
227
|
+
{
|
|
228
|
+
"messages": {
|
|
229
|
+
"unread": {
|
|
230
|
+
"one": "You have one unread message",
|
|
231
|
+
"other": "You have {count} unread messages"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Utilisation
|
|
237
|
+
const Notification = ({ unreadCount }) => {
|
|
238
|
+
const { t } = useTranslation();
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div>
|
|
242
|
+
{/* Pluriel automatique */}
|
|
243
|
+
<p>{t('messages.unread', { count: unreadCount })}</p>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Chargement dynamique de modules
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { useEffect } from 'react';
|
|
253
|
+
import { useTranslation } from '@jsarc/intl';
|
|
254
|
+
|
|
255
|
+
const AdminModule = () => {
|
|
256
|
+
const { loadModule, t, isModuleLoaded } = useTranslation('admin');
|
|
257
|
+
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
// Charger les traductions du module admin à la demande
|
|
260
|
+
if (!isModuleLoaded) {
|
|
261
|
+
loadModule('admin');
|
|
262
|
+
}
|
|
263
|
+
}, []);
|
|
264
|
+
|
|
265
|
+
if (!isModuleLoaded) return <div>Loading translations...</div>;
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
<h1>{t('admin.dashboard.title')}</h1>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## 🎯 Exemples Complets
|
|
276
|
+
|
|
277
|
+
### Exemple 1 : Sélecteur de langue
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { useTranslation } from '@jsarc/intl';
|
|
281
|
+
|
|
282
|
+
const LanguageSwitcher = () => {
|
|
283
|
+
const { currentLocale, changeLocale, t } = useTranslation();
|
|
284
|
+
|
|
285
|
+
const languages = [
|
|
286
|
+
{ code: 'en', name: 'English' },
|
|
287
|
+
{ code: 'fr', name: 'Français' },
|
|
288
|
+
{ code: 'es', name: 'Español' },
|
|
289
|
+
{ code: 'de', name: 'Deutsch' }
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div className="language-switcher">
|
|
294
|
+
<span>{t('common.language')}:</span>
|
|
295
|
+
<select
|
|
296
|
+
value={currentLocale}
|
|
297
|
+
onChange={(e) => changeLocale(e.target.value)}
|
|
298
|
+
>
|
|
299
|
+
{languages.map(lang => (
|
|
300
|
+
<option key={lang.code} value={lang.code}>
|
|
301
|
+
{lang.name}
|
|
302
|
+
</option>
|
|
303
|
+
))}
|
|
304
|
+
</select>
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Exemple 2 : Formulaire internationalisé
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { useTranslation } from '@jsarc/intl';
|
|
314
|
+
import { useState } from 'react';
|
|
315
|
+
|
|
316
|
+
const ContactForm = () => {
|
|
317
|
+
const { t } = useTranslation();
|
|
318
|
+
const [formData, setFormData] = useState({
|
|
319
|
+
name: '',
|
|
320
|
+
email: '',
|
|
321
|
+
message: ''
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const handleSubmit = (e) => {
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
// Soumettre le formulaire
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<form onSubmit={handleSubmit} className="contact-form">
|
|
331
|
+
<div className="form-group">
|
|
332
|
+
<label>{t('form.name')}</label>
|
|
333
|
+
<input
|
|
334
|
+
value={formData.name}
|
|
335
|
+
onChange={e => setFormData({...formData, name: e.target.value})}
|
|
336
|
+
placeholder={t('form.name_placeholder')}
|
|
337
|
+
/>
|
|
338
|
+
{!formData.name && (
|
|
339
|
+
<span className="error">{t('errors.required')}</span>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div className="form-group">
|
|
344
|
+
<label>{t('form.email')}</label>
|
|
345
|
+
<input
|
|
346
|
+
type="email"
|
|
347
|
+
value={formData.email}
|
|
348
|
+
onChange={e => setFormData({...formData, email: e.target.value})}
|
|
349
|
+
placeholder={t('form.email_placeholder')}
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<div className="form-group">
|
|
354
|
+
<label>{t('form.message')}</label>
|
|
355
|
+
<textarea
|
|
356
|
+
value={formData.message}
|
|
357
|
+
onChange={e => setFormData({...formData, message: e.target.value})}
|
|
358
|
+
placeholder={t('form.message_placeholder')}
|
|
359
|
+
rows={4}
|
|
360
|
+
/>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<button type="submit">
|
|
364
|
+
{t('form.submit')}
|
|
365
|
+
</button>
|
|
366
|
+
</form>
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Exemple 3 : Dashboard avec modules multiples
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { useTranslation } from '@jsarc/intl';
|
|
375
|
+
import { useEffect } from 'react';
|
|
376
|
+
|
|
377
|
+
const Dashboard = () => {
|
|
378
|
+
const { t, loadModule, currentLocale, isLoading } = useTranslation('admin');
|
|
379
|
+
|
|
380
|
+
// Charger les traductions de plusieurs modules
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
const loadModules = async () => {
|
|
383
|
+
await loadModule('admin');
|
|
384
|
+
await loadModule('analytics');
|
|
385
|
+
await loadModule('reports');
|
|
386
|
+
};
|
|
387
|
+
loadModules();
|
|
388
|
+
}, [currentLocale]);
|
|
389
|
+
|
|
390
|
+
if (isLoading) return <div>Loading translations...</div>;
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<div className="dashboard">
|
|
394
|
+
<header>
|
|
395
|
+
<h1>{t('admin.dashboard.title')}</h1>
|
|
396
|
+
<p>{t('common.welcome', { user: 'John Doe' })}</p>
|
|
397
|
+
</header>
|
|
398
|
+
|
|
399
|
+
<section className="stats">
|
|
400
|
+
<div className="stat-card">
|
|
401
|
+
<h3>{t('analytics.visitors', {}, { moduleName: 'analytics' })}</h3>
|
|
402
|
+
<p>1,234</p>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div className="stat-card">
|
|
406
|
+
<h3>{t('reports.monthly', {}, { moduleName: 'reports' })}</h3>
|
|
407
|
+
<p>{t('reports.growth', { percent: '15%' })}</p>
|
|
408
|
+
</div>
|
|
409
|
+
</section>
|
|
410
|
+
|
|
411
|
+
<footer>
|
|
412
|
+
<p>{t('common.last_updated', { date: new Date().toLocaleDateString(currentLocale) })}</p>
|
|
413
|
+
</footer>
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## 📋 API Reference
|
|
420
|
+
|
|
421
|
+
### ArcIntlProvider
|
|
422
|
+
| Prop | Type | Description | Required |
|
|
423
|
+
|------|------|-------------|----------|
|
|
424
|
+
| `translations` | `TranslationsConfig` | Configuration des chargeurs de traductions | Oui |
|
|
425
|
+
| `supportedLocales` | `string[]` | Locales supportées (défaut: ['en', 'fr']) | Non |
|
|
426
|
+
| `children` | `React.ReactNode` | Composants enfants | Oui |
|
|
427
|
+
|
|
428
|
+
### Hook useTranslation
|
|
429
|
+
Retourne un objet avec:
|
|
430
|
+
- `t(key: string, params?: Record<string, any>, options?: TranslationOptions)`: Fonction de traduction
|
|
431
|
+
- `changeLocale(locale: string)`: Changer la locale actuelle
|
|
432
|
+
- `currentLocale`: Locale actuelle
|
|
433
|
+
- `isLoading`: État de chargement global
|
|
434
|
+
- `isModuleLoaded`: Indique si le module demandé est chargé
|
|
435
|
+
- `loadModule(moduleName: string)`: Charge un module spécifique
|
|
436
|
+
|
|
437
|
+
### Options de Traduction
|
|
438
|
+
```typescript
|
|
439
|
+
interface TranslationOptions {
|
|
440
|
+
moduleName?: string; // Nom du module (défaut: 'core')
|
|
441
|
+
defaultValue?: string; // Valeur par défaut si clé non trouvée
|
|
442
|
+
count?: number; // Pour la gestion des pluriels
|
|
443
|
+
context?: string; // Contexte spécifique
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Structure de configuration
|
|
448
|
+
```typescript
|
|
449
|
+
interface TranslationsConfig {
|
|
450
|
+
base: {
|
|
451
|
+
[locale: string]: () => Promise<Record<string, any>>;
|
|
452
|
+
};
|
|
453
|
+
modules?: {
|
|
454
|
+
[moduleName: string]: {
|
|
455
|
+
[locale: string]: () => Promise<Record<string, any>>;
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## 🛡️ Gestion des Erreurs
|
|
462
|
+
|
|
463
|
+
### Fallback sécurisé
|
|
464
|
+
```typescript
|
|
465
|
+
const SafeComponent = () => {
|
|
466
|
+
const { t } = useTranslation();
|
|
467
|
+
|
|
468
|
+
// Utilisation sécurisée avec valeur par défaut
|
|
469
|
+
const title = t('nonexistent.key', {}, {
|
|
470
|
+
defaultValue: 'Default Title'
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Si pas de valeur par défaut, retourne la clé
|
|
474
|
+
const subtitle = t('another.missing.key'); // Retourne: "another.missing.key"
|
|
475
|
+
|
|
476
|
+
return (
|
|
477
|
+
<div>
|
|
478
|
+
<h1>{title}</h1>
|
|
479
|
+
<p>{subtitle}</p>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Logs en développement
|
|
486
|
+
```typescript
|
|
487
|
+
// En mode développement, les clés manquantes sont automatiquement loggées
|
|
488
|
+
const MissingTranslations = () => {
|
|
489
|
+
const { t } = useTranslation();
|
|
490
|
+
|
|
491
|
+
// Ceci loggue un avertissement en développement
|
|
492
|
+
const missingKey = t('non.existent.key');
|
|
493
|
+
|
|
494
|
+
return <div>{missingKey}</div>;
|
|
495
|
+
};
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## 🔧 Configuration TypeScript
|
|
499
|
+
|
|
500
|
+
```json
|
|
501
|
+
{
|
|
502
|
+
"compilerOptions": {
|
|
503
|
+
"target": "ES2020",
|
|
504
|
+
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
|
505
|
+
"module": "ESNext",
|
|
506
|
+
"moduleResolution": "bundler",
|
|
507
|
+
"allowImportingTsExtensions": true,
|
|
508
|
+
"resolveJsonModule": true,
|
|
509
|
+
"isolatedModules": true,
|
|
510
|
+
"noEmit": true,
|
|
511
|
+
"jsx": "react-jsx",
|
|
512
|
+
"strict": true,
|
|
513
|
+
"types": ["vite/client"]
|
|
514
|
+
},
|
|
515
|
+
"include": ["src", "node_modules/@jsarc/intl/**/*"]
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## 📋 Table des Conventions
|
|
520
|
+
|
|
521
|
+
### Structure des fichiers JSON
|
|
522
|
+
|
|
523
|
+
| Chemin | Description | Exemple |
|
|
524
|
+
|--------|-------------|---------|
|
|
525
|
+
| `locales/{locale}.json` | Traductions de base | `locales/en.json` |
|
|
526
|
+
| `modules/{module}/locales/{locale}.json` | Traductions du module | `modules/admin/locales/fr.json` |
|
|
527
|
+
| `public/locales/{locale}.json` | Traductions statiques (optionnel) | `public/locales/es.json` |
|
|
528
|
+
|
|
529
|
+
### Clés de traduction
|
|
530
|
+
|
|
531
|
+
| Format | Description | Exemple |
|
|
532
|
+
|--------|-------------|---------|
|
|
533
|
+
| `namespace.key` | Clé simple | `common.save` |
|
|
534
|
+
| `namespace.nested.key` | Clé imbriquée | `form.contact.email` |
|
|
535
|
+
| `key_{context}` | Avec contexte | `save_draft`, `save_final` |
|
|
536
|
+
|
|
537
|
+
### Paramètres supportés
|
|
538
|
+
|
|
539
|
+
| Paramètre | Type | Description |
|
|
540
|
+
|-----------|------|-------------|
|
|
541
|
+
| `count` | number | Pour la gestion des pluriels |
|
|
542
|
+
| `context` | string | Contexte spécifique (draft, final, etc.) |
|
|
543
|
+
| `moduleName` | string | Module cible pour la traduction |
|
|
544
|
+
| `defaultValue` | string | Valeur par défaut si la clé n'existe pas |
|
|
545
|
+
|
|
546
|
+
## 🔧 Build et Développement
|
|
547
|
+
|
|
548
|
+
### Scripts recommandés
|
|
549
|
+
|
|
550
|
+
```json
|
|
551
|
+
{
|
|
552
|
+
"scripts": {
|
|
553
|
+
"dev": "vite",
|
|
554
|
+
"build": "tsc && vite build",
|
|
555
|
+
"preview": "vite preview",
|
|
556
|
+
"type-check": "tsc --noEmit",
|
|
557
|
+
"extract-translations": "node scripts/extract-keys.js",
|
|
558
|
+
"validate-translations": "node scripts/validate-translations.js"
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Configuration Vite
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// vite.config.ts
|
|
567
|
+
import { defineConfig } from 'vite';
|
|
568
|
+
import react from '@vitejs/plugin-react';
|
|
569
|
+
|
|
570
|
+
export default defineConfig({
|
|
571
|
+
plugins: [react()],
|
|
572
|
+
resolve: {
|
|
573
|
+
alias: {
|
|
574
|
+
'@jsarc/intl': '@jsarc/intl/index.js'
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## 📄 Licence
|
|
581
|
+
|
|
582
|
+
MIT License - Voir le fichier [LICENSE](LICENSE) pour plus de détails.
|
|
583
|
+
|
|
584
|
+
## 🐛 Signaler un Bug
|
|
585
|
+
|
|
586
|
+
Envoyez-nous un mail à l'adresse `contact.inicode@gmail.com` pour :
|
|
587
|
+
- Signaler un bug
|
|
588
|
+
- Proposer une amélioration
|
|
589
|
+
- Poser une question sur l'utilisation
|
|
590
|
+
- Demander une nouvelle fonctionnalité
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
**@jsarc/intl** - La solution d'internationalisation modulaire pour React et TypeScript.
|
|
595
|
+
|
|
596
|
+
*Développé par l'équipe INICODE*
|
package/config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Cooks } from '@jsarc/cooks';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_LOCALE = 'en';
|
|
4
|
+
export const SUPPORTED_LOCALES: string[] = ['en', 'fr'] as const;
|
|
5
|
+
export const COOKIE_NAME = 'app_locale';
|
|
6
|
+
|
|
7
|
+
export type Locale = typeof SUPPORTED_LOCALES[number];
|
|
8
|
+
|
|
9
|
+
export interface TranslationMap {
|
|
10
|
+
[locale: string]: () => Promise<Record<string, any>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModuleTranslations {
|
|
14
|
+
[moduleName: string]: TranslationMap;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TranslationsConfig {
|
|
18
|
+
base: {
|
|
19
|
+
[locale: string]: () => Promise<Record<string, any>>;
|
|
20
|
+
};
|
|
21
|
+
modules?: ModuleTranslations;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const getSavedLocale = (supportedLocales: string[] = SUPPORTED_LOCALES): string => {
|
|
25
|
+
const saved = Cooks.get(COOKIE_NAME);
|
|
26
|
+
const supportedLocalesRes = (
|
|
27
|
+
typeof supportedLocales === 'object' &&
|
|
28
|
+
!!Array.isArray(supportedLocales) &&
|
|
29
|
+
supportedLocales.length > 0
|
|
30
|
+
) ? supportedLocales : SUPPORTED_LOCALES;
|
|
31
|
+
return supportedLocalesRes.includes(saved as string) ? saved as string : DEFAULT_LOCALE;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const saveLocale = (locale: string): void => {
|
|
35
|
+
Cooks.set(COOKIE_NAME, locale, { expires: 365 * 24 * 3600, sameSite: 'strict' });
|
|
36
|
+
};
|