@lingo.dev/_sdk 0.16.4 → 0.17.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/build/index.cjs +119 -14
- package/build/index.d.cts +59 -1
- package/build/index.d.ts +59 -1
- package/build/index.mjs +111 -6
- package/package.json +1 -1
package/build/index.cjs
CHANGED
|
@@ -114,7 +114,13 @@ var engineParamsSchema = _zod2.default.object({
|
|
|
114
114
|
apiUrl: _zod2.default.string().url().default("https://api.lingo.dev"),
|
|
115
115
|
batchSize: _zod2.default.number().int().gt(0).lte(250).default(25),
|
|
116
116
|
idealBatchItemSize: _zod2.default.number().int().gt(0).lte(2500).default(250),
|
|
117
|
-
engineId: _zod2.default.string().optional()
|
|
117
|
+
engineId: _zod2.default.string().optional(),
|
|
118
|
+
// Number of times a localization request is retried after a transient
|
|
119
|
+
// failure (5xx response or network error). `0` disables retries.
|
|
120
|
+
maxRetries: _zod2.default.number().int().gte(0).default(3),
|
|
121
|
+
// Base delay (ms) for the exponential backoff between retries. The actual
|
|
122
|
+
// wait grows as `retryDelayMs * 2 ** attempt` plus a small random jitter.
|
|
123
|
+
retryDelayMs: _zod2.default.number().int().gte(0).default(500)
|
|
118
124
|
}).passthrough();
|
|
119
125
|
var normalizedLocaleCodeSchema = __spec.localeCodeSchema.transform(__spec.normalizeLocale);
|
|
120
126
|
var payloadSchema = _zod2.default.record(_zod2.default.string(), _zod2.default.any());
|
|
@@ -129,6 +135,12 @@ var localizationParamsSchema = _zod2.default.object({
|
|
|
129
135
|
filePath: _zod2.default.string().optional(),
|
|
130
136
|
triggerType: _zod2.default.enum(["cli", "ci"]).optional()
|
|
131
137
|
});
|
|
138
|
+
var estimateItemsSchema = _zod2.default.array(
|
|
139
|
+
_zod2.default.object({
|
|
140
|
+
targetLocale: normalizedLocaleCodeSchema,
|
|
141
|
+
sourceChars: _zod2.default.number().int().nonnegative()
|
|
142
|
+
})
|
|
143
|
+
).min(1);
|
|
132
144
|
var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
133
145
|
|
|
134
146
|
__init() {this.sessionId = _cuid2.createId.call(void 0, )}
|
|
@@ -166,6 +178,74 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
166
178
|
}
|
|
167
179
|
throw new Error(context ? `${context}: ${msg}` : msg);
|
|
168
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
183
|
+
*/
|
|
184
|
+
static sleep(ms, signal) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
if (_optionalChain([signal, 'optionalAccess', _4 => _4.aborted])) {
|
|
187
|
+
reject(new Error("Operation was aborted"));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const onAbort = () => {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
reject(new Error("Operation was aborted"));
|
|
193
|
+
};
|
|
194
|
+
const timer = setTimeout(() => {
|
|
195
|
+
_optionalChain([signal, 'optionalAccess', _5 => _5.removeEventListener, 'call', _6 => _6("abort", onAbort)]);
|
|
196
|
+
resolve();
|
|
197
|
+
}, ms);
|
|
198
|
+
_optionalChain([signal, 'optionalAccess', _7 => _7.addEventListener, 'call', _8 => _8("abort", onAbort, { once: true })]);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Exponential backoff with full jitter: a random delay in
|
|
203
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
204
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
205
|
+
*/
|
|
206
|
+
backoffDelay(attempt) {
|
|
207
|
+
const ceiling = this.config.retryDelayMs * 2 ** attempt;
|
|
208
|
+
return Math.round(Math.random() * ceiling);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
212
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
213
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
214
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
215
|
+
* retried.
|
|
216
|
+
* @param url - The request URL
|
|
217
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
218
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
219
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
220
|
+
*/
|
|
221
|
+
async fetchWithRetry(url, init, signal) {
|
|
222
|
+
const { maxRetries } = this.config;
|
|
223
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
224
|
+
if (_optionalChain([signal, 'optionalAccess', _9 => _9.aborted])) {
|
|
225
|
+
throw new Error("Operation was aborted");
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const res = await fetch(url, init);
|
|
229
|
+
const isServerError = res.status >= 500 && res.status < 600;
|
|
230
|
+
if (isServerError && attempt < maxRetries) {
|
|
231
|
+
await _optionalChain([res, 'access', _10 => _10.body, 'optionalAccess', _11 => _11.cancel, 'call', _12 => _12()]);
|
|
232
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
return res;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (_optionalChain([signal, 'optionalAccess', _13 => _13.aborted])) {
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
if (attempt < maxRetries) {
|
|
241
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
throw new Error("Localization request failed after exhausting retries");
|
|
248
|
+
}
|
|
169
249
|
/**
|
|
170
250
|
* Create a new LingoDotDevEngine instance
|
|
171
251
|
* @param config - Configuration options for the Engine
|
|
@@ -233,12 +313,16 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
233
313
|
metadata: filePath ? { filePath } : void 0,
|
|
234
314
|
...this.config.engineId && { engineId: this.config.engineId }
|
|
235
315
|
};
|
|
236
|
-
const res = await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
316
|
+
const res = await this.fetchWithRetry(
|
|
317
|
+
url,
|
|
318
|
+
{
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: this.headers,
|
|
321
|
+
body: JSON.stringify(body, null, 2),
|
|
322
|
+
signal
|
|
323
|
+
},
|
|
240
324
|
signal
|
|
241
|
-
|
|
325
|
+
);
|
|
242
326
|
await _LingoDotDevEngine.throwOnHttpError(res);
|
|
243
327
|
const jsonResponse = await res.json();
|
|
244
328
|
if (!jsonResponse.data && jsonResponse.error) {
|
|
@@ -579,7 +663,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
579
663
|
break;
|
|
580
664
|
}
|
|
581
665
|
const siblings = Array.from(parent.childNodes).filter(
|
|
582
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
666
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _14 => _14.textContent, 'optionalAccess', _15 => _15.trim, 'call', _16 => _16()])
|
|
583
667
|
);
|
|
584
668
|
const index = siblings.indexOf(current);
|
|
585
669
|
if (index !== -1) {
|
|
@@ -599,7 +683,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
599
683
|
parent = parent.parentElement;
|
|
600
684
|
}
|
|
601
685
|
if (node.nodeType === 3) {
|
|
602
|
-
const text = _optionalChain([node, 'access',
|
|
686
|
+
const text = _optionalChain([node, 'access', _17 => _17.textContent, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()]) || "";
|
|
603
687
|
if (text) {
|
|
604
688
|
extractedContent[getPath(node)] = text;
|
|
605
689
|
}
|
|
@@ -614,15 +698,15 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
614
698
|
}
|
|
615
699
|
});
|
|
616
700
|
Array.from(element.childNodes).filter(
|
|
617
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
701
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _20 => _20.textContent, 'optionalAccess', _21 => _21.trim, 'call', _22 => _22()])
|
|
618
702
|
).forEach(processNode);
|
|
619
703
|
}
|
|
620
704
|
};
|
|
621
705
|
Array.from(document.head.childNodes).filter(
|
|
622
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
706
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _23 => _23.textContent, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()])
|
|
623
707
|
).forEach(processNode);
|
|
624
708
|
Array.from(document.body.childNodes).filter(
|
|
625
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
709
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _26 => _26.textContent, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()])
|
|
626
710
|
).forEach(processNode);
|
|
627
711
|
const localizedContent = await this._localizeRaw(
|
|
628
712
|
extractedContent,
|
|
@@ -638,10 +722,10 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
638
722
|
let current = parent;
|
|
639
723
|
for (const index of indices) {
|
|
640
724
|
const siblings = Array.from(parent.childNodes).filter(
|
|
641
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
725
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _29 => _29.textContent, 'optionalAccess', _30 => _30.trim, 'call', _31 => _31()])
|
|
642
726
|
);
|
|
643
727
|
current = siblings[parseInt(index)] || null;
|
|
644
|
-
if (_optionalChain([current, 'optionalAccess',
|
|
728
|
+
if (_optionalChain([current, 'optionalAccess', _32 => _32.nodeType]) === 1) {
|
|
645
729
|
parent = current;
|
|
646
730
|
}
|
|
647
731
|
}
|
|
@@ -721,6 +805,27 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
721
805
|
throw error;
|
|
722
806
|
}
|
|
723
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Estimate the cost of localizing content BEFORE submitting it.
|
|
810
|
+
* Pure computation server-side — nothing is translated, stored, or billed.
|
|
811
|
+
* @param items - Per-target-locale character counts of translatable source
|
|
812
|
+
* text (sum of source string lengths, excluding keys and markup). Duplicate
|
|
813
|
+
* locales are summed by the server.
|
|
814
|
+
* @param signal - Optional AbortSignal to cancel the operation
|
|
815
|
+
* @returns Promise resolving to an approximate cost with per-locale breakdown
|
|
816
|
+
*/
|
|
817
|
+
async estimate(items, signal) {
|
|
818
|
+
const parsedItems = estimateItemsSchema.parse(items);
|
|
819
|
+
const url = `${this.config.apiUrl}/process/estimate`;
|
|
820
|
+
const res = await fetch(url, {
|
|
821
|
+
method: "POST",
|
|
822
|
+
headers: this.headers,
|
|
823
|
+
body: JSON.stringify({ items: parsedItems }),
|
|
824
|
+
signal
|
|
825
|
+
});
|
|
826
|
+
await _LingoDotDevEngine.throwOnHttpError(res, "Error estimating cost");
|
|
827
|
+
return res.json();
|
|
828
|
+
}
|
|
724
829
|
async whoami(signal) {
|
|
725
830
|
const url = `${this.config.apiUrl}/users/me`;
|
|
726
831
|
const res = await fetch(url, {
|
|
@@ -730,7 +835,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
730
835
|
});
|
|
731
836
|
if (res.ok) {
|
|
732
837
|
const payload = await res.json();
|
|
733
|
-
if (!_optionalChain([payload, 'optionalAccess',
|
|
838
|
+
if (!_optionalChain([payload, 'optionalAccess', _33 => _33.email])) {
|
|
734
839
|
return null;
|
|
735
840
|
}
|
|
736
841
|
return {
|
package/build/index.d.cts
CHANGED
|
@@ -7,6 +7,8 @@ declare const engineParamsSchema: Z.ZodObject<{
|
|
|
7
7
|
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
|
8
8
|
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
|
9
9
|
engineId: Z.ZodOptional<Z.ZodString>;
|
|
10
|
+
maxRetries: Z.ZodDefault<Z.ZodNumber>;
|
|
11
|
+
retryDelayMs: Z.ZodDefault<Z.ZodNumber>;
|
|
10
12
|
}, Z.core.$loose>;
|
|
11
13
|
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
|
12
14
|
declare const localizationParamsSchema: Z.ZodObject<{
|
|
@@ -21,6 +23,27 @@ declare const localizationParamsSchema: Z.ZodObject<{
|
|
|
21
23
|
ci: "ci";
|
|
22
24
|
}>>;
|
|
23
25
|
}, Z.core.$strip>;
|
|
26
|
+
/**
|
|
27
|
+
* Approximate localization cost returned by `/process/estimate`.
|
|
28
|
+
* `approximate` is always true — the estimate is a chars→tokens heuristic,
|
|
29
|
+
* not a quote. Actual cost may differ.
|
|
30
|
+
*/
|
|
31
|
+
type CostEstimate = {
|
|
32
|
+
approximate: boolean;
|
|
33
|
+
totals: {
|
|
34
|
+
sourceChars: number;
|
|
35
|
+
estimatedOutputTokens: number;
|
|
36
|
+
estimatedLlmCostUsd: number;
|
|
37
|
+
estimatedLocalizationCostUsd: number;
|
|
38
|
+
estimatedTotalCostUsd: number;
|
|
39
|
+
};
|
|
40
|
+
byLocale: {
|
|
41
|
+
targetLocale: string;
|
|
42
|
+
sourceChars: number;
|
|
43
|
+
estimatedOutputTokens: number;
|
|
44
|
+
estimatedCostUsd: number;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
24
47
|
/**
|
|
25
48
|
* LingoDotDevEngine class for interacting with the LingoDotDev API
|
|
26
49
|
* A powerful localization engine that supports various content types including
|
|
@@ -32,6 +55,28 @@ declare class LingoDotDevEngine {
|
|
|
32
55
|
private get headers();
|
|
33
56
|
private static extractErrorMessage;
|
|
34
57
|
private static throwOnHttpError;
|
|
58
|
+
/**
|
|
59
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
60
|
+
*/
|
|
61
|
+
private static sleep;
|
|
62
|
+
/**
|
|
63
|
+
* Exponential backoff with full jitter: a random delay in
|
|
64
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
65
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
66
|
+
*/
|
|
67
|
+
private backoffDelay;
|
|
68
|
+
/**
|
|
69
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
70
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
71
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
72
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
73
|
+
* retried.
|
|
74
|
+
* @param url - The request URL
|
|
75
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
76
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
77
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
78
|
+
*/
|
|
79
|
+
private fetchWithRetry;
|
|
35
80
|
/**
|
|
36
81
|
* Create a new LingoDotDevEngine instance
|
|
37
82
|
* @param config - Configuration options for the Engine
|
|
@@ -158,6 +203,19 @@ declare class LingoDotDevEngine {
|
|
|
158
203
|
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
|
159
204
|
*/
|
|
160
205
|
recognizeLocale(text: string, signal?: AbortSignal): Promise<LocaleCode>;
|
|
206
|
+
/**
|
|
207
|
+
* Estimate the cost of localizing content BEFORE submitting it.
|
|
208
|
+
* Pure computation server-side — nothing is translated, stored, or billed.
|
|
209
|
+
* @param items - Per-target-locale character counts of translatable source
|
|
210
|
+
* text (sum of source string lengths, excluding keys and markup). Duplicate
|
|
211
|
+
* locales are summed by the server.
|
|
212
|
+
* @param signal - Optional AbortSignal to cancel the operation
|
|
213
|
+
* @returns Promise resolving to an approximate cost with per-locale breakdown
|
|
214
|
+
*/
|
|
215
|
+
estimate(items: {
|
|
216
|
+
targetLocale: string;
|
|
217
|
+
sourceChars: number;
|
|
218
|
+
}[], signal?: AbortSignal): Promise<CostEstimate>;
|
|
161
219
|
whoami(signal?: AbortSignal): Promise<{
|
|
162
220
|
email: string;
|
|
163
221
|
id: string;
|
|
@@ -178,4 +236,4 @@ declare class LingoEngine extends LingoDotDevEngine {
|
|
|
178
236
|
constructor(config: Partial<Z.infer<typeof engineParamsSchema>>);
|
|
179
237
|
}
|
|
180
238
|
|
|
181
|
-
export { LingoDotDevEngine, LingoEngine, ReplexicaEngine };
|
|
239
|
+
export { type CostEstimate, LingoDotDevEngine, LingoEngine, ReplexicaEngine };
|
package/build/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ declare const engineParamsSchema: Z.ZodObject<{
|
|
|
7
7
|
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
|
8
8
|
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
|
9
9
|
engineId: Z.ZodOptional<Z.ZodString>;
|
|
10
|
+
maxRetries: Z.ZodDefault<Z.ZodNumber>;
|
|
11
|
+
retryDelayMs: Z.ZodDefault<Z.ZodNumber>;
|
|
10
12
|
}, Z.core.$loose>;
|
|
11
13
|
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
|
12
14
|
declare const localizationParamsSchema: Z.ZodObject<{
|
|
@@ -21,6 +23,27 @@ declare const localizationParamsSchema: Z.ZodObject<{
|
|
|
21
23
|
ci: "ci";
|
|
22
24
|
}>>;
|
|
23
25
|
}, Z.core.$strip>;
|
|
26
|
+
/**
|
|
27
|
+
* Approximate localization cost returned by `/process/estimate`.
|
|
28
|
+
* `approximate` is always true — the estimate is a chars→tokens heuristic,
|
|
29
|
+
* not a quote. Actual cost may differ.
|
|
30
|
+
*/
|
|
31
|
+
type CostEstimate = {
|
|
32
|
+
approximate: boolean;
|
|
33
|
+
totals: {
|
|
34
|
+
sourceChars: number;
|
|
35
|
+
estimatedOutputTokens: number;
|
|
36
|
+
estimatedLlmCostUsd: number;
|
|
37
|
+
estimatedLocalizationCostUsd: number;
|
|
38
|
+
estimatedTotalCostUsd: number;
|
|
39
|
+
};
|
|
40
|
+
byLocale: {
|
|
41
|
+
targetLocale: string;
|
|
42
|
+
sourceChars: number;
|
|
43
|
+
estimatedOutputTokens: number;
|
|
44
|
+
estimatedCostUsd: number;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
24
47
|
/**
|
|
25
48
|
* LingoDotDevEngine class for interacting with the LingoDotDev API
|
|
26
49
|
* A powerful localization engine that supports various content types including
|
|
@@ -32,6 +55,28 @@ declare class LingoDotDevEngine {
|
|
|
32
55
|
private get headers();
|
|
33
56
|
private static extractErrorMessage;
|
|
34
57
|
private static throwOnHttpError;
|
|
58
|
+
/**
|
|
59
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
60
|
+
*/
|
|
61
|
+
private static sleep;
|
|
62
|
+
/**
|
|
63
|
+
* Exponential backoff with full jitter: a random delay in
|
|
64
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
65
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
66
|
+
*/
|
|
67
|
+
private backoffDelay;
|
|
68
|
+
/**
|
|
69
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
70
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
71
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
72
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
73
|
+
* retried.
|
|
74
|
+
* @param url - The request URL
|
|
75
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
76
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
77
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
78
|
+
*/
|
|
79
|
+
private fetchWithRetry;
|
|
35
80
|
/**
|
|
36
81
|
* Create a new LingoDotDevEngine instance
|
|
37
82
|
* @param config - Configuration options for the Engine
|
|
@@ -158,6 +203,19 @@ declare class LingoDotDevEngine {
|
|
|
158
203
|
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
|
159
204
|
*/
|
|
160
205
|
recognizeLocale(text: string, signal?: AbortSignal): Promise<LocaleCode>;
|
|
206
|
+
/**
|
|
207
|
+
* Estimate the cost of localizing content BEFORE submitting it.
|
|
208
|
+
* Pure computation server-side — nothing is translated, stored, or billed.
|
|
209
|
+
* @param items - Per-target-locale character counts of translatable source
|
|
210
|
+
* text (sum of source string lengths, excluding keys and markup). Duplicate
|
|
211
|
+
* locales are summed by the server.
|
|
212
|
+
* @param signal - Optional AbortSignal to cancel the operation
|
|
213
|
+
* @returns Promise resolving to an approximate cost with per-locale breakdown
|
|
214
|
+
*/
|
|
215
|
+
estimate(items: {
|
|
216
|
+
targetLocale: string;
|
|
217
|
+
sourceChars: number;
|
|
218
|
+
}[], signal?: AbortSignal): Promise<CostEstimate>;
|
|
161
219
|
whoami(signal?: AbortSignal): Promise<{
|
|
162
220
|
email: string;
|
|
163
221
|
id: string;
|
|
@@ -178,4 +236,4 @@ declare class LingoEngine extends LingoDotDevEngine {
|
|
|
178
236
|
constructor(config: Partial<Z.infer<typeof engineParamsSchema>>);
|
|
179
237
|
}
|
|
180
238
|
|
|
181
|
-
export { LingoDotDevEngine, LingoEngine, ReplexicaEngine };
|
|
239
|
+
export { type CostEstimate, LingoDotDevEngine, LingoEngine, ReplexicaEngine };
|
package/build/index.mjs
CHANGED
|
@@ -114,7 +114,13 @@ var engineParamsSchema = Z.object({
|
|
|
114
114
|
apiUrl: Z.string().url().default("https://api.lingo.dev"),
|
|
115
115
|
batchSize: Z.number().int().gt(0).lte(250).default(25),
|
|
116
116
|
idealBatchItemSize: Z.number().int().gt(0).lte(2500).default(250),
|
|
117
|
-
engineId: Z.string().optional()
|
|
117
|
+
engineId: Z.string().optional(),
|
|
118
|
+
// Number of times a localization request is retried after a transient
|
|
119
|
+
// failure (5xx response or network error). `0` disables retries.
|
|
120
|
+
maxRetries: Z.number().int().gte(0).default(3),
|
|
121
|
+
// Base delay (ms) for the exponential backoff between retries. The actual
|
|
122
|
+
// wait grows as `retryDelayMs * 2 ** attempt` plus a small random jitter.
|
|
123
|
+
retryDelayMs: Z.number().int().gte(0).default(500)
|
|
118
124
|
}).passthrough();
|
|
119
125
|
var normalizedLocaleCodeSchema = localeCodeSchema.transform(normalizeLocale);
|
|
120
126
|
var payloadSchema = Z.record(Z.string(), Z.any());
|
|
@@ -129,6 +135,12 @@ var localizationParamsSchema = Z.object({
|
|
|
129
135
|
filePath: Z.string().optional(),
|
|
130
136
|
triggerType: Z.enum(["cli", "ci"]).optional()
|
|
131
137
|
});
|
|
138
|
+
var estimateItemsSchema = Z.array(
|
|
139
|
+
Z.object({
|
|
140
|
+
targetLocale: normalizedLocaleCodeSchema,
|
|
141
|
+
sourceChars: Z.number().int().nonnegative()
|
|
142
|
+
})
|
|
143
|
+
).min(1);
|
|
132
144
|
var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
133
145
|
config;
|
|
134
146
|
sessionId = createId();
|
|
@@ -166,6 +178,74 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
166
178
|
}
|
|
167
179
|
throw new Error(context ? `${context}: ${msg}` : msg);
|
|
168
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
183
|
+
*/
|
|
184
|
+
static sleep(ms, signal) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
if (signal?.aborted) {
|
|
187
|
+
reject(new Error("Operation was aborted"));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const onAbort = () => {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
reject(new Error("Operation was aborted"));
|
|
193
|
+
};
|
|
194
|
+
const timer = setTimeout(() => {
|
|
195
|
+
signal?.removeEventListener("abort", onAbort);
|
|
196
|
+
resolve();
|
|
197
|
+
}, ms);
|
|
198
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Exponential backoff with full jitter: a random delay in
|
|
203
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
204
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
205
|
+
*/
|
|
206
|
+
backoffDelay(attempt) {
|
|
207
|
+
const ceiling = this.config.retryDelayMs * 2 ** attempt;
|
|
208
|
+
return Math.round(Math.random() * ceiling);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
212
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
213
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
214
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
215
|
+
* retried.
|
|
216
|
+
* @param url - The request URL
|
|
217
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
218
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
219
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
220
|
+
*/
|
|
221
|
+
async fetchWithRetry(url, init, signal) {
|
|
222
|
+
const { maxRetries } = this.config;
|
|
223
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
224
|
+
if (signal?.aborted) {
|
|
225
|
+
throw new Error("Operation was aborted");
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const res = await fetch(url, init);
|
|
229
|
+
const isServerError = res.status >= 500 && res.status < 600;
|
|
230
|
+
if (isServerError && attempt < maxRetries) {
|
|
231
|
+
await res.body?.cancel();
|
|
232
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
return res;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (signal?.aborted) {
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
if (attempt < maxRetries) {
|
|
241
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
throw new Error("Localization request failed after exhausting retries");
|
|
248
|
+
}
|
|
169
249
|
/**
|
|
170
250
|
* Create a new LingoDotDevEngine instance
|
|
171
251
|
* @param config - Configuration options for the Engine
|
|
@@ -233,12 +313,16 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
233
313
|
metadata: filePath ? { filePath } : void 0,
|
|
234
314
|
...this.config.engineId && { engineId: this.config.engineId }
|
|
235
315
|
};
|
|
236
|
-
const res = await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
316
|
+
const res = await this.fetchWithRetry(
|
|
317
|
+
url,
|
|
318
|
+
{
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: this.headers,
|
|
321
|
+
body: JSON.stringify(body, null, 2),
|
|
322
|
+
signal
|
|
323
|
+
},
|
|
240
324
|
signal
|
|
241
|
-
|
|
325
|
+
);
|
|
242
326
|
await _LingoDotDevEngine.throwOnHttpError(res);
|
|
243
327
|
const jsonResponse = await res.json();
|
|
244
328
|
if (!jsonResponse.data && jsonResponse.error) {
|
|
@@ -721,6 +805,27 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
721
805
|
throw error;
|
|
722
806
|
}
|
|
723
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Estimate the cost of localizing content BEFORE submitting it.
|
|
810
|
+
* Pure computation server-side — nothing is translated, stored, or billed.
|
|
811
|
+
* @param items - Per-target-locale character counts of translatable source
|
|
812
|
+
* text (sum of source string lengths, excluding keys and markup). Duplicate
|
|
813
|
+
* locales are summed by the server.
|
|
814
|
+
* @param signal - Optional AbortSignal to cancel the operation
|
|
815
|
+
* @returns Promise resolving to an approximate cost with per-locale breakdown
|
|
816
|
+
*/
|
|
817
|
+
async estimate(items, signal) {
|
|
818
|
+
const parsedItems = estimateItemsSchema.parse(items);
|
|
819
|
+
const url = `${this.config.apiUrl}/process/estimate`;
|
|
820
|
+
const res = await fetch(url, {
|
|
821
|
+
method: "POST",
|
|
822
|
+
headers: this.headers,
|
|
823
|
+
body: JSON.stringify({ items: parsedItems }),
|
|
824
|
+
signal
|
|
825
|
+
});
|
|
826
|
+
await _LingoDotDevEngine.throwOnHttpError(res, "Error estimating cost");
|
|
827
|
+
return res.json();
|
|
828
|
+
}
|
|
724
829
|
async whoami(signal) {
|
|
725
830
|
const url = `${this.config.apiUrl}/users/me`;
|
|
726
831
|
const res = await fetch(url, {
|