lokal-core 1.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 +636 -0
- package/package.json +37 -0
- package/src/ai/translator.ts +366 -0
- package/src/ast/parser.ts +171 -0
- package/src/config/loader.ts +160 -0
- package/src/index.ts +14 -0
- package/src/lokal-core.d.ts +113 -0
- package/src/storage/file-storage.ts +200 -0
- package/src/types/@babel/traverse.d.ts +74 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# LOKAL - AI-Powered Localization for React
|
|
2
|
+
|
|
3
|
+
[](https://github.com/devcoda25/lokal/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/lokal-core)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
LOKAL is an AI-powered localization ecosystem that helps you extract, translate, and manage translation strings in your React applications.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **CLI Commands** - Easy command-line workflow
|
|
12
|
+
- **AST-based Extraction** - Automatically finds translation strings in your code
|
|
13
|
+
- **AI Translation** - Powered by Google Gemini, OpenAI, or DeepSeek
|
|
14
|
+
- **Incremental Sync** - Only translates changed content using content hashing
|
|
15
|
+
- **File Storage** - JSON-based locale file management
|
|
16
|
+
- **Framework Ready** - Works with React and React Native
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install CLI globally
|
|
22
|
+
npm install -g lokal
|
|
23
|
+
|
|
24
|
+
# Or use with npx
|
|
25
|
+
npx lokal --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## CLI Commands
|
|
29
|
+
|
|
30
|
+
LOKAL provides three main CLI commands:
|
|
31
|
+
|
|
32
|
+
### 1. `lokal init`
|
|
33
|
+
|
|
34
|
+
Initialize LOKAL in your project:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx lokal init
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Options:**
|
|
41
|
+
- `-l, --locales <locales>` - Comma-separated locales (default: "en")
|
|
42
|
+
- `-d, --default-locale <locale>` - Default locale (default: "en")
|
|
43
|
+
- `-f, --force` - Force reinitialization
|
|
44
|
+
|
|
45
|
+
**What it does:**
|
|
46
|
+
- Creates `lokal.config.js`
|
|
47
|
+
- Creates `locales/` directory
|
|
48
|
+
- Creates default locale file (e.g., `locales/en.json`)
|
|
49
|
+
|
|
50
|
+
### 2. `lokal scan`
|
|
51
|
+
|
|
52
|
+
Scan source files for translation strings:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx lokal scan
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Options:**
|
|
59
|
+
- `-c, --config <path>` - Path to config file
|
|
60
|
+
- `-o, --output <path>` - Output directory
|
|
61
|
+
- `-v, --verbose` - Show detailed output
|
|
62
|
+
|
|
63
|
+
**What it does:**
|
|
64
|
+
- Scans `./src` for `t()` and `<T>` patterns
|
|
65
|
+
- Extracts all translation strings
|
|
66
|
+
- Updates the default locale file
|
|
67
|
+
|
|
68
|
+
### 3. `lokal translate`
|
|
69
|
+
|
|
70
|
+
Translate missing strings using AI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Translate to first non-default locale
|
|
74
|
+
npx lokal translate
|
|
75
|
+
|
|
76
|
+
# Translate to specific locale
|
|
77
|
+
npx lokal translate --locale es
|
|
78
|
+
|
|
79
|
+
# Translate all locales
|
|
80
|
+
npx lokal translate --all
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Options:**
|
|
84
|
+
- `-c, --config <path>` - Path to config file
|
|
85
|
+
- `-l, --locale <locale>` - Specific locale to translate
|
|
86
|
+
- `-a, --all` - Translate all locales
|
|
87
|
+
- `-v, --verbose` - Show detailed output
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
After running `lokal init`, a `lokal.config.js` file is created:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
module.exports = {
|
|
95
|
+
// Supported locales
|
|
96
|
+
locales: ['en', 'es', 'fr'],
|
|
97
|
+
|
|
98
|
+
// Default locale
|
|
99
|
+
defaultLocale: 'en',
|
|
100
|
+
|
|
101
|
+
// Function name for translations (t("key"))
|
|
102
|
+
functionName: 't',
|
|
103
|
+
|
|
104
|
+
// Component name for translations (<T>key</T>)
|
|
105
|
+
componentName: 'T',
|
|
106
|
+
|
|
107
|
+
// Source directory to scan
|
|
108
|
+
sourceDir: './src',
|
|
109
|
+
|
|
110
|
+
// Output directory for locale files
|
|
111
|
+
outputDir: './locales',
|
|
112
|
+
|
|
113
|
+
// AI Translation settings
|
|
114
|
+
ai: {
|
|
115
|
+
provider: 'gemini', // 'openai', 'gemini', or 'deepseek'
|
|
116
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
117
|
+
model: 'gemini-2.5-flash'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Complete Workflow
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Step 1: Initialize your project
|
|
126
|
+
npx lokal init --locales en,es,fr --default-locale en
|
|
127
|
+
|
|
128
|
+
# Step 2: Add translation strings to your code
|
|
129
|
+
# t("Hello") or <T>Hello</T>
|
|
130
|
+
|
|
131
|
+
# Step 3: Scan for strings (run after adding new strings)
|
|
132
|
+
npx lokal scan
|
|
133
|
+
|
|
134
|
+
# Step 4: Translate with AI
|
|
135
|
+
npx lokal translate --all
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Library Usage (Advanced)
|
|
141
|
+
|
|
142
|
+
You can also use LOKAL as a library in your code:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { ASTParser, GeminiProvider, AITranslator, FileStorage } from 'lokal-core';
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Architecture
|
|
151
|
+
|
|
152
|
+
LOKAL consists of three main components:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
┌─────────────────────────────────────────────┐
|
|
156
|
+
│ LOKAL Core │
|
|
157
|
+
├─────────────────────────────────────────────┤
|
|
158
|
+
│ │
|
|
159
|
+
│ ┌─────────────┐ ┌─────────────┐ │
|
|
160
|
+
│ │ AST Parser │ │ AI Translate│ │
|
|
161
|
+
│ └──────┬──────┘ └──────┬──────┘ │
|
|
162
|
+
│ │ │ │
|
|
163
|
+
│ ▼ ▼ │
|
|
164
|
+
│ ┌─────────────────────────────┐ │
|
|
165
|
+
│ │ Translation Workflow │ │
|
|
166
|
+
│ │ 1. Extract strings │ │
|
|
167
|
+
│ │ 2. Translate with AI │ │
|
|
168
|
+
│ │ 3. Save to files │ │
|
|
169
|
+
│ └──────────────┬──────────────┘ │
|
|
170
|
+
│ ▼ │
|
|
171
|
+
│ ┌─────────────────────────────┐ │
|
|
172
|
+
│ │ File Storage │ │
|
|
173
|
+
│ └─────────────────────────────┘ │
|
|
174
|
+
│ │
|
|
175
|
+
└─────────────────────────────────────────────┘
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 1. AST Parser
|
|
181
|
+
|
|
182
|
+
The AST Parser scans your source code and extracts translation strings using Abstract Syntax Tree parsing.
|
|
183
|
+
|
|
184
|
+
### Supported Patterns
|
|
185
|
+
|
|
186
|
+
| Pattern | Code Example | Extracts |
|
|
187
|
+
|---------|--------------|----------|
|
|
188
|
+
| Function call | `t("Hello World")` | `"Hello World"` |
|
|
189
|
+
| Function with variable | `t(greeting)` | ❌ (not extracted) |
|
|
190
|
+
| JSX Component | `<T>Welcome</T>` | `"Welcome"` |
|
|
191
|
+
| JSX with expression | `<T>{name}</T>` | ❌ (not extracted) |
|
|
192
|
+
|
|
193
|
+
### Basic Usage
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { ASTParser } from 'lokal-core';
|
|
197
|
+
|
|
198
|
+
// Create parser with default settings
|
|
199
|
+
const parser = new ASTParser({
|
|
200
|
+
filePath: './src' // Path to scan
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Parse a single file
|
|
204
|
+
const result = parser.parseFile('./src/components/Button.tsx');
|
|
205
|
+
|
|
206
|
+
console.log(result.strings);
|
|
207
|
+
// [
|
|
208
|
+
// { key: "Submit", value: "Submit", file: "...", line: 10, column: 5 },
|
|
209
|
+
// { key: "Cancel", value: "Cancel", file: "...", line: 11, column: 5 }
|
|
210
|
+
// ]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Custom Function and Component Names
|
|
214
|
+
|
|
215
|
+
By default, LOKAL looks for `t()` function and `<T>` component. You can customize this:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const parser = new ASTParser({
|
|
219
|
+
filePath: './src',
|
|
220
|
+
functionName: 'translate', // Looks for translate("string")
|
|
221
|
+
componentName: 'Trans' // Looks for <Trans>string</Trans>
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Scan Directory
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Scan entire directory
|
|
229
|
+
const result = parser.scanDirectory('./src', ['.ts', '.tsx', '.js', '.jsx']);
|
|
230
|
+
|
|
231
|
+
// result.strings - Array of extracted strings
|
|
232
|
+
// result.errors - Any parsing errors
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Type Definitions
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
interface ExtractedString {
|
|
239
|
+
key: string; // Translation key (same as value for simple strings)
|
|
240
|
+
value: string; // The actual string content
|
|
241
|
+
file: string; // File path where found
|
|
242
|
+
line: number; // Line number
|
|
243
|
+
column: number; // Column number
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface ScanResult {
|
|
247
|
+
strings: ExtractedString[];
|
|
248
|
+
errors: string[];
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 2. AI Translation
|
|
255
|
+
|
|
256
|
+
LOKAL supports multiple AI providers for translating your strings.
|
|
257
|
+
|
|
258
|
+
### Supported Providers
|
|
259
|
+
|
|
260
|
+
| Provider | Model | API Key |
|
|
261
|
+
|----------|-------|---------|
|
|
262
|
+
| Google Gemini | gemini-2.5-flash | Google AI Studio |
|
|
263
|
+
| OpenAI | gpt-4o-mini | OpenAI Platform |
|
|
264
|
+
| DeepSeek | deepseek-chat | DeepSeek Platform |
|
|
265
|
+
|
|
266
|
+
### Google Gemini (Recommended)
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { GeminiProvider, AITranslator } from 'lokal-core';
|
|
270
|
+
|
|
271
|
+
// Create Gemini provider
|
|
272
|
+
const gemini = new GeminiProvider(
|
|
273
|
+
'YOUR-GEMINI-API-KEY', // Get from https://aistudio.google.com/app/apikey
|
|
274
|
+
'gemini-2.5-flash' // Model name (optional)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Create translator
|
|
278
|
+
const translator = new AITranslator(gemini);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### OpenAI
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { OpenAIProvider, AITranslator } from 'lokal-core';
|
|
285
|
+
|
|
286
|
+
const openai = new OpenAIProvider(
|
|
287
|
+
'YOUR-OPENAI-API-KEY', // Get from https://platform.openai.com/api-keys
|
|
288
|
+
'gpt-4o-mini' // Model (optional)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const translator = new AITranslator(openai);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### DeepSeek
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { DeepSeekProvider, AITranslator } from 'lokal-core';
|
|
298
|
+
|
|
299
|
+
const deepseek = new DeepSeekProvider(
|
|
300
|
+
'YOUR-DEEPSEEK-API-KEY',
|
|
301
|
+
'deepseek-chat'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const translator = new AITranslator(deepseek);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Translate Strings
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// Single translation
|
|
311
|
+
const result = await translator.translate({
|
|
312
|
+
sourceText: 'Hello World',
|
|
313
|
+
sourceLocale: 'en',
|
|
314
|
+
targetLocale: 'es'
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
console.log(result.translatedText); // "Hola Mundo"
|
|
318
|
+
|
|
319
|
+
// Batch translation
|
|
320
|
+
const results = await translator.translateBatch([
|
|
321
|
+
{ sourceText: 'Hello', sourceLocale: 'en', targetLocale: 'es' },
|
|
322
|
+
{ sourceText: 'Goodbye', sourceLocale: 'en', targetLocale: 'es' }
|
|
323
|
+
]);
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Incremental Sync
|
|
327
|
+
|
|
328
|
+
LOKAL uses content hashing to skip translating unchanged strings:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// First translation
|
|
332
|
+
const sourceData = { greeting: 'Hello', welcome: 'Welcome' };
|
|
333
|
+
const targetData = {};
|
|
334
|
+
|
|
335
|
+
const translated1 = await translator.translateMissingKeys(
|
|
336
|
+
sourceData,
|
|
337
|
+
targetData,
|
|
338
|
+
'en',
|
|
339
|
+
'es'
|
|
340
|
+
);
|
|
341
|
+
// translated1 = { greeting: 'Hola', welcome: 'Bienvenido' }
|
|
342
|
+
|
|
343
|
+
// Second call with SAME source - skips already translated
|
|
344
|
+
const translated2 = await translator.translateMissingKeys(
|
|
345
|
+
sourceData, // Same as before
|
|
346
|
+
translated1, // Previous result
|
|
347
|
+
'en',
|
|
348
|
+
'es'
|
|
349
|
+
);
|
|
350
|
+
// Returns translated1 immediately (skips API calls)
|
|
351
|
+
|
|
352
|
+
// Third call with CHANGED source - only translates changed
|
|
353
|
+
sourceData.greeting = 'Hi'; // Changed!
|
|
354
|
+
const translated3 = await translator.translateMissingKeys(
|
|
355
|
+
sourceData,
|
|
356
|
+
translated2,
|
|
357
|
+
'en',
|
|
358
|
+
'es'
|
|
359
|
+
);
|
|
360
|
+
// Only 'greeting' is re-translated
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 3. File Storage
|
|
366
|
+
|
|
367
|
+
LOKAL provides file-based storage for your locale files.
|
|
368
|
+
|
|
369
|
+
### Basic Usage
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { FileStorage } from 'lokal-core';
|
|
373
|
+
|
|
374
|
+
const storage = new FileStorage('./locales');
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Save Locale
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
const localeData = {
|
|
381
|
+
common: {
|
|
382
|
+
greeting: 'Hello',
|
|
383
|
+
goodbye: 'Goodbye'
|
|
384
|
+
},
|
|
385
|
+
home: {
|
|
386
|
+
title: 'Welcome',
|
|
387
|
+
subtitle: 'Welcome to our app'
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
storage.saveLocale('en', localeData);
|
|
392
|
+
// Creates: locales/en.json
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Load Locale
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
const enData = storage.loadLocale('en');
|
|
399
|
+
// Returns: { locale: 'en', data: {...}, hash: '...', lastUpdated: '...' }
|
|
400
|
+
|
|
401
|
+
// Get just the data
|
|
402
|
+
const data = enData?.data;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Merge Data
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Merge new keys into existing locale
|
|
409
|
+
const merged = storage.mergeLocaleData('en', newKeys, preserveExisting = true);
|
|
410
|
+
|
|
411
|
+
// Example:
|
|
412
|
+
const existing = { common: { greeting: 'Hello' } };
|
|
413
|
+
const newKeys = { common: { greeting: 'Hi', newKey: 'New' } };
|
|
414
|
+
const result = storage.mergeLocaleData('en', newKeys, true);
|
|
415
|
+
// result = { common: { greeting: 'Hello', newKey: 'New' } }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Other Operations
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
// Check if locale exists
|
|
422
|
+
storage.localeExists('en'); // true/false
|
|
423
|
+
|
|
424
|
+
// Get all available locales
|
|
425
|
+
storage.getAvailableLocales(); // ['en', 'es', 'fr']
|
|
426
|
+
|
|
427
|
+
// Delete locale
|
|
428
|
+
storage.deleteLocale('fr');
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Complete Example: Translate a React App
|
|
434
|
+
|
|
435
|
+
### Step 1: Create the translation script
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// translate.ts
|
|
439
|
+
import { ASTParser, GeminiProvider, AITranslator, FileStorage } from 'lokal-core';
|
|
440
|
+
|
|
441
|
+
async function translateApp() {
|
|
442
|
+
const SOURCE_DIR = './src';
|
|
443
|
+
const TARGET_LOCALES = ['es', 'fr', 'de', 'ja'];
|
|
444
|
+
const SOURCE_LOCALE = 'en';
|
|
445
|
+
|
|
446
|
+
// 1. Extract strings from source
|
|
447
|
+
console.log('🔍 Scanning for translation strings...');
|
|
448
|
+
const parser = new ASTParser({ filePath: SOURCE_DIR });
|
|
449
|
+
const scanResult = parser.scanDirectory(SOURCE_DIR);
|
|
450
|
+
|
|
451
|
+
if (scanResult.errors.length > 0) {
|
|
452
|
+
console.warn('⚠️ Some files had errors:', scanResult.errors);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 2. Build source locale data
|
|
456
|
+
const sourceData: Record<string, any> = {};
|
|
457
|
+
for (const str of scanResult.strings) {
|
|
458
|
+
sourceData[str.key] = str.value;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 3. Save source locale
|
|
462
|
+
console.log('💾 Saving source locale...');
|
|
463
|
+
const storage = new FileStorage('./locales');
|
|
464
|
+
storage.saveLocale(SOURCE_LOCALE, sourceData);
|
|
465
|
+
|
|
466
|
+
// 4. Translate to each target locale
|
|
467
|
+
console.log('🤖 Starting AI translation...');
|
|
468
|
+
const provider = new GeminiProvider('YOUR-API-KEY');
|
|
469
|
+
const translator = new AITranslator(provider);
|
|
470
|
+
|
|
471
|
+
for (const targetLocale of TARGET_LOCALES) {
|
|
472
|
+
console.log(` Translating to ${targetLocale}...`);
|
|
473
|
+
|
|
474
|
+
const targetData = storage.loadLocale(targetLocale)?.data || {};
|
|
475
|
+
const translated = await translator.translateMissingKeys(
|
|
476
|
+
sourceData,
|
|
477
|
+
targetData,
|
|
478
|
+
SOURCE_LOCALE,
|
|
479
|
+
targetLocale
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
storage.saveLocale(targetLocale, translated);
|
|
483
|
+
console.log(` ✓ ${targetLocale} complete`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
console.log('✅ Translation complete!');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
translateApp().catch(console.error);
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Step 2: Run the script
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
npx tsx translate.ts
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Step 3: Use in your React app
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
// locales/en.json
|
|
502
|
+
{
|
|
503
|
+
"greeting": "Hello",
|
|
504
|
+
"welcome": "Welcome to our app"
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// locales/es.json
|
|
508
|
+
{
|
|
509
|
+
"greeting": "Hola",
|
|
510
|
+
"welcome": "Bienvenido a nuestra aplicación"
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// App.jsx
|
|
514
|
+
import en from './locales/en.json';
|
|
515
|
+
import es from './locales/es.json';
|
|
516
|
+
|
|
517
|
+
const locales = { en, es };
|
|
518
|
+
const [locale, setLocale] = useState('en');
|
|
519
|
+
|
|
520
|
+
function t(key) {
|
|
521
|
+
return locales[locale]?.[key] || key;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function App() {
|
|
525
|
+
return (
|
|
526
|
+
<div>
|
|
527
|
+
<h1>{t('greeting')}</h1>
|
|
528
|
+
<p>{t('welcome')}</p>
|
|
529
|
+
</div>
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## API Reference
|
|
537
|
+
|
|
538
|
+
### ASTParser
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
new ASTParser(options: ScanOptions)
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**ScanOptions:**
|
|
545
|
+
- `filePath: string` - Path to file or directory
|
|
546
|
+
- `functionName?: string` - Translation function name (default: 't')
|
|
547
|
+
- `componentName?: string` - Translation component name (default: 'T')
|
|
548
|
+
|
|
549
|
+
**Methods:**
|
|
550
|
+
- `parseFile(filePath: string): ScanResult` - Parse single file
|
|
551
|
+
- `parseContent(content: string, filePath?: string): ScanResult` - Parse content string
|
|
552
|
+
- `scanDirectory(dirPath: string, extensions?: string[]): ScanResult` - Scan directory
|
|
553
|
+
|
|
554
|
+
### TranslationProvider
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
interface TranslationProvider {
|
|
558
|
+
translate(request: TranslationRequest): Promise<TranslationResult>;
|
|
559
|
+
translateBatch(requests: TranslationRequest[]): Promise<TranslationResult[]>;
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**TranslationRequest:**
|
|
564
|
+
```typescript
|
|
565
|
+
{
|
|
566
|
+
sourceText: string;
|
|
567
|
+
sourceLocale: string;
|
|
568
|
+
targetLocale: string;
|
|
569
|
+
context?: string;
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**TranslationResult:**
|
|
574
|
+
```typescript
|
|
575
|
+
{
|
|
576
|
+
translatedText: string;
|
|
577
|
+
sourceText: string;
|
|
578
|
+
success: boolean;
|
|
579
|
+
error?: string;
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### AITranslator
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
new AITranslator(provider: TranslationProvider)
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Methods:**
|
|
590
|
+
- `translateMissingKeys(sourceData, targetData, sourceLocale, targetLocale): Promise<LocaleData>`
|
|
591
|
+
|
|
592
|
+
### FileStorage
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
new FileStorage(basePath: string)
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Methods:**
|
|
599
|
+
- `loadLocale(locale: string): LocaleFile | null`
|
|
600
|
+
- `saveLocale(locale: string, data: LocaleData): void`
|
|
601
|
+
- `loadAllLocales(): Map<string, LocaleFile>`
|
|
602
|
+
- `localeExists(locale: string): boolean`
|
|
603
|
+
- `deleteLocale(locale: string): boolean`
|
|
604
|
+
- `getAvailableLocales(): string[]`
|
|
605
|
+
- `mergeLocaleData(locale: string, newData: LocaleData, preserveExisting?: boolean): LocaleData`
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Error Handling
|
|
610
|
+
|
|
611
|
+
All AI providers return proper error information:
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
const result = await translator.translate({
|
|
615
|
+
sourceText: 'Hello',
|
|
616
|
+
sourceLocale: 'en',
|
|
617
|
+
targetLocale: 'es'
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
if (!result.success) {
|
|
621
|
+
console.error('Translation failed:', result.error);
|
|
622
|
+
// Handle errors appropriately
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Common errors:
|
|
627
|
+
- `429` - Rate limit exceeded
|
|
628
|
+
- `insufficient_quota` - No API credits left
|
|
629
|
+
- `invalid_api_key` - Invalid API key
|
|
630
|
+
- `model_not_found` - Model doesn't exist
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## License
|
|
635
|
+
|
|
636
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lokal-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core business logic for LOKAL - AST parsing, AI connectors, and storage",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "tsup --watch",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"lint": "echo 'Use root lint command'"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@babel/parser": "^7.23.0",
|
|
22
|
+
"@babel/traverse": "^7.23.0",
|
|
23
|
+
"@babel/types": "^7.23.0",
|
|
24
|
+
"cosmiconfig": "^8.3.0",
|
|
25
|
+
"deepmerge": "^4.3.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.10.0",
|
|
29
|
+
"eslint": "^8.57.0",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.3.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|