@idealyst/mcp-server 1.2.7 → 1.2.8
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/dist/data/components/Button.d.ts.map +1 -1
- package/dist/data/components/Button.js +5 -6
- package/dist/data/components/Button.js.map +1 -1
- package/dist/data/components/Input.d.ts.map +1 -1
- package/dist/data/components/Input.js +4 -3
- package/dist/data/components/Input.js.map +1 -1
- package/dist/data/components/Text.d.ts.map +1 -1
- package/dist/data/components/Text.js +26 -19
- package/dist/data/components/Text.js.map +1 -1
- package/dist/data/framework-guides.d.ts.map +1 -1
- package/dist/data/framework-guides.js +842 -174
- package/dist/data/framework-guides.js.map +1 -1
- package/dist/data/translate-guides.d.ts +2 -0
- package/dist/data/translate-guides.d.ts.map +1 -0
- package/dist/data/translate-guides.js +1030 -0
- package/dist/data/translate-guides.js.map +1 -0
- package/dist/generated/types.json +247 -243
- package/dist/index.js +71 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/get-types.d.ts +11 -0
- package/dist/tools/get-types.d.ts.map +1 -1
- package/dist/tools/get-types.js +25 -0
- package/dist/tools/get-types.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
export const translateGuides = {
|
|
2
|
+
"idealyst://translate/overview": `# @idealyst/translate Overview
|
|
3
|
+
|
|
4
|
+
Cross-platform internationalization for the Idealyst Framework. Wraps \`react-i18next\` with a unified API and includes a Babel plugin for static translation key analysis.
|
|
5
|
+
|
|
6
|
+
## Core Features
|
|
7
|
+
|
|
8
|
+
- **Unified API** - Single API for React and React Native
|
|
9
|
+
- **Babel Plugin** - Static extraction of translation keys at build time
|
|
10
|
+
- **Missing Translation Detection** - Automatically detect keys missing translations
|
|
11
|
+
- **Unused Translation Detection** - Find translations not used in code
|
|
12
|
+
- **JSON Report** - Generate detailed reports for CI/CD integration
|
|
13
|
+
- **Namespace Support** - Organize translations with nested namespaces
|
|
14
|
+
- **Pluralization** - Full i18next pluralization support
|
|
15
|
+
- **Interpolation** - Variable interpolation in translations
|
|
16
|
+
- **Rich Text** - Component interpolation with the Trans component
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
\`\`\`bash
|
|
21
|
+
yarn add @idealyst/translate react-i18next i18next
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Create Translation Files
|
|
27
|
+
|
|
28
|
+
\`\`\`
|
|
29
|
+
locales/
|
|
30
|
+
├── en/
|
|
31
|
+
│ └── common.json
|
|
32
|
+
└── es/
|
|
33
|
+
└── common.json
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
**locales/en/common.json**
|
|
37
|
+
\`\`\`json
|
|
38
|
+
{
|
|
39
|
+
"welcome": {
|
|
40
|
+
"title": "Welcome to Our App",
|
|
41
|
+
"greeting": "Hello, {{name}}!"
|
|
42
|
+
},
|
|
43
|
+
"buttons": {
|
|
44
|
+
"submit": "Submit",
|
|
45
|
+
"cancel": "Cancel"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
### 2. Set Up the Provider
|
|
51
|
+
|
|
52
|
+
\`\`\`tsx
|
|
53
|
+
import { TranslateProvider } from '@idealyst/translate';
|
|
54
|
+
import en from './locales/en/common.json';
|
|
55
|
+
import es from './locales/es/common.json';
|
|
56
|
+
|
|
57
|
+
const config = {
|
|
58
|
+
defaultLanguage: 'en',
|
|
59
|
+
languages: ['en', 'es'],
|
|
60
|
+
resources: {
|
|
61
|
+
en: { common: en },
|
|
62
|
+
es: { common: es },
|
|
63
|
+
},
|
|
64
|
+
defaultNamespace: 'common',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function App() {
|
|
68
|
+
return (
|
|
69
|
+
<TranslateProvider config={config}>
|
|
70
|
+
<MyApp />
|
|
71
|
+
</TranslateProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
\`\`\`
|
|
75
|
+
|
|
76
|
+
### 3. Use Translations
|
|
77
|
+
|
|
78
|
+
\`\`\`tsx
|
|
79
|
+
import { useTranslation } from '@idealyst/translate';
|
|
80
|
+
|
|
81
|
+
function MyComponent() {
|
|
82
|
+
const { t } = useTranslation('common');
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<h1>{t('welcome.title')}</h1>
|
|
87
|
+
<p>{t('welcome.greeting', { name: 'John' })}</p>
|
|
88
|
+
<button>{t('buttons.submit')}</button>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
## Key Concepts
|
|
95
|
+
|
|
96
|
+
### Key Formats
|
|
97
|
+
The package supports two key formats:
|
|
98
|
+
|
|
99
|
+
\`\`\`tsx
|
|
100
|
+
// Namespace:key format (i18next standard)
|
|
101
|
+
t('auth:login.title')
|
|
102
|
+
|
|
103
|
+
// Namespace.key format (first segment is namespace)
|
|
104
|
+
t('auth.login.title')
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
### Namespace Organization
|
|
108
|
+
Organize translations by domain:
|
|
109
|
+
- \`common.json\` - Shared UI strings
|
|
110
|
+
- \`auth.json\` - Authentication strings
|
|
111
|
+
- \`errors.json\` - Error messages
|
|
112
|
+
- \`forms.json\` - Form labels and validation
|
|
113
|
+
`,
|
|
114
|
+
"idealyst://translate/runtime-api": `# Runtime API Reference
|
|
115
|
+
|
|
116
|
+
Complete reference for the @idealyst/translate runtime API.
|
|
117
|
+
|
|
118
|
+
## TranslateProvider
|
|
119
|
+
|
|
120
|
+
Wrap your app with the provider to enable translations:
|
|
121
|
+
|
|
122
|
+
\`\`\`tsx
|
|
123
|
+
import { TranslateProvider } from '@idealyst/translate';
|
|
124
|
+
|
|
125
|
+
<TranslateProvider
|
|
126
|
+
config={{
|
|
127
|
+
defaultLanguage: 'en',
|
|
128
|
+
languages: ['en', 'es', 'fr'],
|
|
129
|
+
resources: {
|
|
130
|
+
en: { common: enCommon, auth: enAuth },
|
|
131
|
+
es: { common: esCommon, auth: esAuth },
|
|
132
|
+
},
|
|
133
|
+
defaultNamespace: 'common',
|
|
134
|
+
fallbackLanguage: 'en',
|
|
135
|
+
debug: false,
|
|
136
|
+
}}
|
|
137
|
+
onInitialized={(i18n) => console.log('i18n ready')}
|
|
138
|
+
onLanguageChanged={(lang) => console.log('Language:', lang)}
|
|
139
|
+
>
|
|
140
|
+
<App />
|
|
141
|
+
</TranslateProvider>
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
### TranslateConfig Options
|
|
145
|
+
|
|
146
|
+
| Option | Type | Required | Description |
|
|
147
|
+
|--------|------|----------|-------------|
|
|
148
|
+
| \`defaultLanguage\` | string | Yes | Default language code |
|
|
149
|
+
| \`languages\` | string[] | Yes | Supported language codes |
|
|
150
|
+
| \`resources\` | object | No | Pre-loaded translation resources |
|
|
151
|
+
| \`defaultNamespace\` | string | No | Default namespace (default: 'translation') |
|
|
152
|
+
| \`fallbackLanguage\` | string | No | Fallback when key missing |
|
|
153
|
+
| \`debug\` | boolean | No | Enable debug logging |
|
|
154
|
+
|
|
155
|
+
## useTranslation Hook
|
|
156
|
+
|
|
157
|
+
The main hook for accessing translations:
|
|
158
|
+
|
|
159
|
+
\`\`\`tsx
|
|
160
|
+
import { useTranslation } from '@idealyst/translate';
|
|
161
|
+
|
|
162
|
+
function Component() {
|
|
163
|
+
const { t, language, languages, ready, i18n } = useTranslation('common');
|
|
164
|
+
|
|
165
|
+
// Simple translation
|
|
166
|
+
const title = t('welcome.title');
|
|
167
|
+
|
|
168
|
+
// With interpolation
|
|
169
|
+
const greeting = t('welcome.greeting', { name: 'World' });
|
|
170
|
+
|
|
171
|
+
// With default value
|
|
172
|
+
const fallback = t('missing.key', { defaultValue: 'Fallback text' });
|
|
173
|
+
|
|
174
|
+
// With pluralization
|
|
175
|
+
const items = t('items', { count: 5 });
|
|
176
|
+
|
|
177
|
+
// With context
|
|
178
|
+
const gendered = t('liked', { context: 'male' });
|
|
179
|
+
|
|
180
|
+
return <div>{title}</div>;
|
|
181
|
+
}
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
### Return Values
|
|
185
|
+
|
|
186
|
+
| Property | Type | Description |
|
|
187
|
+
|----------|------|-------------|
|
|
188
|
+
| \`t\` | function | Translation function |
|
|
189
|
+
| \`language\` | string | Current language code |
|
|
190
|
+
| \`languages\` | string[] | All available languages |
|
|
191
|
+
| \`ready\` | boolean | Whether translations are loaded |
|
|
192
|
+
| \`i18n\` | i18n | i18next instance for advanced usage |
|
|
193
|
+
|
|
194
|
+
### Translation Options
|
|
195
|
+
|
|
196
|
+
\`\`\`tsx
|
|
197
|
+
t('key', {
|
|
198
|
+
// Default value if key not found
|
|
199
|
+
defaultValue: 'Fallback',
|
|
200
|
+
|
|
201
|
+
// Interpolation values
|
|
202
|
+
name: 'John',
|
|
203
|
+
count: 5,
|
|
204
|
+
|
|
205
|
+
// Pluralization count
|
|
206
|
+
count: 3,
|
|
207
|
+
|
|
208
|
+
// Context for contextual translations
|
|
209
|
+
context: 'male',
|
|
210
|
+
});
|
|
211
|
+
\`\`\`
|
|
212
|
+
|
|
213
|
+
## useLanguage Hook
|
|
214
|
+
|
|
215
|
+
For language management:
|
|
216
|
+
|
|
217
|
+
\`\`\`tsx
|
|
218
|
+
import { useLanguage } from '@idealyst/translate';
|
|
219
|
+
|
|
220
|
+
function LanguageControls() {
|
|
221
|
+
const {
|
|
222
|
+
language, // Current: 'en'
|
|
223
|
+
languages, // Available: ['en', 'es', 'fr']
|
|
224
|
+
setLanguage, // Change language
|
|
225
|
+
isSupported, // Check availability
|
|
226
|
+
getDisplayName, // Get 'English', 'Español'
|
|
227
|
+
} = useLanguage();
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
|
|
231
|
+
{languages.map((lang) => (
|
|
232
|
+
<option key={lang} value={lang}>
|
|
233
|
+
{getDisplayName(lang)}
|
|
234
|
+
</option>
|
|
235
|
+
))}
|
|
236
|
+
</select>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
## Trans Component
|
|
242
|
+
|
|
243
|
+
For rich text with embedded components:
|
|
244
|
+
|
|
245
|
+
\`\`\`tsx
|
|
246
|
+
import { Trans } from '@idealyst/translate';
|
|
247
|
+
|
|
248
|
+
function RichText() {
|
|
249
|
+
return (
|
|
250
|
+
<Trans
|
|
251
|
+
i18nKey="common.richText"
|
|
252
|
+
components={{
|
|
253
|
+
terms: <a href="/terms" />,
|
|
254
|
+
privacy: <a href="/privacy" />,
|
|
255
|
+
bold: <strong />,
|
|
256
|
+
}}
|
|
257
|
+
values={{ name: 'User' }}
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Translation:
|
|
263
|
+
// "richText": "Read our <terms>Terms</terms> and <privacy>Privacy Policy</privacy>"
|
|
264
|
+
//
|
|
265
|
+
// Renders:
|
|
266
|
+
// Read our <a href="/terms">Terms</a> and <a href="/privacy">Privacy Policy</a>
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
### Trans Props
|
|
270
|
+
|
|
271
|
+
| Prop | Type | Required | Description |
|
|
272
|
+
|------|------|----------|-------------|
|
|
273
|
+
| \`i18nKey\` | string | Yes | Translation key |
|
|
274
|
+
| \`ns\` | string | No | Namespace |
|
|
275
|
+
| \`components\` | object | No | Component interpolations |
|
|
276
|
+
| \`values\` | object | No | Value interpolations |
|
|
277
|
+
| \`count\` | number | No | Pluralization count |
|
|
278
|
+
`,
|
|
279
|
+
"idealyst://translate/babel-plugin": `# Babel Plugin Reference
|
|
280
|
+
|
|
281
|
+
The Babel plugin extracts translation keys at build time and generates reports of missing/unused translations.
|
|
282
|
+
|
|
283
|
+
## Installation
|
|
284
|
+
|
|
285
|
+
The plugin is included with @idealyst/translate. Just configure it in your Babel config.
|
|
286
|
+
|
|
287
|
+
## Configuration
|
|
288
|
+
|
|
289
|
+
### babel.config.js
|
|
290
|
+
|
|
291
|
+
\`\`\`javascript
|
|
292
|
+
module.exports = {
|
|
293
|
+
presets: ['@babel/preset-react', '@babel/preset-typescript'],
|
|
294
|
+
plugins: [
|
|
295
|
+
['@idealyst/translate/plugin', {
|
|
296
|
+
// Required: paths to translation JSON files
|
|
297
|
+
translationFiles: ['./locales/**/*.json'],
|
|
298
|
+
|
|
299
|
+
// Optional: output path for the report
|
|
300
|
+
reportPath: '.idealyst/translations-report.json',
|
|
301
|
+
|
|
302
|
+
// Optional: default namespace
|
|
303
|
+
defaultNamespace: 'common',
|
|
304
|
+
|
|
305
|
+
// Optional: emit console warnings
|
|
306
|
+
emitWarnings: true,
|
|
307
|
+
|
|
308
|
+
// Optional: fail build on missing translations
|
|
309
|
+
failOnMissing: false,
|
|
310
|
+
|
|
311
|
+
// Optional: verbose logging
|
|
312
|
+
verbose: false,
|
|
313
|
+
}],
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
### Vite Configuration
|
|
319
|
+
|
|
320
|
+
\`\`\`typescript
|
|
321
|
+
// vite.config.ts
|
|
322
|
+
import { defineConfig } from 'vite';
|
|
323
|
+
import react from '@vitejs/plugin-react';
|
|
324
|
+
|
|
325
|
+
export default defineConfig({
|
|
326
|
+
plugins: [
|
|
327
|
+
react({
|
|
328
|
+
babel: {
|
|
329
|
+
plugins: [
|
|
330
|
+
['@idealyst/translate/plugin', {
|
|
331
|
+
translationFiles: ['./locales/**/*.json'],
|
|
332
|
+
reportPath: '.idealyst/translations-report.json',
|
|
333
|
+
defaultNamespace: 'common',
|
|
334
|
+
}],
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
}),
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
\`\`\`
|
|
341
|
+
|
|
342
|
+
### React Native Configuration
|
|
343
|
+
|
|
344
|
+
\`\`\`javascript
|
|
345
|
+
// babel.config.js
|
|
346
|
+
module.exports = {
|
|
347
|
+
presets: ['module:@react-native/babel-preset'],
|
|
348
|
+
plugins: [
|
|
349
|
+
['@idealyst/translate/plugin', {
|
|
350
|
+
translationFiles: ['./locales/**/*.json'],
|
|
351
|
+
reportPath: '.idealyst/translations-report.json',
|
|
352
|
+
defaultNamespace: 'common',
|
|
353
|
+
emitWarnings: true,
|
|
354
|
+
}],
|
|
355
|
+
],
|
|
356
|
+
};
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
## Plugin Options
|
|
360
|
+
|
|
361
|
+
| Option | Type | Default | Description |
|
|
362
|
+
|--------|------|---------|-------------|
|
|
363
|
+
| \`translationFiles\` | string[] | Required | Glob patterns for translation files |
|
|
364
|
+
| \`reportPath\` | string | '.idealyst/translations-report.json' | Output path for report |
|
|
365
|
+
| \`languages\` | string[] | Auto-detected | Languages to check |
|
|
366
|
+
| \`defaultNamespace\` | string | 'translation' | Default namespace |
|
|
367
|
+
| \`failOnMissing\` | boolean | false | Fail build if missing |
|
|
368
|
+
| \`emitWarnings\` | boolean | true | Console warnings |
|
|
369
|
+
| \`verbose\` | boolean | false | Verbose logging |
|
|
370
|
+
|
|
371
|
+
## What Gets Extracted
|
|
372
|
+
|
|
373
|
+
The plugin statically analyzes your code for:
|
|
374
|
+
|
|
375
|
+
\`\`\`tsx
|
|
376
|
+
// t() function calls
|
|
377
|
+
t('common.key')
|
|
378
|
+
t('namespace:key')
|
|
379
|
+
t('key', { defaultValue: 'Default' })
|
|
380
|
+
|
|
381
|
+
// i18n.t() method calls
|
|
382
|
+
i18n.t('common.key')
|
|
383
|
+
|
|
384
|
+
// Trans component
|
|
385
|
+
<Trans i18nKey="common.richText" />
|
|
386
|
+
<Trans i18nKey={"common.richText"} />
|
|
387
|
+
|
|
388
|
+
// Dynamic keys (tracked but marked as dynamic)
|
|
389
|
+
const key = \`common.\${type}\`;
|
|
390
|
+
t(key); // Marked as isDynamic: true
|
|
391
|
+
\`\`\`
|
|
392
|
+
|
|
393
|
+
## Report Structure
|
|
394
|
+
|
|
395
|
+
The plugin generates a JSON report:
|
|
396
|
+
|
|
397
|
+
\`\`\`json
|
|
398
|
+
{
|
|
399
|
+
"timestamp": "2026-01-08T12:00:00.000Z",
|
|
400
|
+
"totalKeys": 45,
|
|
401
|
+
"dynamicKeys": [
|
|
402
|
+
{
|
|
403
|
+
"key": "<dynamic>",
|
|
404
|
+
"file": "src/DynamicComponent.tsx",
|
|
405
|
+
"line": 15,
|
|
406
|
+
"isDynamic": true
|
|
407
|
+
}
|
|
408
|
+
],
|
|
409
|
+
"extractedKeys": [
|
|
410
|
+
{
|
|
411
|
+
"key": "common.buttons.submit",
|
|
412
|
+
"namespace": "common",
|
|
413
|
+
"localKey": "buttons.submit",
|
|
414
|
+
"file": "src/Form.tsx",
|
|
415
|
+
"line": 42,
|
|
416
|
+
"column": 12,
|
|
417
|
+
"defaultValue": "Submit",
|
|
418
|
+
"isDynamic": false
|
|
419
|
+
}
|
|
420
|
+
],
|
|
421
|
+
"languages": ["en", "es", "fr"],
|
|
422
|
+
"missing": {
|
|
423
|
+
"en": [],
|
|
424
|
+
"es": [
|
|
425
|
+
{
|
|
426
|
+
"key": "common.buttons.submit",
|
|
427
|
+
"namespace": "common",
|
|
428
|
+
"usedIn": [
|
|
429
|
+
{ "file": "src/Form.tsx", "line": 42, "column": 12 }
|
|
430
|
+
],
|
|
431
|
+
"defaultValue": "Submit"
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
},
|
|
435
|
+
"unused": {
|
|
436
|
+
"en": ["common.legacy.oldFeature"]
|
|
437
|
+
},
|
|
438
|
+
"summary": {
|
|
439
|
+
"totalMissing": 1,
|
|
440
|
+
"totalUnused": 1,
|
|
441
|
+
"coveragePercent": {
|
|
442
|
+
"en": 100,
|
|
443
|
+
"es": 98,
|
|
444
|
+
"fr": 100
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
\`\`\`
|
|
449
|
+
|
|
450
|
+
## CI/CD Integration
|
|
451
|
+
|
|
452
|
+
### GitHub Actions
|
|
453
|
+
|
|
454
|
+
\`\`\`yaml
|
|
455
|
+
name: Translation Check
|
|
456
|
+
|
|
457
|
+
on: [push, pull_request]
|
|
458
|
+
|
|
459
|
+
jobs:
|
|
460
|
+
check-translations:
|
|
461
|
+
runs-on: ubuntu-latest
|
|
462
|
+
steps:
|
|
463
|
+
- uses: actions/checkout@v3
|
|
464
|
+
- uses: actions/setup-node@v3
|
|
465
|
+
with:
|
|
466
|
+
node-version: '20'
|
|
467
|
+
- run: yarn install
|
|
468
|
+
- run: yarn build
|
|
469
|
+
- name: Check missing translations
|
|
470
|
+
run: |
|
|
471
|
+
MISSING=$(jq '.summary.totalMissing' .idealyst/translations-report.json)
|
|
472
|
+
if [ "$MISSING" -gt 0 ]; then
|
|
473
|
+
echo "Missing translations: $MISSING"
|
|
474
|
+
jq '.missing' .idealyst/translations-report.json
|
|
475
|
+
exit 1
|
|
476
|
+
fi
|
|
477
|
+
\`\`\`
|
|
478
|
+
|
|
479
|
+
### Shell Script
|
|
480
|
+
|
|
481
|
+
\`\`\`bash
|
|
482
|
+
#!/bin/bash
|
|
483
|
+
yarn build
|
|
484
|
+
|
|
485
|
+
MISSING=$(jq '.summary.totalMissing' .idealyst/translations-report.json)
|
|
486
|
+
|
|
487
|
+
if [ "$MISSING" -gt 0 ]; then
|
|
488
|
+
echo "ERROR: $MISSING missing translation(s)"
|
|
489
|
+
jq -r '.missing | to_entries[] | select(.value | length > 0) | "\\(.key): \\(.value | length) missing"' \\
|
|
490
|
+
.idealyst/translations-report.json
|
|
491
|
+
exit 1
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
echo "All translations present!"
|
|
495
|
+
\`\`\`
|
|
496
|
+
`,
|
|
497
|
+
"idealyst://translate/translation-files": `# Translation File Format
|
|
498
|
+
|
|
499
|
+
Guide to organizing and formatting translation files for @idealyst/translate.
|
|
500
|
+
|
|
501
|
+
## Directory Structure
|
|
502
|
+
|
|
503
|
+
Organize translations by language and namespace:
|
|
504
|
+
|
|
505
|
+
\`\`\`
|
|
506
|
+
locales/
|
|
507
|
+
├── en/
|
|
508
|
+
│ ├── common.json # Common UI strings
|
|
509
|
+
│ ├── auth.json # Authentication strings
|
|
510
|
+
│ ├── errors.json # Error messages
|
|
511
|
+
│ └── forms.json # Form labels
|
|
512
|
+
├── es/
|
|
513
|
+
│ ├── common.json
|
|
514
|
+
│ ├── auth.json
|
|
515
|
+
│ ├── errors.json
|
|
516
|
+
│ └── forms.json
|
|
517
|
+
└── fr/
|
|
518
|
+
├── common.json
|
|
519
|
+
├── auth.json
|
|
520
|
+
├── errors.json
|
|
521
|
+
└── forms.json
|
|
522
|
+
\`\`\`
|
|
523
|
+
|
|
524
|
+
## JSON Format
|
|
525
|
+
|
|
526
|
+
### Basic Structure
|
|
527
|
+
|
|
528
|
+
\`\`\`json
|
|
529
|
+
{
|
|
530
|
+
"simple": "Simple text",
|
|
531
|
+
|
|
532
|
+
"nested": {
|
|
533
|
+
"keys": {
|
|
534
|
+
"work": "Like this"
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
\`\`\`
|
|
539
|
+
|
|
540
|
+
### Interpolation
|
|
541
|
+
|
|
542
|
+
Use \`{{variable}}\` for dynamic values:
|
|
543
|
+
|
|
544
|
+
\`\`\`json
|
|
545
|
+
{
|
|
546
|
+
"greeting": "Hello, {{name}}!",
|
|
547
|
+
"welcome": "Welcome back, {{user}}. You have {{count}} messages."
|
|
548
|
+
}
|
|
549
|
+
\`\`\`
|
|
550
|
+
|
|
551
|
+
Usage:
|
|
552
|
+
\`\`\`tsx
|
|
553
|
+
t('greeting', { name: 'John' }) // "Hello, John!"
|
|
554
|
+
t('welcome', { user: 'Alice', count: 5 }) // "Welcome back, Alice. You have 5 messages."
|
|
555
|
+
\`\`\`
|
|
556
|
+
|
|
557
|
+
### Pluralization
|
|
558
|
+
|
|
559
|
+
Add \`_plural\` suffix for plural forms:
|
|
560
|
+
|
|
561
|
+
\`\`\`json
|
|
562
|
+
{
|
|
563
|
+
"item": "{{count}} item",
|
|
564
|
+
"item_plural": "{{count}} items",
|
|
565
|
+
|
|
566
|
+
"message": "You have {{count}} new message",
|
|
567
|
+
"message_plural": "You have {{count}} new messages"
|
|
568
|
+
}
|
|
569
|
+
\`\`\`
|
|
570
|
+
|
|
571
|
+
Usage:
|
|
572
|
+
\`\`\`tsx
|
|
573
|
+
t('item', { count: 1 }) // "1 item"
|
|
574
|
+
t('item', { count: 5 }) // "5 items"
|
|
575
|
+
\`\`\`
|
|
576
|
+
|
|
577
|
+
### Context
|
|
578
|
+
|
|
579
|
+
Add \`_context\` suffix for contextual variations:
|
|
580
|
+
|
|
581
|
+
\`\`\`json
|
|
582
|
+
{
|
|
583
|
+
"friend": "A friend",
|
|
584
|
+
"friend_male": "A boyfriend",
|
|
585
|
+
"friend_female": "A girlfriend"
|
|
586
|
+
}
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
589
|
+
Usage:
|
|
590
|
+
\`\`\`tsx
|
|
591
|
+
t('friend') // "A friend"
|
|
592
|
+
t('friend', { context: 'male' }) // "A boyfriend"
|
|
593
|
+
t('friend', { context: 'female' }) // "A girlfriend"
|
|
594
|
+
\`\`\`
|
|
595
|
+
|
|
596
|
+
### Rich Text (Trans Component)
|
|
597
|
+
|
|
598
|
+
Use HTML-like tags for component interpolation:
|
|
599
|
+
|
|
600
|
+
\`\`\`json
|
|
601
|
+
{
|
|
602
|
+
"terms": "By signing up, you agree to our <terms>Terms of Service</terms> and <privacy>Privacy Policy</privacy>.",
|
|
603
|
+
"welcome": "Welcome, <bold>{{name}}</bold>! Click <link>here</link> to continue."
|
|
604
|
+
}
|
|
605
|
+
\`\`\`
|
|
606
|
+
|
|
607
|
+
Usage:
|
|
608
|
+
\`\`\`tsx
|
|
609
|
+
<Trans
|
|
610
|
+
i18nKey="common.terms"
|
|
611
|
+
components={{
|
|
612
|
+
terms: <a href="/terms" />,
|
|
613
|
+
privacy: <a href="/privacy" />,
|
|
614
|
+
}}
|
|
615
|
+
/>
|
|
616
|
+
\`\`\`
|
|
617
|
+
|
|
618
|
+
## Namespace Organization
|
|
619
|
+
|
|
620
|
+
### common.json - Shared UI
|
|
621
|
+
\`\`\`json
|
|
622
|
+
{
|
|
623
|
+
"buttons": {
|
|
624
|
+
"submit": "Submit",
|
|
625
|
+
"cancel": "Cancel",
|
|
626
|
+
"save": "Save",
|
|
627
|
+
"delete": "Delete",
|
|
628
|
+
"edit": "Edit"
|
|
629
|
+
},
|
|
630
|
+
"labels": {
|
|
631
|
+
"loading": "Loading...",
|
|
632
|
+
"error": "An error occurred",
|
|
633
|
+
"success": "Success!"
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
\`\`\`
|
|
637
|
+
|
|
638
|
+
### auth.json - Authentication
|
|
639
|
+
\`\`\`json
|
|
640
|
+
{
|
|
641
|
+
"login": {
|
|
642
|
+
"title": "Sign In",
|
|
643
|
+
"email": "Email",
|
|
644
|
+
"password": "Password",
|
|
645
|
+
"submit": "Sign In",
|
|
646
|
+
"forgotPassword": "Forgot password?"
|
|
647
|
+
},
|
|
648
|
+
"register": {
|
|
649
|
+
"title": "Create Account",
|
|
650
|
+
"submit": "Sign Up"
|
|
651
|
+
},
|
|
652
|
+
"errors": {
|
|
653
|
+
"invalidCredentials": "Invalid email or password",
|
|
654
|
+
"emailTaken": "Email is already registered"
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
\`\`\`
|
|
658
|
+
|
|
659
|
+
### errors.json - Error Messages
|
|
660
|
+
\`\`\`json
|
|
661
|
+
{
|
|
662
|
+
"network": {
|
|
663
|
+
"offline": "You are offline. Please check your connection.",
|
|
664
|
+
"timeout": "Request timed out. Please try again."
|
|
665
|
+
},
|
|
666
|
+
"validation": {
|
|
667
|
+
"required": "This field is required",
|
|
668
|
+
"email": "Please enter a valid email",
|
|
669
|
+
"minLength": "Must be at least {{min}} characters"
|
|
670
|
+
},
|
|
671
|
+
"http": {
|
|
672
|
+
"400": "Bad request",
|
|
673
|
+
"401": "Unauthorized",
|
|
674
|
+
"403": "Forbidden",
|
|
675
|
+
"404": "Not found",
|
|
676
|
+
"500": "Server error"
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
\`\`\`
|
|
680
|
+
|
|
681
|
+
## Best Practices
|
|
682
|
+
|
|
683
|
+
### 1. Use Descriptive Keys
|
|
684
|
+
\`\`\`json
|
|
685
|
+
// Good
|
|
686
|
+
{
|
|
687
|
+
"userProfile": {
|
|
688
|
+
"editButton": "Edit Profile",
|
|
689
|
+
"saveSuccess": "Profile saved successfully"
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Avoid
|
|
694
|
+
{
|
|
695
|
+
"btn1": "Edit Profile",
|
|
696
|
+
"msg1": "Profile saved successfully"
|
|
697
|
+
}
|
|
698
|
+
\`\`\`
|
|
699
|
+
|
|
700
|
+
### 2. Group Related Translations
|
|
701
|
+
\`\`\`json
|
|
702
|
+
{
|
|
703
|
+
"checkout": {
|
|
704
|
+
"title": "Checkout",
|
|
705
|
+
"steps": {
|
|
706
|
+
"shipping": "Shipping",
|
|
707
|
+
"payment": "Payment",
|
|
708
|
+
"review": "Review"
|
|
709
|
+
},
|
|
710
|
+
"buttons": {
|
|
711
|
+
"next": "Continue",
|
|
712
|
+
"back": "Go Back",
|
|
713
|
+
"placeOrder": "Place Order"
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
\`\`\`
|
|
718
|
+
|
|
719
|
+
### 3. Keep Keys Consistent Across Languages
|
|
720
|
+
All language files should have the same key structure:
|
|
721
|
+
|
|
722
|
+
\`\`\`json
|
|
723
|
+
// en/common.json
|
|
724
|
+
{ "greeting": "Hello" }
|
|
725
|
+
|
|
726
|
+
// es/common.json
|
|
727
|
+
{ "greeting": "Hola" }
|
|
728
|
+
|
|
729
|
+
// fr/common.json
|
|
730
|
+
{ "greeting": "Bonjour" }
|
|
731
|
+
\`\`\`
|
|
732
|
+
|
|
733
|
+
### 4. Add Comments with Default Values
|
|
734
|
+
The Babel plugin extracts \`defaultValue\` for documentation:
|
|
735
|
+
|
|
736
|
+
\`\`\`tsx
|
|
737
|
+
t('newFeature.title', { defaultValue: 'New Feature' })
|
|
738
|
+
\`\`\`
|
|
739
|
+
|
|
740
|
+
This appears in the report and helps translators understand context.
|
|
741
|
+
`,
|
|
742
|
+
"idealyst://translate/examples": `# Translation Examples
|
|
743
|
+
|
|
744
|
+
Complete code examples for common @idealyst/translate patterns.
|
|
745
|
+
|
|
746
|
+
## Basic App Setup
|
|
747
|
+
|
|
748
|
+
\`\`\`tsx
|
|
749
|
+
// App.tsx
|
|
750
|
+
import { TranslateProvider } from '@idealyst/translate';
|
|
751
|
+
import en from './locales/en/common.json';
|
|
752
|
+
import es from './locales/es/common.json';
|
|
753
|
+
|
|
754
|
+
const config = {
|
|
755
|
+
defaultLanguage: 'en',
|
|
756
|
+
languages: ['en', 'es'],
|
|
757
|
+
resources: {
|
|
758
|
+
en: { common: en },
|
|
759
|
+
es: { common: es },
|
|
760
|
+
},
|
|
761
|
+
defaultNamespace: 'common',
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
export function App() {
|
|
765
|
+
return (
|
|
766
|
+
<TranslateProvider config={config}>
|
|
767
|
+
<Navigation />
|
|
768
|
+
</TranslateProvider>
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
\`\`\`
|
|
772
|
+
|
|
773
|
+
## Component with Translations
|
|
774
|
+
|
|
775
|
+
\`\`\`tsx
|
|
776
|
+
// components/WelcomeScreen.tsx
|
|
777
|
+
import { View, Text, Button } from '@idealyst/components';
|
|
778
|
+
import { useTranslation } from '@idealyst/translate';
|
|
779
|
+
|
|
780
|
+
export function WelcomeScreen() {
|
|
781
|
+
const { t } = useTranslation('common');
|
|
782
|
+
|
|
783
|
+
return (
|
|
784
|
+
<View>
|
|
785
|
+
<Text variant="h1">{t('welcome.title')}</Text>
|
|
786
|
+
<Text>{t('welcome.subtitle')}</Text>
|
|
787
|
+
<Button onPress={() => {}}>
|
|
788
|
+
{t('buttons.getStarted')}
|
|
789
|
+
</Button>
|
|
790
|
+
</View>
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
\`\`\`
|
|
794
|
+
|
|
795
|
+
## Language Switcher Component
|
|
796
|
+
|
|
797
|
+
\`\`\`tsx
|
|
798
|
+
// components/LanguageSwitcher.tsx
|
|
799
|
+
import { Select } from '@idealyst/components';
|
|
800
|
+
import { useLanguage } from '@idealyst/translate';
|
|
801
|
+
|
|
802
|
+
export function LanguageSwitcher() {
|
|
803
|
+
const { language, languages, setLanguage, getDisplayName } = useLanguage();
|
|
804
|
+
|
|
805
|
+
const options = languages.map((lang) => ({
|
|
806
|
+
value: lang,
|
|
807
|
+
label: getDisplayName(lang),
|
|
808
|
+
}));
|
|
809
|
+
|
|
810
|
+
return (
|
|
811
|
+
<Select
|
|
812
|
+
value={language}
|
|
813
|
+
options={options}
|
|
814
|
+
onChange={(value) => setLanguage(value)}
|
|
815
|
+
label="Language"
|
|
816
|
+
/>
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
\`\`\`
|
|
820
|
+
|
|
821
|
+
## Form with Validation Messages
|
|
822
|
+
|
|
823
|
+
\`\`\`tsx
|
|
824
|
+
// components/LoginForm.tsx
|
|
825
|
+
import { View, Input, Button, Text } from '@idealyst/components';
|
|
826
|
+
import { useTranslation } from '@idealyst/translate';
|
|
827
|
+
import { useState } from 'react';
|
|
828
|
+
|
|
829
|
+
export function LoginForm() {
|
|
830
|
+
const { t } = useTranslation('auth');
|
|
831
|
+
const [error, setError] = useState<string | null>(null);
|
|
832
|
+
|
|
833
|
+
const handleSubmit = async () => {
|
|
834
|
+
try {
|
|
835
|
+
// ... login logic
|
|
836
|
+
} catch (e) {
|
|
837
|
+
setError(t('errors.invalidCredentials'));
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
return (
|
|
842
|
+
<View>
|
|
843
|
+
<Text variant="h2">{t('login.title')}</Text>
|
|
844
|
+
|
|
845
|
+
<Input
|
|
846
|
+
label={t('login.email')}
|
|
847
|
+
placeholder={t('login.emailPlaceholder')}
|
|
848
|
+
keyboardType="email-address"
|
|
849
|
+
/>
|
|
850
|
+
|
|
851
|
+
<Input
|
|
852
|
+
label={t('login.password')}
|
|
853
|
+
placeholder={t('login.passwordPlaceholder')}
|
|
854
|
+
secureTextEntry
|
|
855
|
+
/>
|
|
856
|
+
|
|
857
|
+
{error && <Text intent="error">{error}</Text>}
|
|
858
|
+
|
|
859
|
+
<Button onPress={handleSubmit}>
|
|
860
|
+
{t('login.submit')}
|
|
861
|
+
</Button>
|
|
862
|
+
</View>
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
\`\`\`
|
|
866
|
+
|
|
867
|
+
## Pluralization Example
|
|
868
|
+
|
|
869
|
+
\`\`\`tsx
|
|
870
|
+
// components/NotificationBadge.tsx
|
|
871
|
+
import { Badge } from '@idealyst/components';
|
|
872
|
+
import { useTranslation } from '@idealyst/translate';
|
|
873
|
+
|
|
874
|
+
interface Props {
|
|
875
|
+
count: number;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export function NotificationBadge({ count }: Props) {
|
|
879
|
+
const { t } = useTranslation('common');
|
|
880
|
+
|
|
881
|
+
if (count === 0) return null;
|
|
882
|
+
|
|
883
|
+
return (
|
|
884
|
+
<Badge intent="primary">
|
|
885
|
+
{t('notifications.count', { count })}
|
|
886
|
+
</Badge>
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Translation file:
|
|
891
|
+
// {
|
|
892
|
+
// "notifications": {
|
|
893
|
+
// "count": "{{count}} notification",
|
|
894
|
+
// "count_plural": "{{count}} notifications"
|
|
895
|
+
// }
|
|
896
|
+
// }
|
|
897
|
+
\`\`\`
|
|
898
|
+
|
|
899
|
+
## Rich Text with Trans Component
|
|
900
|
+
|
|
901
|
+
\`\`\`tsx
|
|
902
|
+
// components/TermsAgreement.tsx
|
|
903
|
+
import { View, Text, Checkbox } from '@idealyst/components';
|
|
904
|
+
import { Trans, useTranslation } from '@idealyst/translate';
|
|
905
|
+
import { Link } from '@idealyst/navigation';
|
|
906
|
+
import { useState } from 'react';
|
|
907
|
+
|
|
908
|
+
export function TermsAgreement() {
|
|
909
|
+
const { t } = useTranslation('auth');
|
|
910
|
+
const [agreed, setAgreed] = useState(false);
|
|
911
|
+
|
|
912
|
+
return (
|
|
913
|
+
<View>
|
|
914
|
+
<Checkbox
|
|
915
|
+
checked={agreed}
|
|
916
|
+
onChange={setAgreed}
|
|
917
|
+
label={
|
|
918
|
+
<Trans
|
|
919
|
+
i18nKey="auth.termsAgreement"
|
|
920
|
+
components={{
|
|
921
|
+
terms: <Link to="/terms" />,
|
|
922
|
+
privacy: <Link to="/privacy" />,
|
|
923
|
+
}}
|
|
924
|
+
/>
|
|
925
|
+
}
|
|
926
|
+
/>
|
|
927
|
+
</View>
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Translation file:
|
|
932
|
+
// {
|
|
933
|
+
// "termsAgreement": "I agree to the <terms>Terms of Service</terms> and <privacy>Privacy Policy</privacy>"
|
|
934
|
+
// }
|
|
935
|
+
\`\`\`
|
|
936
|
+
|
|
937
|
+
## Persisting Language Preference
|
|
938
|
+
|
|
939
|
+
\`\`\`tsx
|
|
940
|
+
// hooks/usePersistedLanguage.ts
|
|
941
|
+
import { useLanguage } from '@idealyst/translate';
|
|
942
|
+
import { useEffect } from 'react';
|
|
943
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
944
|
+
|
|
945
|
+
const LANGUAGE_KEY = '@app/language';
|
|
946
|
+
|
|
947
|
+
export function usePersistedLanguage() {
|
|
948
|
+
const { language, setLanguage } = useLanguage();
|
|
949
|
+
|
|
950
|
+
// Load saved language on mount
|
|
951
|
+
useEffect(() => {
|
|
952
|
+
AsyncStorage.getItem(LANGUAGE_KEY).then((saved) => {
|
|
953
|
+
if (saved) setLanguage(saved);
|
|
954
|
+
});
|
|
955
|
+
}, []);
|
|
956
|
+
|
|
957
|
+
// Save language when changed
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
AsyncStorage.setItem(LANGUAGE_KEY, language);
|
|
960
|
+
}, [language]);
|
|
961
|
+
|
|
962
|
+
return { language, setLanguage };
|
|
963
|
+
}
|
|
964
|
+
\`\`\`
|
|
965
|
+
|
|
966
|
+
## Multiple Namespaces
|
|
967
|
+
|
|
968
|
+
\`\`\`tsx
|
|
969
|
+
// components/UserProfile.tsx
|
|
970
|
+
import { useTranslation } from '@idealyst/translate';
|
|
971
|
+
|
|
972
|
+
export function UserProfile() {
|
|
973
|
+
// Load multiple namespaces
|
|
974
|
+
const { t } = useTranslation(['common', 'user']);
|
|
975
|
+
|
|
976
|
+
return (
|
|
977
|
+
<View>
|
|
978
|
+
{/* Use namespace prefix */}
|
|
979
|
+
<Text variant="h1">{t('user:profile.title')}</Text>
|
|
980
|
+
<Button>{t('common:buttons.edit')}</Button>
|
|
981
|
+
</View>
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
\`\`\`
|
|
985
|
+
|
|
986
|
+
## Date and Number Formatting
|
|
987
|
+
|
|
988
|
+
\`\`\`tsx
|
|
989
|
+
// components/OrderSummary.tsx
|
|
990
|
+
import { View, Text } from '@idealyst/components';
|
|
991
|
+
import { useTranslation, useLanguage } from '@idealyst/translate';
|
|
992
|
+
|
|
993
|
+
interface Props {
|
|
994
|
+
total: number;
|
|
995
|
+
orderDate: Date;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function OrderSummary({ total, orderDate }: Props) {
|
|
999
|
+
const { t } = useTranslation('orders');
|
|
1000
|
+
const { language } = useLanguage();
|
|
1001
|
+
|
|
1002
|
+
// Format based on current language
|
|
1003
|
+
const formattedTotal = new Intl.NumberFormat(language, {
|
|
1004
|
+
style: 'currency',
|
|
1005
|
+
currency: 'USD',
|
|
1006
|
+
}).format(total);
|
|
1007
|
+
|
|
1008
|
+
const formattedDate = new Intl.DateTimeFormat(language, {
|
|
1009
|
+
dateStyle: 'long',
|
|
1010
|
+
}).format(orderDate);
|
|
1011
|
+
|
|
1012
|
+
return (
|
|
1013
|
+
<View>
|
|
1014
|
+
<Text>{t('summary.total', { amount: formattedTotal })}</Text>
|
|
1015
|
+
<Text>{t('summary.date', { date: formattedDate })}</Text>
|
|
1016
|
+
</View>
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Translation file:
|
|
1021
|
+
// {
|
|
1022
|
+
// "summary": {
|
|
1023
|
+
// "total": "Total: {{amount}}",
|
|
1024
|
+
// "date": "Order placed on {{date}}"
|
|
1025
|
+
// }
|
|
1026
|
+
// }
|
|
1027
|
+
\`\`\`
|
|
1028
|
+
`,
|
|
1029
|
+
};
|
|
1030
|
+
//# sourceMappingURL=translate-guides.js.map
|