ai-l10n-core 1.4.0 → 1.5.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/CHANGELOG.md +62 -0
- package/README.md +117 -27
- package/dist/consoleLogger.d.ts +0 -4
- package/dist/consoleLogger.js +10 -21
- package/dist/translationService.d.ts +42 -6
- package/dist/translationService.js +163 -78
- package/package.json +1 -1
- package/dist/test/consoleLogger.test.d.ts +0 -1
- package/dist/test/consoleLogger.test.js +0 -242
- package/dist/test/translationService.test.d.ts +0 -1
- package/dist/test/translationService.test.js +0 -454
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,68 @@ All notable changes to the core package will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2026-04-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`ApiResponse<T>` type** — Generic discriminated union used as the return type of all API methods:
|
|
12
|
+
```typescript
|
|
13
|
+
type ApiResponse<T> =
|
|
14
|
+
| { success: true; data: T }
|
|
15
|
+
| { success: false; reason: string; message: string };
|
|
16
|
+
```
|
|
17
|
+
- **Balance API** — New `getBalance(apiKey)` method on `L10nTranslationService` retrieves the current character balance from the `GET /v2/balance` endpoint. Returns `ApiResponse<BalanceResponse>` (always resolves, never throws).
|
|
18
|
+
- **`BalanceResponse` type** — `{ currentBalance: number }`
|
|
19
|
+
- **Structured responses for all methods** — `getBalance()`, `getLanguages()`, and `predictLanguages()` now return `ApiResponse<T>` and never throw
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **`translate()` return type changed from `TranslationResult | null` to `TranslationResponse`** (breaking change)
|
|
23
|
+
- `TranslationResponse` is now `ApiResponse<TranslationResult> & { currentBalance?: number }`
|
|
24
|
+
- The method always resolves — it never throws
|
|
25
|
+
- On success: `{ success: true, data: TranslationResult, currentBalance?: number }`
|
|
26
|
+
- On error: `{ success: false, reason: string, message: string, currentBalance?: number }`
|
|
27
|
+
- `reason` values: `"noApiKey"`, `"unauthorized"`, `"paymentRequired"`, `"badRequest"`, `"requestTooLarge"`, `"serverError"`, `"networkError"`, `"translationError"`
|
|
28
|
+
- **`getLanguages()` now requires `apiKey` as its first parameter** (bug fix — previously no API key was sent)
|
|
29
|
+
- New signature: `getLanguages(apiKey: string, options?: ...): Promise<ApiResponse<SupportedLanguagesResponse>>`
|
|
30
|
+
- Returns structured `ApiResponse` instead of throwing; error reasons: `noApiKey`, `unauthorized`, `badRequest`, `networkError`
|
|
31
|
+
- **`getBalance()` no longer throws** — returns `ApiResponse<BalanceResponse>`; error reasons: `noApiKey`, `unauthorized`, `networkError`
|
|
32
|
+
- **`predictLanguages()` no longer throws** — returns `ApiResponse<Language[]>`; error reason: `networkError`
|
|
33
|
+
|
|
34
|
+
### Migration
|
|
35
|
+
```typescript
|
|
36
|
+
// translate() — Before (1.4.x)
|
|
37
|
+
try {
|
|
38
|
+
const result = await service.translate(request, apiKey);
|
|
39
|
+
if (!result) return; // null for 401/402
|
|
40
|
+
console.log(result.translations);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(e.message); // thrown for 400/413/500
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// translate() — After (1.5.0)
|
|
46
|
+
const response = await service.translate(request, apiKey);
|
|
47
|
+
if (!response.success) {
|
|
48
|
+
console.error(response.message, '| reason:', response.reason);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(response.data.translations);
|
|
52
|
+
|
|
53
|
+
// getBalance() — Before (1.4.x)
|
|
54
|
+
const { currentBalance } = await service.getBalance(apiKey); // threw on error
|
|
55
|
+
|
|
56
|
+
// getBalance() — After (1.5.0)
|
|
57
|
+
const result = await service.getBalance(apiKey);
|
|
58
|
+
if (!result.success) console.error(result.reason);
|
|
59
|
+
else console.log(result.data.currentBalance);
|
|
60
|
+
|
|
61
|
+
// getLanguages() — Before (1.4.x)
|
|
62
|
+
const { languages } = await service.getLanguages(options); // no apiKey, threw on error
|
|
63
|
+
|
|
64
|
+
// getLanguages() — After (1.5.0)
|
|
65
|
+
const result = await service.getLanguages(apiKey, options);
|
|
66
|
+
if (!result.success) console.error(result.reason);
|
|
67
|
+
else console.log(result.data.languages);
|
|
68
|
+
```
|
|
69
|
+
|
|
8
70
|
## [1.4.0] - 2026-04-02
|
|
9
71
|
|
|
10
72
|
### Added
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Platform-independent core for AI-powered localization — translation API client
|
|
|
7
7
|
|
|
8
8
|
This is the foundational library for the [ai-l10n](https://www.npmjs.com/package/ai-l10n) ecosystem. It provides the low-level translation API client, logger interface, and language utilities used by the SDK and CLI.
|
|
9
9
|
|
|
10
|
-
Powered by [l10n
|
|
10
|
+
Powered by [l10n](https://l10n.dev).dev
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -117,7 +117,7 @@ const service = new L10nTranslationService(customLogger);
|
|
|
117
117
|
|
|
118
118
|
#### Methods
|
|
119
119
|
|
|
120
|
-
##### `translate(request: TranslationRequest, apiKey: string): Promise<
|
|
120
|
+
##### `translate(request: TranslationRequest, apiKey: string): Promise<TranslationResponse>`
|
|
121
121
|
|
|
122
122
|
Translates localization content using the l10n.dev API.
|
|
123
123
|
|
|
@@ -125,11 +125,44 @@ Translates localization content using the l10n.dev API.
|
|
|
125
125
|
- `request: TranslationRequest` — Translation request configuration
|
|
126
126
|
- `apiKey: string` — API key for authentication
|
|
127
127
|
|
|
128
|
-
**Returns:** `Promise<
|
|
128
|
+
**Returns:** `Promise<TranslationResponse>` — Always resolves (never throws). Check `success` field to determine success or failure.
|
|
129
129
|
|
|
130
|
-
**
|
|
130
|
+
**Example:**
|
|
131
|
+
```typescript
|
|
132
|
+
const response = await service.translate(request, apiKey);
|
|
133
|
+
if (!response.success) {
|
|
134
|
+
console.error(response.message, 'reason:', response.reason);
|
|
135
|
+
if (response.reason === 'paymentRequired') {
|
|
136
|
+
console.log('Current balance:', response.currentBalance);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
console.log('Translated:', response.data.translations);
|
|
140
|
+
console.log('Balance remaining:', response.currentBalance);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
##### `getBalance(apiKey: string): Promise<ApiResponse<BalanceResponse>>`
|
|
145
|
+
|
|
146
|
+
Retrieves the current character balance available for translation.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
- `apiKey: string` — API key for authentication
|
|
150
|
+
|
|
151
|
+
**Returns:** `Promise<ApiResponse<BalanceResponse>>` — Always resolves (never throws). Check `success` to determine success or failure. On success, `data.currentBalance` holds the number of characters available.
|
|
131
152
|
|
|
132
|
-
|
|
153
|
+
**Error reasons:** `noApiKey`, `unauthorized`, `networkError`
|
|
154
|
+
|
|
155
|
+
**Example:**
|
|
156
|
+
```typescript
|
|
157
|
+
const response = await service.getBalance(apiKey);
|
|
158
|
+
if (!response.success) {
|
|
159
|
+
console.error(response.message, 'reason:', response.reason);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`Available: ${response.data.currentBalance.toLocaleString()} characters`);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
##### `predictLanguages(input: string, limit?: number): Promise<ApiResponse<Language[]>>`
|
|
133
166
|
|
|
134
167
|
Predicts possible language codes from a text input (language name in English or native language, region, or script).
|
|
135
168
|
|
|
@@ -137,39 +170,76 @@ Predicts possible language codes from a text input (language name in English or
|
|
|
137
170
|
- `input: string` — Text to analyze
|
|
138
171
|
- `limit?: number` — Maximum number of predictions (default: 10)
|
|
139
172
|
|
|
140
|
-
**Returns:** `Promise<Language[]
|
|
173
|
+
**Returns:** `Promise<ApiResponse<Language[]>>` — Always resolves (never throws). On success, `data` is an array of predicted languages with codes and names.
|
|
174
|
+
|
|
175
|
+
**Error reasons:** `networkError`
|
|
141
176
|
|
|
142
|
-
##### `getLanguages(options?: { codes?: string[]; proficiencyLevels?: LanguageProficiencyLevel[] }): Promise<SupportedLanguagesResponse
|
|
177
|
+
##### `getLanguages(apiKey: string, options?: { codes?: string[]; proficiencyLevels?: LanguageProficiencyLevel[] }): Promise<ApiResponse<SupportedLanguagesResponse>>`
|
|
143
178
|
|
|
144
179
|
Retrieves a list of supported languages, optionally filtered by language codes or proficiency levels.
|
|
145
180
|
|
|
146
181
|
**Parameters:**
|
|
182
|
+
- `apiKey: string` — API key for authentication
|
|
147
183
|
- `options?` — Optional filter object
|
|
148
184
|
- `codes?: string[]` — Filter by specific language codes (e.g., `["en", "es", "fr"]`)
|
|
149
185
|
- `proficiencyLevels?: LanguageProficiencyLevel[]` — Filter by proficiency level: `"strong"`, `"high"`, `"moderate"`, or `"limited"`
|
|
150
186
|
|
|
151
|
-
**Returns:** `Promise<SupportedLanguagesResponse
|
|
187
|
+
**Returns:** `Promise<ApiResponse<SupportedLanguagesResponse>>` — Always resolves (never throws). On success, `data.languages` is an array of `SupportedLanguage` entries.
|
|
152
188
|
|
|
153
|
-
**
|
|
189
|
+
**Error reasons:** `noApiKey`, `unauthorized`, `badRequest`, `networkError`
|
|
154
190
|
|
|
155
191
|
**Example:**
|
|
156
192
|
```typescript
|
|
157
193
|
// Get all supported languages
|
|
158
|
-
const
|
|
194
|
+
const response = await service.getLanguages(apiKey);
|
|
195
|
+
if (!response.success) {
|
|
196
|
+
console.error(response.message, 'reason:', response.reason);
|
|
197
|
+
} else {
|
|
198
|
+
const { languages } = response.data;
|
|
199
|
+
}
|
|
159
200
|
|
|
160
201
|
// Filter by proficiency level
|
|
161
|
-
const
|
|
202
|
+
const response = await service.getLanguages(apiKey, {
|
|
162
203
|
proficiencyLevels: ['strong', 'high'],
|
|
163
204
|
});
|
|
164
205
|
|
|
165
206
|
// Filter by specific codes
|
|
166
|
-
const
|
|
207
|
+
const response = await service.getLanguages(apiKey, {
|
|
167
208
|
codes: ['en', 'es', 'fr'],
|
|
168
209
|
});
|
|
169
210
|
```
|
|
170
211
|
|
|
171
212
|
#### Types
|
|
172
213
|
|
|
214
|
+
##### ApiResponse\<T\>
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
type ApiResponse<T> =
|
|
218
|
+
| { success: true; data: T }
|
|
219
|
+
| { success: false; reason: string; message: string };
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
All methods that contact the API return an `ApiResponse<T>`. Check `success` before accessing `data`.
|
|
223
|
+
|
|
224
|
+
##### TranslationResponse
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
type TranslationResponse = ApiResponse<TranslationResult> & { currentBalance?: number };
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The union distributes over the intersection, so `currentBalance?` is available on both branches:
|
|
231
|
+
- On `success: true`: `data` holds the `TranslationResult`, `currentBalance` is the remaining balance.
|
|
232
|
+
- On `success: false`: `reason` and `message` describe the error, `currentBalance` is set when `reason` is `"paymentRequired"`.
|
|
233
|
+
|
|
234
|
+
##### BalanceResponse
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface BalanceResponse {
|
|
238
|
+
/** Current balance of characters available for translation. */
|
|
239
|
+
currentBalance: number;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
173
243
|
##### TranslationRequest
|
|
174
244
|
|
|
175
245
|
```typescript
|
|
@@ -192,9 +262,6 @@ interface TranslationRequest {
|
|
|
192
262
|
/** Translate metadata along with UI strings (e.g., ARB `@key` descriptions) */
|
|
193
263
|
translateMetadata?: boolean;
|
|
194
264
|
|
|
195
|
-
/** Return translations as JSON string */
|
|
196
|
-
returnTranslationsAsString: boolean;
|
|
197
|
-
|
|
198
265
|
/** Client identifier */
|
|
199
266
|
client: string;
|
|
200
267
|
|
|
@@ -284,9 +351,6 @@ enum FinishReason {
|
|
|
284
351
|
/** Some content was filtered due to content policy */
|
|
285
352
|
contentFilter = "contentFilter",
|
|
286
353
|
|
|
287
|
-
/** Insufficient character balance */
|
|
288
|
-
insufficientBalance = "insufficientBalance",
|
|
289
|
-
|
|
290
354
|
/** Translation failed with error */
|
|
291
355
|
error = "error"
|
|
292
356
|
}
|
|
@@ -319,17 +383,43 @@ interface SupportedLanguage {
|
|
|
319
383
|
|
|
320
384
|
#### Error Handling
|
|
321
385
|
|
|
322
|
-
|
|
386
|
+
All methods always resolve — they never throw. Check `response.success`:
|
|
387
|
+
|
|
388
|
+
##### `translate()` error reasons
|
|
389
|
+
|
|
390
|
+
| `reason` | Description |
|
|
391
|
+
|----------|-------------|
|
|
392
|
+
| `"noApiKey"` | API key was not provided |
|
|
393
|
+
| `"unauthorized"` | API key is invalid (401) |
|
|
394
|
+
| `"paymentRequired"` | Insufficient balance (402); `currentBalance` is set |
|
|
395
|
+
| `"badRequest"` | Validation error (400); `message` contains details |
|
|
396
|
+
| `"requestTooLarge"` | Request exceeds 5 MB (413) |
|
|
397
|
+
| `"serverError"` | Internal server error (500) |
|
|
398
|
+
| `"networkError"` | Connection or other failure |
|
|
399
|
+
| `"translationError"` | API returned `finishReason: "error"` |
|
|
400
|
+
|
|
401
|
+
##### `getBalance()` error reasons
|
|
402
|
+
|
|
403
|
+
| `reason` | Description |
|
|
404
|
+
|----------|-------------|
|
|
405
|
+
| `"noApiKey"` | API key was not provided |
|
|
406
|
+
| `"unauthorized"` | API key is invalid (401) |
|
|
407
|
+
| `"networkError"` | Connection or other failure |
|
|
408
|
+
|
|
409
|
+
##### `getLanguages()` error reasons
|
|
323
410
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
411
|
+
| `reason` | Description |
|
|
412
|
+
|----------|-------------|
|
|
413
|
+
| `"noApiKey"` | API key was not provided |
|
|
414
|
+
| `"unauthorized"` | API key is invalid (401) |
|
|
415
|
+
| `"badRequest"` | Validation error (400); `message` contains details |
|
|
416
|
+
| `"networkError"` | Connection or other failure |
|
|
327
417
|
|
|
328
|
-
|
|
418
|
+
##### `predictLanguages()` error reasons
|
|
329
419
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
420
|
+
| `reason` | Description |
|
|
421
|
+
|----------|-------------|
|
|
422
|
+
| `"networkError"` | Connection or other failure |
|
|
333
423
|
|
|
334
424
|
---
|
|
335
425
|
|
|
@@ -484,4 +574,4 @@ MIT
|
|
|
484
574
|
|
|
485
575
|
## Credits
|
|
486
576
|
|
|
487
|
-
Powered by [l10n
|
|
577
|
+
Powered by [l10n](https://l10n.dev).dev — AI-powered localization service
|
package/dist/consoleLogger.d.ts
CHANGED
package/dist/consoleLogger.js
CHANGED
|
@@ -9,12 +9,11 @@ class ConsoleLogger {
|
|
|
9
9
|
* Show and log error message
|
|
10
10
|
*/
|
|
11
11
|
showAndLogError(message, error, context, linkBtnText, url) {
|
|
12
|
-
console.error(`❌ ${message}`);
|
|
13
|
-
if (context) {
|
|
14
|
-
console.error(`Context: ${context}`);
|
|
15
|
-
}
|
|
16
12
|
if (error) {
|
|
17
|
-
|
|
13
|
+
console.error(`❌ ${message}`, context ?? "", error);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.error(`❌ ${message}`, context ?? "");
|
|
18
17
|
}
|
|
19
18
|
if (linkBtnText && url) {
|
|
20
19
|
console.error(`${linkBtnText}: ${url}`);
|
|
@@ -30,32 +29,22 @@ class ConsoleLogger {
|
|
|
30
29
|
* Log warning message
|
|
31
30
|
*/
|
|
32
31
|
logWarning(message, error) {
|
|
33
|
-
console.warn(`⚠️ ${message}`);
|
|
34
32
|
if (error) {
|
|
35
|
-
|
|
33
|
+
console.warn(`⚠️ ${message}`, error);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.warn(`⚠️ ${message}`);
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
40
|
* Log error message
|
|
40
41
|
*/
|
|
41
42
|
logError(message, error) {
|
|
42
|
-
console.error(`❌ ${message}`);
|
|
43
43
|
if (error) {
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Log error details to console
|
|
49
|
-
*/
|
|
50
|
-
logErrorDetails(error, logFn) {
|
|
51
|
-
if (error instanceof Error) {
|
|
52
|
-
logFn(`Error: ${error.message}`);
|
|
53
|
-
if (error.stack) {
|
|
54
|
-
logFn(error.stack);
|
|
55
|
-
}
|
|
44
|
+
console.error(`❌ ${message}`, error);
|
|
56
45
|
}
|
|
57
46
|
else {
|
|
58
|
-
|
|
47
|
+
console.error(`❌ ${message}`);
|
|
59
48
|
}
|
|
60
49
|
}
|
|
61
50
|
}
|
|
@@ -16,7 +16,6 @@ export interface TranslationRequest {
|
|
|
16
16
|
useShortening?: boolean;
|
|
17
17
|
generatePluralForms?: boolean;
|
|
18
18
|
translateMetadata?: boolean;
|
|
19
|
-
returnTranslationsAsString: boolean;
|
|
20
19
|
client: string;
|
|
21
20
|
translateOnlyNewStrings?: boolean;
|
|
22
21
|
targetStrings?: string;
|
|
@@ -50,7 +49,6 @@ export declare enum FinishReason {
|
|
|
50
49
|
stop = "stop",
|
|
51
50
|
length = "length",
|
|
52
51
|
contentFilter = "contentFilter",
|
|
53
|
-
insufficientBalance = "insufficientBalance",
|
|
54
52
|
error = "error"
|
|
55
53
|
}
|
|
56
54
|
export interface Language {
|
|
@@ -82,13 +80,51 @@ export interface SupportedLanguage {
|
|
|
82
80
|
export interface SupportedLanguagesResponse {
|
|
83
81
|
languages: SupportedLanguage[];
|
|
84
82
|
}
|
|
83
|
+
export interface BalanceResponse {
|
|
84
|
+
/** Current balance of characters available for translation. */
|
|
85
|
+
currentBalance: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Discriminated union returned by all public service methods.
|
|
89
|
+
* Check `success` to narrow to the data or error branch.
|
|
90
|
+
*/
|
|
91
|
+
export type ApiResponse<T> = {
|
|
92
|
+
success: true;
|
|
93
|
+
data: T;
|
|
94
|
+
} | {
|
|
95
|
+
success: false;
|
|
96
|
+
reason: string;
|
|
97
|
+
message: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Response from translate(). Extends ApiResponse<TranslationResult> with an
|
|
101
|
+
* optional currentBalance field present on both the success and error branches.
|
|
102
|
+
*
|
|
103
|
+
* On success: data contains the TranslationResult; currentBalance is the remaining balance.
|
|
104
|
+
* On error: reason and message describe the failure; currentBalance is set on 402 errors.
|
|
105
|
+
*/
|
|
106
|
+
export type TranslationResponse = ApiResponse<TranslationResult> & {
|
|
107
|
+
currentBalance?: number;
|
|
108
|
+
};
|
|
85
109
|
export declare class L10nTranslationService {
|
|
86
110
|
private readonly logger;
|
|
87
111
|
constructor(logger?: ILogger);
|
|
88
|
-
|
|
112
|
+
getBalance(apiKey: string): Promise<ApiResponse<BalanceResponse>>;
|
|
113
|
+
getLanguages(apiKey: string, options?: {
|
|
89
114
|
codes?: string[];
|
|
90
115
|
proficiencyLevels?: LanguageProficiencyLevel[];
|
|
91
|
-
}): Promise<SupportedLanguagesResponse
|
|
92
|
-
predictLanguages(input: string, limit?: number): Promise<Language[]
|
|
93
|
-
translate(request: TranslationRequest, apiKey: string): Promise<
|
|
116
|
+
}): Promise<ApiResponse<SupportedLanguagesResponse>>;
|
|
117
|
+
predictLanguages(input: string, limit?: number): Promise<ApiResponse<Language[]>>;
|
|
118
|
+
translate(request: TranslationRequest, apiKey: string): Promise<TranslationResponse>;
|
|
119
|
+
private checkApiKey;
|
|
120
|
+
/**
|
|
121
|
+
* Maps an HTTP error to a structured ApiResponse error.
|
|
122
|
+
* Handles 400 (with validation error extraction), 401, 403, 429, 502, 503, and 500.
|
|
123
|
+
*/
|
|
124
|
+
private handleErrorResponse;
|
|
125
|
+
/**
|
|
126
|
+
* Attempts to parse the response body as JSON. If parsing fails, returns the raw text.
|
|
127
|
+
* Logs a warning if JSON parsing fails.
|
|
128
|
+
*/
|
|
129
|
+
private parseErrorBody;
|
|
94
130
|
}
|