@idealyst/translate 1.2.3
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 +773 -0
- package/package.json +77 -0
- package/src/babel/__tests__/extractor.test.ts +224 -0
- package/src/babel/__tests__/plugin.test.ts +289 -0
- package/src/babel/__tests__/reporter.test.ts +314 -0
- package/src/babel/extractor.ts +179 -0
- package/src/babel/index.ts +21 -0
- package/src/babel/plugin.js +545 -0
- package/src/babel/reporter.ts +287 -0
- package/src/babel/types.ts +214 -0
- package/src/components/Trans.tsx +72 -0
- package/src/components/index.ts +2 -0
- package/src/components/types.ts +64 -0
- package/src/config/index.ts +2 -0
- package/src/config/types.ts +10 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/types.ts +113 -0
- package/src/hooks/useLanguage.ts +73 -0
- package/src/hooks/useTranslation.ts +52 -0
- package/src/index.native.ts +2 -0
- package/src/index.ts +22 -0
- package/src/index.web.ts +24 -0
- package/src/provider/TranslateProvider.tsx +132 -0
- package/src/provider/index.ts +6 -0
- package/src/provider/types.ts +119 -0
- package/src/utils/__tests__/namespace.test.ts +211 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/namespace.ts +97 -0
package/README.md
ADDED
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
# @idealyst/translate
|
|
2
|
+
|
|
3
|
+
Cross-platform internationalization for the Idealyst Framework. Wraps `react-i18next` with a unified API and includes a Babel plugin for static translation key analysis.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Translation Files](#translation-files)
|
|
11
|
+
- [Runtime API](#runtime-api)
|
|
12
|
+
- [Babel Plugin](#babel-plugin)
|
|
13
|
+
- [CI/CD Integration](#cicd-integration)
|
|
14
|
+
- [Platform-Specific Setup](#platform-specific-setup)
|
|
15
|
+
- [Advanced Usage](#advanced-usage)
|
|
16
|
+
- [API Reference](#api-reference)
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Unified API** - Single API for React and React Native
|
|
21
|
+
- **Babel Plugin** - Static extraction of translation keys at build time
|
|
22
|
+
- **Missing Translation Detection** - Automatically detect keys missing translations
|
|
23
|
+
- **Unused Translation Detection** - Find translations not used in code
|
|
24
|
+
- **JSON Report** - Generate detailed reports for CI/CD integration
|
|
25
|
+
- **Namespace Support** - Organize translations with nested namespaces
|
|
26
|
+
- **Pluralization** - Full i18next pluralization support
|
|
27
|
+
- **Interpolation** - Variable interpolation in translations
|
|
28
|
+
- **Rich Text** - Component interpolation with the Trans component
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# npm
|
|
34
|
+
npm install @idealyst/translate react-i18next i18next
|
|
35
|
+
|
|
36
|
+
# yarn
|
|
37
|
+
yarn add @idealyst/translate react-i18next i18next
|
|
38
|
+
|
|
39
|
+
# pnpm
|
|
40
|
+
pnpm add @idealyst/translate react-i18next i18next
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### 1. Create Translation Files
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
locales/
|
|
49
|
+
├── en/
|
|
50
|
+
│ └── common.json
|
|
51
|
+
└── es/
|
|
52
|
+
└── common.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**locales/en/common.json**
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"welcome": {
|
|
59
|
+
"title": "Welcome to Our App",
|
|
60
|
+
"greeting": "Hello, {{name}}!"
|
|
61
|
+
},
|
|
62
|
+
"buttons": {
|
|
63
|
+
"submit": "Submit",
|
|
64
|
+
"cancel": "Cancel"
|
|
65
|
+
},
|
|
66
|
+
"items": "{{count}} item",
|
|
67
|
+
"items_plural": "{{count}} items"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**locales/es/common.json**
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"welcome": {
|
|
75
|
+
"title": "Bienvenido a Nuestra App",
|
|
76
|
+
"greeting": "¡Hola, {{name}}!"
|
|
77
|
+
},
|
|
78
|
+
"buttons": {
|
|
79
|
+
"submit": "Enviar",
|
|
80
|
+
"cancel": "Cancelar"
|
|
81
|
+
},
|
|
82
|
+
"items": "{{count}} artículo",
|
|
83
|
+
"items_plural": "{{count}} artículos"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Set Up the Provider
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// App.tsx
|
|
91
|
+
import { TranslateProvider } from '@idealyst/translate';
|
|
92
|
+
import en from './locales/en/common.json';
|
|
93
|
+
import es from './locales/es/common.json';
|
|
94
|
+
|
|
95
|
+
const config = {
|
|
96
|
+
defaultLanguage: 'en',
|
|
97
|
+
languages: ['en', 'es'],
|
|
98
|
+
resources: {
|
|
99
|
+
en: { common: en },
|
|
100
|
+
es: { common: es },
|
|
101
|
+
},
|
|
102
|
+
defaultNamespace: 'common',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function App() {
|
|
106
|
+
return (
|
|
107
|
+
<TranslateProvider config={config}>
|
|
108
|
+
<MyApp />
|
|
109
|
+
</TranslateProvider>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Use Translations
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// MyComponent.tsx
|
|
118
|
+
import { useTranslation } from '@idealyst/translate';
|
|
119
|
+
|
|
120
|
+
function MyComponent() {
|
|
121
|
+
const { t } = useTranslation('common');
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
{/* Simple translation */}
|
|
126
|
+
<h1>{t('welcome.title')}</h1>
|
|
127
|
+
|
|
128
|
+
{/* With interpolation */}
|
|
129
|
+
<p>{t('welcome.greeting', { name: 'John' })}</p>
|
|
130
|
+
|
|
131
|
+
{/* With pluralization */}
|
|
132
|
+
<p>{t('items', { count: 5 })}</p>
|
|
133
|
+
|
|
134
|
+
{/* Buttons */}
|
|
135
|
+
<button>{t('buttons.submit')}</button>
|
|
136
|
+
<button>{t('buttons.cancel')}</button>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 4. Language Switching
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { useLanguage } from '@idealyst/translate';
|
|
146
|
+
|
|
147
|
+
function LanguageSwitcher() {
|
|
148
|
+
const { language, languages, setLanguage, getDisplayName } = useLanguage();
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
|
|
152
|
+
{languages.map((lang) => (
|
|
153
|
+
<option key={lang} value={lang}>
|
|
154
|
+
{getDisplayName(lang)}
|
|
155
|
+
</option>
|
|
156
|
+
))}
|
|
157
|
+
</select>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Translation Files
|
|
163
|
+
|
|
164
|
+
### Directory Structure
|
|
165
|
+
|
|
166
|
+
Organize translations by language and namespace:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
locales/
|
|
170
|
+
├── en/
|
|
171
|
+
│ ├── common.json # Common UI strings
|
|
172
|
+
│ ├── auth.json # Authentication strings
|
|
173
|
+
│ └── errors.json # Error messages
|
|
174
|
+
├── es/
|
|
175
|
+
│ ├── common.json
|
|
176
|
+
│ ├── auth.json
|
|
177
|
+
│ └── errors.json
|
|
178
|
+
└── fr/
|
|
179
|
+
├── common.json
|
|
180
|
+
├── auth.json
|
|
181
|
+
└── errors.json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### JSON Format
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"namespace.key": "value",
|
|
189
|
+
|
|
190
|
+
"nested": {
|
|
191
|
+
"keys": {
|
|
192
|
+
"work": "Like this"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
"interpolation": "Hello, {{name}}!",
|
|
197
|
+
|
|
198
|
+
"plural": "{{count}} item",
|
|
199
|
+
"plural_plural": "{{count}} items",
|
|
200
|
+
|
|
201
|
+
"context_male": "He liked it",
|
|
202
|
+
"context_female": "She liked it",
|
|
203
|
+
|
|
204
|
+
"richText": "Read our <terms>Terms</terms> and <privacy>Privacy Policy</privacy>"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Key Formats
|
|
209
|
+
|
|
210
|
+
The plugin supports two key formats:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// Namespace:key format (i18next standard)
|
|
214
|
+
t('auth:login.title')
|
|
215
|
+
|
|
216
|
+
// Namespace.key format (first segment is namespace)
|
|
217
|
+
t('auth.login.title')
|
|
218
|
+
|
|
219
|
+
// Both resolve to:
|
|
220
|
+
// namespace: "auth"
|
|
221
|
+
// localKey: "login.title"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Runtime API
|
|
225
|
+
|
|
226
|
+
### TranslateProvider
|
|
227
|
+
|
|
228
|
+
Wrap your app with the provider:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { TranslateProvider } from '@idealyst/translate';
|
|
232
|
+
|
|
233
|
+
<TranslateProvider
|
|
234
|
+
config={{
|
|
235
|
+
defaultLanguage: 'en',
|
|
236
|
+
languages: ['en', 'es', 'fr'],
|
|
237
|
+
resources: {
|
|
238
|
+
en: { common: enCommon, auth: enAuth },
|
|
239
|
+
es: { common: esCommon, auth: esAuth },
|
|
240
|
+
},
|
|
241
|
+
defaultNamespace: 'common',
|
|
242
|
+
fallbackLanguage: 'en',
|
|
243
|
+
debug: false,
|
|
244
|
+
}}
|
|
245
|
+
onInitialized={(i18n) => console.log('i18n ready')}
|
|
246
|
+
onLanguageChanged={(lang) => console.log('Language:', lang)}
|
|
247
|
+
>
|
|
248
|
+
<App />
|
|
249
|
+
</TranslateProvider>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### useTranslation Hook
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { useTranslation } from '@idealyst/translate';
|
|
256
|
+
|
|
257
|
+
function Component() {
|
|
258
|
+
const { t, language, languages, ready, i18n } = useTranslation('common');
|
|
259
|
+
|
|
260
|
+
// Simple translation
|
|
261
|
+
const title = t('welcome.title');
|
|
262
|
+
|
|
263
|
+
// With interpolation
|
|
264
|
+
const greeting = t('welcome.greeting', { name: 'World' });
|
|
265
|
+
|
|
266
|
+
// With default value
|
|
267
|
+
const fallback = t('missing.key', { defaultValue: 'Fallback text' });
|
|
268
|
+
|
|
269
|
+
// With pluralization
|
|
270
|
+
const items = t('items', { count: 5 });
|
|
271
|
+
|
|
272
|
+
// With context
|
|
273
|
+
const gendered = t('liked', { context: 'male' });
|
|
274
|
+
|
|
275
|
+
return <div>{title}</div>;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### useLanguage Hook
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
import { useLanguage } from '@idealyst/translate';
|
|
283
|
+
|
|
284
|
+
function LanguageControls() {
|
|
285
|
+
const {
|
|
286
|
+
language, // Current language: 'en'
|
|
287
|
+
languages, // Available: ['en', 'es', 'fr']
|
|
288
|
+
setLanguage, // Change language
|
|
289
|
+
isSupported, // Check if language is available
|
|
290
|
+
getDisplayName, // Get display name: 'English', 'Español'
|
|
291
|
+
} = useLanguage();
|
|
292
|
+
|
|
293
|
+
const handleChange = async (newLang: string) => {
|
|
294
|
+
if (isSupported(newLang)) {
|
|
295
|
+
await setLanguage(newLang);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<div>
|
|
301
|
+
<p>Current: {getDisplayName(language)}</p>
|
|
302
|
+
<button onClick={() => handleChange('es')}>Switch to Spanish</button>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Trans Component
|
|
309
|
+
|
|
310
|
+
For rich text with embedded components:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { Trans } from '@idealyst/translate';
|
|
314
|
+
|
|
315
|
+
function RichText() {
|
|
316
|
+
return (
|
|
317
|
+
<Trans
|
|
318
|
+
i18nKey="common.richText"
|
|
319
|
+
components={{
|
|
320
|
+
terms: <a href="/terms" />,
|
|
321
|
+
privacy: <a href="/privacy" />,
|
|
322
|
+
bold: <strong />,
|
|
323
|
+
}}
|
|
324
|
+
values={{ name: 'User' }}
|
|
325
|
+
/>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// With translation:
|
|
330
|
+
// "richText": "Read our <terms>Terms</terms> and <privacy>Privacy Policy</privacy>, <bold>{{name}}</bold>!"
|
|
331
|
+
//
|
|
332
|
+
// Renders:
|
|
333
|
+
// Read our <a href="/terms">Terms</a> and <a href="/privacy">Privacy Policy</a>, <strong>User</strong>!
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Babel Plugin
|
|
337
|
+
|
|
338
|
+
The Babel plugin extracts translation keys at build time and generates a report of missing/unused translations.
|
|
339
|
+
|
|
340
|
+
### Configuration
|
|
341
|
+
|
|
342
|
+
**babel.config.js**
|
|
343
|
+
```javascript
|
|
344
|
+
module.exports = {
|
|
345
|
+
presets: ['@babel/preset-react', '@babel/preset-typescript'],
|
|
346
|
+
plugins: [
|
|
347
|
+
['@idealyst/translate/plugin', {
|
|
348
|
+
// Required: paths to translation JSON files (supports glob)
|
|
349
|
+
translationFiles: ['./locales/**/*.json'],
|
|
350
|
+
|
|
351
|
+
// Optional: output path for the report
|
|
352
|
+
reportPath: '.idealyst/translations-report.json',
|
|
353
|
+
|
|
354
|
+
// Optional: default namespace when not specified in key
|
|
355
|
+
defaultNamespace: 'common',
|
|
356
|
+
|
|
357
|
+
// Optional: emit console warnings for missing translations
|
|
358
|
+
emitWarnings: true,
|
|
359
|
+
|
|
360
|
+
// Optional: fail build if missing translations found
|
|
361
|
+
failOnMissing: false,
|
|
362
|
+
|
|
363
|
+
// Optional: verbose logging
|
|
364
|
+
verbose: false,
|
|
365
|
+
}],
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**For Vite (vite.config.ts)**
|
|
371
|
+
```typescript
|
|
372
|
+
import { defineConfig } from 'vite';
|
|
373
|
+
import react from '@vitejs/plugin-react';
|
|
374
|
+
|
|
375
|
+
export default defineConfig({
|
|
376
|
+
plugins: [
|
|
377
|
+
react({
|
|
378
|
+
babel: {
|
|
379
|
+
plugins: [
|
|
380
|
+
['@idealyst/translate/plugin', {
|
|
381
|
+
translationFiles: ['./locales/**/*.json'],
|
|
382
|
+
reportPath: '.idealyst/translations-report.json',
|
|
383
|
+
defaultNamespace: 'common',
|
|
384
|
+
}],
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
],
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**For React Native (babel.config.js)**
|
|
393
|
+
```javascript
|
|
394
|
+
module.exports = {
|
|
395
|
+
presets: ['module:@react-native/babel-preset'],
|
|
396
|
+
plugins: [
|
|
397
|
+
['@idealyst/translate/plugin', {
|
|
398
|
+
translationFiles: ['./locales/**/*.json'],
|
|
399
|
+
reportPath: '.idealyst/translations-report.json',
|
|
400
|
+
defaultNamespace: 'common',
|
|
401
|
+
emitWarnings: true,
|
|
402
|
+
}],
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### What the Plugin Extracts
|
|
408
|
+
|
|
409
|
+
The plugin statically analyzes your code for:
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
// t() function calls
|
|
413
|
+
t('common.key')
|
|
414
|
+
t('namespace:key')
|
|
415
|
+
t('key', { defaultValue: 'Default' })
|
|
416
|
+
|
|
417
|
+
// i18n.t() method calls
|
|
418
|
+
i18n.t('common.key')
|
|
419
|
+
|
|
420
|
+
// Trans component
|
|
421
|
+
<Trans i18nKey="common.richText" />
|
|
422
|
+
<Trans i18nKey={"common.richText"} />
|
|
423
|
+
|
|
424
|
+
// Dynamic keys are tracked but marked as such
|
|
425
|
+
const key = `common.${type}`;
|
|
426
|
+
t(key); // Marked as isDynamic: true
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Report Output
|
|
430
|
+
|
|
431
|
+
The plugin generates `.idealyst/translations-report.json`:
|
|
432
|
+
|
|
433
|
+
```json
|
|
434
|
+
{
|
|
435
|
+
"timestamp": "2026-01-08T12:00:00.000Z",
|
|
436
|
+
"totalKeys": 45,
|
|
437
|
+
"dynamicKeys": [
|
|
438
|
+
{
|
|
439
|
+
"key": "<dynamic>",
|
|
440
|
+
"file": "src/DynamicComponent.tsx",
|
|
441
|
+
"line": 15,
|
|
442
|
+
"isDynamic": true
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
"extractedKeys": [
|
|
446
|
+
{
|
|
447
|
+
"key": "common.buttons.submit",
|
|
448
|
+
"namespace": "common",
|
|
449
|
+
"localKey": "buttons.submit",
|
|
450
|
+
"file": "src/Form.tsx",
|
|
451
|
+
"line": 42,
|
|
452
|
+
"column": 12,
|
|
453
|
+
"defaultValue": "Submit",
|
|
454
|
+
"isDynamic": false
|
|
455
|
+
}
|
|
456
|
+
],
|
|
457
|
+
"languages": ["en", "es", "fr"],
|
|
458
|
+
"missing": {
|
|
459
|
+
"en": [],
|
|
460
|
+
"es": [
|
|
461
|
+
{
|
|
462
|
+
"key": "common.buttons.submit",
|
|
463
|
+
"namespace": "common",
|
|
464
|
+
"usedIn": [
|
|
465
|
+
{ "file": "src/Form.tsx", "line": 42, "column": 12 }
|
|
466
|
+
],
|
|
467
|
+
"defaultValue": "Submit"
|
|
468
|
+
}
|
|
469
|
+
],
|
|
470
|
+
"fr": []
|
|
471
|
+
},
|
|
472
|
+
"unused": {
|
|
473
|
+
"en": ["common.legacy.oldFeature"],
|
|
474
|
+
"es": [],
|
|
475
|
+
"fr": []
|
|
476
|
+
},
|
|
477
|
+
"summary": {
|
|
478
|
+
"totalMissing": 1,
|
|
479
|
+
"totalUnused": 1,
|
|
480
|
+
"coveragePercent": {
|
|
481
|
+
"en": 100,
|
|
482
|
+
"es": 98,
|
|
483
|
+
"fr": 100
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## CI/CD Integration
|
|
490
|
+
|
|
491
|
+
### GitHub Actions Example
|
|
492
|
+
|
|
493
|
+
```yaml
|
|
494
|
+
name: Translation Check
|
|
495
|
+
|
|
496
|
+
on: [push, pull_request]
|
|
497
|
+
|
|
498
|
+
jobs:
|
|
499
|
+
check-translations:
|
|
500
|
+
runs-on: ubuntu-latest
|
|
501
|
+
steps:
|
|
502
|
+
- uses: actions/checkout@v3
|
|
503
|
+
|
|
504
|
+
- name: Setup Node
|
|
505
|
+
uses: actions/setup-node@v3
|
|
506
|
+
with:
|
|
507
|
+
node-version: '20'
|
|
508
|
+
|
|
509
|
+
- name: Install dependencies
|
|
510
|
+
run: yarn install
|
|
511
|
+
|
|
512
|
+
- name: Build (generates translation report)
|
|
513
|
+
run: yarn build
|
|
514
|
+
|
|
515
|
+
- name: Check for missing translations
|
|
516
|
+
run: |
|
|
517
|
+
MISSING=$(jq '.summary.totalMissing' .idealyst/translations-report.json)
|
|
518
|
+
if [ "$MISSING" -gt 0 ]; then
|
|
519
|
+
echo "Missing translations found: $MISSING"
|
|
520
|
+
jq '.missing' .idealyst/translations-report.json
|
|
521
|
+
exit 1
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
- name: Check coverage threshold
|
|
525
|
+
run: |
|
|
526
|
+
jq -e '.summary.coveragePercent | to_entries | all(.value >= 95)' \
|
|
527
|
+
.idealyst/translations-report.json || \
|
|
528
|
+
(echo "Translation coverage below 95%" && exit 1)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Shell Script
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
#!/bin/bash
|
|
535
|
+
|
|
536
|
+
# Build and generate report
|
|
537
|
+
yarn build
|
|
538
|
+
|
|
539
|
+
# Check for missing translations
|
|
540
|
+
MISSING=$(jq '.summary.totalMissing' .idealyst/translations-report.json)
|
|
541
|
+
|
|
542
|
+
if [ "$MISSING" -gt 0 ]; then
|
|
543
|
+
echo "ERROR: $MISSING missing translation(s) found"
|
|
544
|
+
echo ""
|
|
545
|
+
echo "Missing translations:"
|
|
546
|
+
jq -r '.missing | to_entries[] | select(.value | length > 0) | "\(.key): \(.value | length) missing"' \
|
|
547
|
+
.idealyst/translations-report.json
|
|
548
|
+
exit 1
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
echo "All translations present!"
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Platform-Specific Setup
|
|
555
|
+
|
|
556
|
+
### React (Vite)
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// vite.config.ts
|
|
560
|
+
import { defineConfig } from 'vite';
|
|
561
|
+
import react from '@vitejs/plugin-react';
|
|
562
|
+
|
|
563
|
+
export default defineConfig({
|
|
564
|
+
plugins: [
|
|
565
|
+
react({
|
|
566
|
+
babel: {
|
|
567
|
+
plugins: [
|
|
568
|
+
['@idealyst/translate/plugin', {
|
|
569
|
+
translationFiles: ['./src/locales/**/*.json'],
|
|
570
|
+
reportPath: '.idealyst/translations-report.json',
|
|
571
|
+
}],
|
|
572
|
+
],
|
|
573
|
+
},
|
|
574
|
+
}),
|
|
575
|
+
],
|
|
576
|
+
});
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### React Native
|
|
580
|
+
|
|
581
|
+
```javascript
|
|
582
|
+
// babel.config.js
|
|
583
|
+
module.exports = {
|
|
584
|
+
presets: ['module:@react-native/babel-preset'],
|
|
585
|
+
plugins: [
|
|
586
|
+
['@idealyst/translate/plugin', {
|
|
587
|
+
translationFiles: ['./src/locales/**/*.json'],
|
|
588
|
+
reportPath: '.idealyst/translations-report.json',
|
|
589
|
+
}],
|
|
590
|
+
],
|
|
591
|
+
};
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Next.js
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
// next.config.js
|
|
598
|
+
module.exports = {
|
|
599
|
+
// ... other config
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// babel.config.js
|
|
603
|
+
module.exports = {
|
|
604
|
+
presets: ['next/babel'],
|
|
605
|
+
plugins: [
|
|
606
|
+
['@idealyst/translate/plugin', {
|
|
607
|
+
translationFiles: ['./locales/**/*.json'],
|
|
608
|
+
reportPath: '.idealyst/translations-report.json',
|
|
609
|
+
}],
|
|
610
|
+
],
|
|
611
|
+
};
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Advanced Usage
|
|
615
|
+
|
|
616
|
+
### Multiple Namespaces
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
// Load multiple namespaces
|
|
620
|
+
const { t } = useTranslation(['common', 'auth']);
|
|
621
|
+
|
|
622
|
+
// Use specific namespace
|
|
623
|
+
t('common:buttons.submit')
|
|
624
|
+
t('auth:login.title')
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
### Lazy Loading Namespaces
|
|
628
|
+
|
|
629
|
+
```tsx
|
|
630
|
+
import { useTranslation } from '@idealyst/translate';
|
|
631
|
+
|
|
632
|
+
function LazyComponent() {
|
|
633
|
+
// Namespace loaded on demand
|
|
634
|
+
const { t, ready } = useTranslation('largeNamespace');
|
|
635
|
+
|
|
636
|
+
if (!ready) return <Loading />;
|
|
637
|
+
|
|
638
|
+
return <div>{t('content')}</div>;
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Detecting Language from Browser/Device
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
const config = {
|
|
646
|
+
defaultLanguage: navigator.language.split('-')[0] || 'en',
|
|
647
|
+
languages: ['en', 'es', 'fr'],
|
|
648
|
+
// ...
|
|
649
|
+
};
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Persisting Language Preference
|
|
653
|
+
|
|
654
|
+
```tsx
|
|
655
|
+
import { useLanguage } from '@idealyst/translate';
|
|
656
|
+
import { useEffect } from 'react';
|
|
657
|
+
|
|
658
|
+
function LanguagePersistence() {
|
|
659
|
+
const { language, setLanguage } = useLanguage();
|
|
660
|
+
|
|
661
|
+
// Load saved preference on mount
|
|
662
|
+
useEffect(() => {
|
|
663
|
+
const saved = localStorage.getItem('language');
|
|
664
|
+
if (saved) setLanguage(saved);
|
|
665
|
+
}, []);
|
|
666
|
+
|
|
667
|
+
// Save preference when changed
|
|
668
|
+
useEffect(() => {
|
|
669
|
+
localStorage.setItem('language', language);
|
|
670
|
+
}, [language]);
|
|
671
|
+
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## API Reference
|
|
677
|
+
|
|
678
|
+
### TranslateConfig
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
interface TranslateConfig {
|
|
682
|
+
/** Default language code */
|
|
683
|
+
defaultLanguage: string;
|
|
684
|
+
|
|
685
|
+
/** Supported language codes */
|
|
686
|
+
languages: string[];
|
|
687
|
+
|
|
688
|
+
/** Pre-loaded translation resources */
|
|
689
|
+
resources?: Record<string, Record<string, object>>;
|
|
690
|
+
|
|
691
|
+
/** Default namespace */
|
|
692
|
+
defaultNamespace?: string;
|
|
693
|
+
|
|
694
|
+
/** Fallback language when key missing */
|
|
695
|
+
fallbackLanguage?: string;
|
|
696
|
+
|
|
697
|
+
/** Enable debug logging */
|
|
698
|
+
debug?: boolean;
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### TranslatePluginOptions
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
interface TranslatePluginOptions {
|
|
706
|
+
/** Paths to translation files (glob patterns supported) */
|
|
707
|
+
translationFiles: string[];
|
|
708
|
+
|
|
709
|
+
/** Output path for report (default: '.idealyst/translations-report.json') */
|
|
710
|
+
reportPath?: string;
|
|
711
|
+
|
|
712
|
+
/** Languages to check (default: inferred from files) */
|
|
713
|
+
languages?: string[];
|
|
714
|
+
|
|
715
|
+
/** Default namespace (default: 'translation') */
|
|
716
|
+
defaultNamespace?: string;
|
|
717
|
+
|
|
718
|
+
/** Fail build on missing translations (default: false) */
|
|
719
|
+
failOnMissing?: boolean;
|
|
720
|
+
|
|
721
|
+
/** Emit console warnings (default: true) */
|
|
722
|
+
emitWarnings?: boolean;
|
|
723
|
+
|
|
724
|
+
/** Verbose logging (default: false) */
|
|
725
|
+
verbose?: boolean;
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### useTranslation Return Value
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
interface UseTranslationResult {
|
|
733
|
+
/** Translation function */
|
|
734
|
+
t: (key: string, options?: TranslationOptions) => string;
|
|
735
|
+
|
|
736
|
+
/** Current language code */
|
|
737
|
+
language: string;
|
|
738
|
+
|
|
739
|
+
/** All available languages */
|
|
740
|
+
languages: string[];
|
|
741
|
+
|
|
742
|
+
/** Whether translations are loaded */
|
|
743
|
+
ready: boolean;
|
|
744
|
+
|
|
745
|
+
/** i18next instance for advanced usage */
|
|
746
|
+
i18n: i18n;
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### useLanguage Return Value
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
interface UseLanguageResult {
|
|
754
|
+
/** Current language code */
|
|
755
|
+
language: string;
|
|
756
|
+
|
|
757
|
+
/** Available language codes */
|
|
758
|
+
languages: string[];
|
|
759
|
+
|
|
760
|
+
/** Change the current language */
|
|
761
|
+
setLanguage: (lang: string) => Promise<void>;
|
|
762
|
+
|
|
763
|
+
/** Check if a language is supported */
|
|
764
|
+
isSupported: (lang: string) => boolean;
|
|
765
|
+
|
|
766
|
+
/** Get display name for a language code */
|
|
767
|
+
getDisplayName: (lang: string) => string;
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
## License
|
|
772
|
+
|
|
773
|
+
MIT
|