@lingo.dev/_sdk 0.7.12
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 +19 -0
- package/build/index.cjs +326 -0
- package/build/index.d.cts +150 -0
- package/build/index.d.ts +150 -0
- package/build/index.mjs +326 -0
- package/package.json +37 -0
package/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Lingo.dev SDK
|
2
|
+
|
3
|
+
Official SDK for Lingo.dev.
|
4
|
+
|
5
|
+
### Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
npm i lingo.dev
|
9
|
+
```
|
10
|
+
|
11
|
+
### Usage
|
12
|
+
|
13
|
+
```
|
14
|
+
import {} from 'lingo.dev/sdk';
|
15
|
+
```
|
16
|
+
|
17
|
+
### Documentation
|
18
|
+
|
19
|
+
[Documentation](https://lingo.dev/go/docs)
|
package/build/index.cjs
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/index.ts
|
2
|
+
var _zod = require('zod'); var _zod2 = _interopRequireDefault(_zod);
|
3
|
+
var __spec = require('@lingo.dev/_spec');
|
4
|
+
var _cuid2 = require('@paralleldrive/cuid2');
|
5
|
+
var engineParamsSchema = _zod2.default.object({
|
6
|
+
apiKey: _zod2.default.string(),
|
7
|
+
apiUrl: _zod2.default.string().url().default("https://engine.lingo.dev"),
|
8
|
+
batchSize: _zod2.default.number().int().gt(0).lte(250).default(25),
|
9
|
+
idealBatchItemSize: _zod2.default.number().int().gt(0).lte(2500).default(250)
|
10
|
+
}).passthrough();
|
11
|
+
var payloadSchema = _zod2.default.record(_zod2.default.string(), _zod2.default.any());
|
12
|
+
var localizationParamsSchema = _zod2.default.object({
|
13
|
+
sourceLocale: __spec.localeCodeSchema,
|
14
|
+
targetLocale: __spec.localeCodeSchema,
|
15
|
+
fast: _zod2.default.boolean().optional()
|
16
|
+
});
|
17
|
+
var referenceSchema = _zod2.default.record(__spec.localeCodeSchema, payloadSchema);
|
18
|
+
var ReplexicaEngine = class {
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Create a new ReplexicaEngine instance
|
22
|
+
* @param config - Configuration options for the Engine
|
23
|
+
*/
|
24
|
+
constructor(config) {
|
25
|
+
this.config = engineParamsSchema.parse(config);
|
26
|
+
}
|
27
|
+
/**
|
28
|
+
* Localize content using the Lingo.dev API
|
29
|
+
* @param payload - The content to be localized
|
30
|
+
* @param params - Localization parameters including source/target locales and fast mode option
|
31
|
+
* @param reference - Optional reference translations to maintain consistency
|
32
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
33
|
+
* @returns Localized content
|
34
|
+
* @internal
|
35
|
+
*/
|
36
|
+
async _localizeRaw(payload, params, reference, progressCallback) {
|
37
|
+
const finalPayload = payloadSchema.parse(payload);
|
38
|
+
const finalParams = localizationParamsSchema.parse(params);
|
39
|
+
const chunkedPayload = this.extractPayloadChunks(finalPayload);
|
40
|
+
const processedPayloadChunks = [];
|
41
|
+
const workflowId = _cuid2.createId.call(void 0, );
|
42
|
+
for (let i = 0; i < chunkedPayload.length; i++) {
|
43
|
+
const chunk = chunkedPayload[i];
|
44
|
+
const percentageCompleted = Math.round((i + 1) / chunkedPayload.length * 100);
|
45
|
+
const processedPayloadChunk = await this.localizeChunk(
|
46
|
+
finalParams.sourceLocale,
|
47
|
+
finalParams.targetLocale,
|
48
|
+
{ data: chunk, reference },
|
49
|
+
workflowId,
|
50
|
+
params.fast || false
|
51
|
+
);
|
52
|
+
if (progressCallback) {
|
53
|
+
progressCallback(percentageCompleted, chunk, processedPayloadChunk);
|
54
|
+
}
|
55
|
+
processedPayloadChunks.push(processedPayloadChunk);
|
56
|
+
}
|
57
|
+
return Object.assign({}, ...processedPayloadChunks);
|
58
|
+
}
|
59
|
+
/**
|
60
|
+
* Localize a single chunk of content
|
61
|
+
* @param sourceLocale - Source locale
|
62
|
+
* @param targetLocale - Target locale
|
63
|
+
* @param payload - Payload containing the chunk to be localized
|
64
|
+
* @returns Localized chunk
|
65
|
+
*/
|
66
|
+
async localizeChunk(sourceLocale, targetLocale, payload, workflowId, fast) {
|
67
|
+
const res = await fetch(`${this.config.apiUrl}/i18n`, {
|
68
|
+
method: "POST",
|
69
|
+
headers: {
|
70
|
+
"Content-Type": "application/json",
|
71
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
72
|
+
},
|
73
|
+
body: JSON.stringify(
|
74
|
+
{
|
75
|
+
params: { workflowId, fast },
|
76
|
+
locale: {
|
77
|
+
source: sourceLocale,
|
78
|
+
target: targetLocale
|
79
|
+
},
|
80
|
+
data: payload.data,
|
81
|
+
reference: payload.reference
|
82
|
+
},
|
83
|
+
null,
|
84
|
+
2
|
85
|
+
)
|
86
|
+
});
|
87
|
+
if (!res.ok) {
|
88
|
+
if (res.status === 400) {
|
89
|
+
throw new Error(`Invalid request: ${res.statusText}`);
|
90
|
+
} else {
|
91
|
+
const errorText = await res.text();
|
92
|
+
throw new Error(errorText);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
const jsonResponse = await res.json();
|
96
|
+
return jsonResponse.data || {};
|
97
|
+
}
|
98
|
+
/**
|
99
|
+
* Extract payload chunks based on the ideal chunk size
|
100
|
+
* @param payload - The payload to be chunked
|
101
|
+
* @returns An array of payload chunks
|
102
|
+
*/
|
103
|
+
extractPayloadChunks(payload) {
|
104
|
+
const result = [];
|
105
|
+
let currentChunk = {};
|
106
|
+
let currentChunkItemCount = 0;
|
107
|
+
const payloadEntries = Object.entries(payload);
|
108
|
+
for (let i = 0; i < payloadEntries.length; i++) {
|
109
|
+
const [key, value] = payloadEntries[i];
|
110
|
+
currentChunk[key] = value;
|
111
|
+
currentChunkItemCount++;
|
112
|
+
const currentChunkSize = this.countWordsInRecord(currentChunk);
|
113
|
+
if (currentChunkSize > this.config.idealBatchItemSize || currentChunkItemCount >= this.config.batchSize || i === payloadEntries.length - 1) {
|
114
|
+
result.push(currentChunk);
|
115
|
+
currentChunk = {};
|
116
|
+
currentChunkItemCount = 0;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
return result;
|
120
|
+
}
|
121
|
+
/**
|
122
|
+
* Count words in a record or array
|
123
|
+
* @param payload - The payload to count words in
|
124
|
+
* @returns The total number of words
|
125
|
+
*/
|
126
|
+
countWordsInRecord(payload) {
|
127
|
+
if (Array.isArray(payload)) {
|
128
|
+
return payload.reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
|
129
|
+
} else if (typeof payload === "object" && payload !== null) {
|
130
|
+
return Object.values(payload).reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
|
131
|
+
} else if (typeof payload === "string") {
|
132
|
+
return payload.trim().split(/\s+/).filter(Boolean).length;
|
133
|
+
} else {
|
134
|
+
return 0;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
/**
|
138
|
+
* Localize a typical JavaScript object
|
139
|
+
* @param obj - The object to be localized (strings will be extracted and translated)
|
140
|
+
* @param params - Localization parameters:
|
141
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
142
|
+
* - targetLocale: The target language code (e.g., 'es')
|
143
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
144
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
145
|
+
* @returns A new object with the same structure but localized string values
|
146
|
+
*/
|
147
|
+
async localizeObject(obj, params, progressCallback) {
|
148
|
+
return this._localizeRaw(obj, params, void 0, progressCallback);
|
149
|
+
}
|
150
|
+
/**
|
151
|
+
* Localize a single text string
|
152
|
+
* @param text - The text string to be localized
|
153
|
+
* @param params - Localization parameters:
|
154
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
155
|
+
* - targetLocale: The target language code (e.g., 'es')
|
156
|
+
* - fast: Optional boolean to enable fast mode (faster for bigger batches)
|
157
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
158
|
+
* @returns The localized text string
|
159
|
+
*/
|
160
|
+
async localizeText(text, params, progressCallback) {
|
161
|
+
const response = await this._localizeRaw({ text }, params, void 0, progressCallback);
|
162
|
+
return response.text || "";
|
163
|
+
}
|
164
|
+
/**
|
165
|
+
* Localize a text string to multiple target locales
|
166
|
+
* @param text - The text string to be localized
|
167
|
+
* @param params - Localization parameters:
|
168
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
169
|
+
* - targetLocales: An array of target language codes (e.g., ['es', 'fr'])
|
170
|
+
* - fast: Optional boolean to enable fast mode (for bigger batches)
|
171
|
+
* @returns An array of localized text strings
|
172
|
+
*/
|
173
|
+
async batchLocalizeText(text, params) {
|
174
|
+
const responses = await Promise.all(
|
175
|
+
params.targetLocales.map(
|
176
|
+
(targetLocale) => this.localizeText(text, {
|
177
|
+
sourceLocale: params.sourceLocale,
|
178
|
+
targetLocale,
|
179
|
+
fast: params.fast
|
180
|
+
})
|
181
|
+
)
|
182
|
+
);
|
183
|
+
return responses;
|
184
|
+
}
|
185
|
+
/**
|
186
|
+
* Localize a chat sequence while preserving speaker names
|
187
|
+
* @param chat - Array of chat messages, each with 'name' and 'text' properties
|
188
|
+
* @param params - Localization parameters:
|
189
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
190
|
+
* - targetLocale: The target language code (e.g., 'es')
|
191
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
192
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
193
|
+
* @returns Array of localized chat messages with preserved structure
|
194
|
+
*/
|
195
|
+
async localizeChat(chat, params, progressCallback) {
|
196
|
+
const localized = await this._localizeRaw({ chat }, params, void 0, progressCallback);
|
197
|
+
return Object.entries(localized).map(([key, value]) => ({
|
198
|
+
name: chat[parseInt(key.split("_")[1])].name,
|
199
|
+
text: value
|
200
|
+
}));
|
201
|
+
}
|
202
|
+
/**
|
203
|
+
* Localize an HTML document while preserving structure and formatting
|
204
|
+
* Handles both text content and localizable attributes (alt, title, placeholder, meta content)
|
205
|
+
* @param html - The HTML document string to be localized
|
206
|
+
* @param params - Localization parameters:
|
207
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
208
|
+
* - targetLocale: The target language code (e.g., 'es')
|
209
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
210
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
211
|
+
* @returns The localized HTML document as a string, with updated lang attribute
|
212
|
+
*/
|
213
|
+
async localizeHtml(html, params, progressCallback) {
|
214
|
+
const jsdomPackage = await Promise.resolve().then(() => _interopRequireWildcard(require("jsdom")));
|
215
|
+
const { JSDOM } = jsdomPackage;
|
216
|
+
const dom = new JSDOM(html);
|
217
|
+
const document = dom.window.document;
|
218
|
+
const LOCALIZABLE_ATTRIBUTES = {
|
219
|
+
meta: ["content"],
|
220
|
+
img: ["alt"],
|
221
|
+
input: ["placeholder"],
|
222
|
+
a: ["title"]
|
223
|
+
};
|
224
|
+
const UNLOCALIZABLE_TAGS = ["script", "style"];
|
225
|
+
const extractedContent = {};
|
226
|
+
const getPath = (node, attribute) => {
|
227
|
+
const indices = [];
|
228
|
+
let current = node;
|
229
|
+
let rootParent = "";
|
230
|
+
while (current) {
|
231
|
+
const parent = current.parentElement;
|
232
|
+
if (!parent) break;
|
233
|
+
if (parent === document.documentElement) {
|
234
|
+
rootParent = current.nodeName.toLowerCase();
|
235
|
+
break;
|
236
|
+
}
|
237
|
+
const siblings = Array.from(parent.childNodes).filter(
|
238
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _ => _.textContent, 'optionalAccess', _2 => _2.trim, 'call', _3 => _3()])
|
239
|
+
);
|
240
|
+
const index = siblings.indexOf(current);
|
241
|
+
if (index !== -1) {
|
242
|
+
indices.unshift(index);
|
243
|
+
}
|
244
|
+
current = parent;
|
245
|
+
}
|
246
|
+
const basePath = rootParent ? `${rootParent}/${indices.join("/")}` : indices.join("/");
|
247
|
+
return attribute ? `${basePath}#${attribute}` : basePath;
|
248
|
+
};
|
249
|
+
const processNode = (node) => {
|
250
|
+
let parent = node.parentElement;
|
251
|
+
while (parent) {
|
252
|
+
if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
parent = parent.parentElement;
|
256
|
+
}
|
257
|
+
if (node.nodeType === 3) {
|
258
|
+
const text = _optionalChain([node, 'access', _4 => _4.textContent, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]) || "";
|
259
|
+
if (text) {
|
260
|
+
extractedContent[getPath(node)] = text;
|
261
|
+
}
|
262
|
+
} else if (node.nodeType === 1) {
|
263
|
+
const element = node;
|
264
|
+
const tagName = element.tagName.toLowerCase();
|
265
|
+
const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
|
266
|
+
attributes.forEach((attr) => {
|
267
|
+
const value = element.getAttribute(attr);
|
268
|
+
if (value) {
|
269
|
+
extractedContent[getPath(element, attr)] = value;
|
270
|
+
}
|
271
|
+
});
|
272
|
+
Array.from(element.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _7 => _7.textContent, 'optionalAccess', _8 => _8.trim, 'call', _9 => _9()])).forEach(processNode);
|
273
|
+
}
|
274
|
+
};
|
275
|
+
Array.from(document.head.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _10 => _10.textContent, 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()])).forEach(processNode);
|
276
|
+
Array.from(document.body.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _13 => _13.textContent, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()])).forEach(processNode);
|
277
|
+
const localizedContent = await this._localizeRaw(extractedContent, params, void 0, progressCallback);
|
278
|
+
document.documentElement.setAttribute("lang", params.targetLocale);
|
279
|
+
Object.entries(localizedContent).forEach(([path, value]) => {
|
280
|
+
const [nodePath, attribute] = path.split("#");
|
281
|
+
const [rootTag, ...indices] = nodePath.split("/");
|
282
|
+
let parent = rootTag === "head" ? document.head : document.body;
|
283
|
+
let current = parent;
|
284
|
+
for (const index of indices) {
|
285
|
+
const siblings = Array.from(parent.childNodes).filter(
|
286
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _16 => _16.textContent, 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()])
|
287
|
+
);
|
288
|
+
current = siblings[parseInt(index)] || null;
|
289
|
+
if (_optionalChain([current, 'optionalAccess', _19 => _19.nodeType]) === 1) {
|
290
|
+
parent = current;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
if (current) {
|
294
|
+
if (attribute) {
|
295
|
+
current.setAttribute(attribute, value);
|
296
|
+
} else {
|
297
|
+
current.textContent = value;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
});
|
301
|
+
return dom.serialize();
|
302
|
+
}
|
303
|
+
/**
|
304
|
+
* Detect the language of a given text
|
305
|
+
* @param text - The text to analyze
|
306
|
+
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
307
|
+
*/
|
308
|
+
async recognizeLocale(text) {
|
309
|
+
const response = await fetch(`${this.config.apiUrl}/recognize`, {
|
310
|
+
method: "POST",
|
311
|
+
headers: {
|
312
|
+
"Content-Type": "application/json",
|
313
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
314
|
+
},
|
315
|
+
body: JSON.stringify({ text })
|
316
|
+
});
|
317
|
+
if (!response.ok) {
|
318
|
+
throw new Error(`Error recognizing locale: ${response.statusText}`);
|
319
|
+
}
|
320
|
+
const jsonResponse = await response.json();
|
321
|
+
return jsonResponse.locale;
|
322
|
+
}
|
323
|
+
};
|
324
|
+
|
325
|
+
|
326
|
+
exports.ReplexicaEngine = ReplexicaEngine;
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import Z from 'zod';
|
2
|
+
import { LocaleCode } from '@lingo.dev/_spec';
|
3
|
+
|
4
|
+
declare const engineParamsSchema: Z.ZodObject<{
|
5
|
+
apiKey: Z.ZodString;
|
6
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
7
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
8
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
9
|
+
}, "passthrough", Z.ZodTypeAny, Z.objectOutputType<{
|
10
|
+
apiKey: Z.ZodString;
|
11
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
12
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
13
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
14
|
+
}, Z.ZodTypeAny, "passthrough">, Z.objectInputType<{
|
15
|
+
apiKey: Z.ZodString;
|
16
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
17
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
18
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
19
|
+
}, Z.ZodTypeAny, "passthrough">>;
|
20
|
+
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
21
|
+
declare const localizationParamsSchema: Z.ZodObject<{
|
22
|
+
sourceLocale: Z.ZodEffects<Z.ZodString, string, string>;
|
23
|
+
targetLocale: Z.ZodEffects<Z.ZodString, string, string>;
|
24
|
+
fast: Z.ZodOptional<Z.ZodBoolean>;
|
25
|
+
}, "strip", Z.ZodTypeAny, {
|
26
|
+
sourceLocale: string;
|
27
|
+
targetLocale: string;
|
28
|
+
fast?: boolean | undefined;
|
29
|
+
}, {
|
30
|
+
sourceLocale: string;
|
31
|
+
targetLocale: string;
|
32
|
+
fast?: boolean | undefined;
|
33
|
+
}>;
|
34
|
+
declare const referenceSchema: Z.ZodRecord<Z.ZodEffects<Z.ZodString, string, string>, Z.ZodRecord<Z.ZodString, Z.ZodAny>>;
|
35
|
+
/**
|
36
|
+
* ReplexicaEngine class for interacting with the Lingo.dev API
|
37
|
+
* A powerful localization engine that supports various content types including
|
38
|
+
* plain text, objects, chat sequences, and HTML documents.
|
39
|
+
*/
|
40
|
+
declare class ReplexicaEngine {
|
41
|
+
private config;
|
42
|
+
/**
|
43
|
+
* Create a new ReplexicaEngine instance
|
44
|
+
* @param config - Configuration options for the Engine
|
45
|
+
*/
|
46
|
+
constructor(config: Partial<Z.infer<typeof engineParamsSchema>>);
|
47
|
+
/**
|
48
|
+
* Localize content using the Lingo.dev API
|
49
|
+
* @param payload - The content to be localized
|
50
|
+
* @param params - Localization parameters including source/target locales and fast mode option
|
51
|
+
* @param reference - Optional reference translations to maintain consistency
|
52
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
53
|
+
* @returns Localized content
|
54
|
+
* @internal
|
55
|
+
*/
|
56
|
+
_localizeRaw(payload: Z.infer<typeof payloadSchema>, params: Z.infer<typeof localizationParamsSchema>, reference?: Z.infer<typeof referenceSchema>, progressCallback?: (progress: number, sourceChunk: Record<string, string>, processedChunk: Record<string, string>) => void): Promise<Record<string, string>>;
|
57
|
+
/**
|
58
|
+
* Localize a single chunk of content
|
59
|
+
* @param sourceLocale - Source locale
|
60
|
+
* @param targetLocale - Target locale
|
61
|
+
* @param payload - Payload containing the chunk to be localized
|
62
|
+
* @returns Localized chunk
|
63
|
+
*/
|
64
|
+
private localizeChunk;
|
65
|
+
/**
|
66
|
+
* Extract payload chunks based on the ideal chunk size
|
67
|
+
* @param payload - The payload to be chunked
|
68
|
+
* @returns An array of payload chunks
|
69
|
+
*/
|
70
|
+
private extractPayloadChunks;
|
71
|
+
/**
|
72
|
+
* Count words in a record or array
|
73
|
+
* @param payload - The payload to count words in
|
74
|
+
* @returns The total number of words
|
75
|
+
*/
|
76
|
+
private countWordsInRecord;
|
77
|
+
/**
|
78
|
+
* Localize a typical JavaScript object
|
79
|
+
* @param obj - The object to be localized (strings will be extracted and translated)
|
80
|
+
* @param params - Localization parameters:
|
81
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
82
|
+
* - targetLocale: The target language code (e.g., 'es')
|
83
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
84
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
85
|
+
* @returns A new object with the same structure but localized string values
|
86
|
+
*/
|
87
|
+
localizeObject(obj: Record<string, any>, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number, sourceChunk: Record<string, string>, processedChunk: Record<string, string>) => void): Promise<Record<string, any>>;
|
88
|
+
/**
|
89
|
+
* Localize a single text string
|
90
|
+
* @param text - The text string to be localized
|
91
|
+
* @param params - Localization parameters:
|
92
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
93
|
+
* - targetLocale: The target language code (e.g., 'es')
|
94
|
+
* - fast: Optional boolean to enable fast mode (faster for bigger batches)
|
95
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
96
|
+
* @returns The localized text string
|
97
|
+
*/
|
98
|
+
localizeText(text: string, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<string>;
|
99
|
+
/**
|
100
|
+
* Localize a text string to multiple target locales
|
101
|
+
* @param text - The text string to be localized
|
102
|
+
* @param params - Localization parameters:
|
103
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
104
|
+
* - targetLocales: An array of target language codes (e.g., ['es', 'fr'])
|
105
|
+
* - fast: Optional boolean to enable fast mode (for bigger batches)
|
106
|
+
* @returns An array of localized text strings
|
107
|
+
*/
|
108
|
+
batchLocalizeText(text: string, params: {
|
109
|
+
sourceLocale: LocaleCode;
|
110
|
+
targetLocales: LocaleCode[];
|
111
|
+
fast?: boolean;
|
112
|
+
}): Promise<string[]>;
|
113
|
+
/**
|
114
|
+
* Localize a chat sequence while preserving speaker names
|
115
|
+
* @param chat - Array of chat messages, each with 'name' and 'text' properties
|
116
|
+
* @param params - Localization parameters:
|
117
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
118
|
+
* - targetLocale: The target language code (e.g., 'es')
|
119
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
120
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
121
|
+
* @returns Array of localized chat messages with preserved structure
|
122
|
+
*/
|
123
|
+
localizeChat(chat: Array<{
|
124
|
+
name: string;
|
125
|
+
text: string;
|
126
|
+
}>, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<Array<{
|
127
|
+
name: string;
|
128
|
+
text: string;
|
129
|
+
}>>;
|
130
|
+
/**
|
131
|
+
* Localize an HTML document while preserving structure and formatting
|
132
|
+
* Handles both text content and localizable attributes (alt, title, placeholder, meta content)
|
133
|
+
* @param html - The HTML document string to be localized
|
134
|
+
* @param params - Localization parameters:
|
135
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
136
|
+
* - targetLocale: The target language code (e.g., 'es')
|
137
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
138
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
139
|
+
* @returns The localized HTML document as a string, with updated lang attribute
|
140
|
+
*/
|
141
|
+
localizeHtml(html: string, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<string>;
|
142
|
+
/**
|
143
|
+
* Detect the language of a given text
|
144
|
+
* @param text - The text to analyze
|
145
|
+
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
146
|
+
*/
|
147
|
+
recognizeLocale(text: string): Promise<LocaleCode>;
|
148
|
+
}
|
149
|
+
|
150
|
+
export { ReplexicaEngine };
|
package/build/index.d.ts
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
import Z from 'zod';
|
2
|
+
import { LocaleCode } from '@lingo.dev/_spec';
|
3
|
+
|
4
|
+
declare const engineParamsSchema: Z.ZodObject<{
|
5
|
+
apiKey: Z.ZodString;
|
6
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
7
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
8
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
9
|
+
}, "passthrough", Z.ZodTypeAny, Z.objectOutputType<{
|
10
|
+
apiKey: Z.ZodString;
|
11
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
12
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
13
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
14
|
+
}, Z.ZodTypeAny, "passthrough">, Z.objectInputType<{
|
15
|
+
apiKey: Z.ZodString;
|
16
|
+
apiUrl: Z.ZodDefault<Z.ZodString>;
|
17
|
+
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
18
|
+
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
19
|
+
}, Z.ZodTypeAny, "passthrough">>;
|
20
|
+
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
21
|
+
declare const localizationParamsSchema: Z.ZodObject<{
|
22
|
+
sourceLocale: Z.ZodEffects<Z.ZodString, string, string>;
|
23
|
+
targetLocale: Z.ZodEffects<Z.ZodString, string, string>;
|
24
|
+
fast: Z.ZodOptional<Z.ZodBoolean>;
|
25
|
+
}, "strip", Z.ZodTypeAny, {
|
26
|
+
sourceLocale: string;
|
27
|
+
targetLocale: string;
|
28
|
+
fast?: boolean | undefined;
|
29
|
+
}, {
|
30
|
+
sourceLocale: string;
|
31
|
+
targetLocale: string;
|
32
|
+
fast?: boolean | undefined;
|
33
|
+
}>;
|
34
|
+
declare const referenceSchema: Z.ZodRecord<Z.ZodEffects<Z.ZodString, string, string>, Z.ZodRecord<Z.ZodString, Z.ZodAny>>;
|
35
|
+
/**
|
36
|
+
* ReplexicaEngine class for interacting with the Lingo.dev API
|
37
|
+
* A powerful localization engine that supports various content types including
|
38
|
+
* plain text, objects, chat sequences, and HTML documents.
|
39
|
+
*/
|
40
|
+
declare class ReplexicaEngine {
|
41
|
+
private config;
|
42
|
+
/**
|
43
|
+
* Create a new ReplexicaEngine instance
|
44
|
+
* @param config - Configuration options for the Engine
|
45
|
+
*/
|
46
|
+
constructor(config: Partial<Z.infer<typeof engineParamsSchema>>);
|
47
|
+
/**
|
48
|
+
* Localize content using the Lingo.dev API
|
49
|
+
* @param payload - The content to be localized
|
50
|
+
* @param params - Localization parameters including source/target locales and fast mode option
|
51
|
+
* @param reference - Optional reference translations to maintain consistency
|
52
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
53
|
+
* @returns Localized content
|
54
|
+
* @internal
|
55
|
+
*/
|
56
|
+
_localizeRaw(payload: Z.infer<typeof payloadSchema>, params: Z.infer<typeof localizationParamsSchema>, reference?: Z.infer<typeof referenceSchema>, progressCallback?: (progress: number, sourceChunk: Record<string, string>, processedChunk: Record<string, string>) => void): Promise<Record<string, string>>;
|
57
|
+
/**
|
58
|
+
* Localize a single chunk of content
|
59
|
+
* @param sourceLocale - Source locale
|
60
|
+
* @param targetLocale - Target locale
|
61
|
+
* @param payload - Payload containing the chunk to be localized
|
62
|
+
* @returns Localized chunk
|
63
|
+
*/
|
64
|
+
private localizeChunk;
|
65
|
+
/**
|
66
|
+
* Extract payload chunks based on the ideal chunk size
|
67
|
+
* @param payload - The payload to be chunked
|
68
|
+
* @returns An array of payload chunks
|
69
|
+
*/
|
70
|
+
private extractPayloadChunks;
|
71
|
+
/**
|
72
|
+
* Count words in a record or array
|
73
|
+
* @param payload - The payload to count words in
|
74
|
+
* @returns The total number of words
|
75
|
+
*/
|
76
|
+
private countWordsInRecord;
|
77
|
+
/**
|
78
|
+
* Localize a typical JavaScript object
|
79
|
+
* @param obj - The object to be localized (strings will be extracted and translated)
|
80
|
+
* @param params - Localization parameters:
|
81
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
82
|
+
* - targetLocale: The target language code (e.g., 'es')
|
83
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
84
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
85
|
+
* @returns A new object with the same structure but localized string values
|
86
|
+
*/
|
87
|
+
localizeObject(obj: Record<string, any>, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number, sourceChunk: Record<string, string>, processedChunk: Record<string, string>) => void): Promise<Record<string, any>>;
|
88
|
+
/**
|
89
|
+
* Localize a single text string
|
90
|
+
* @param text - The text string to be localized
|
91
|
+
* @param params - Localization parameters:
|
92
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
93
|
+
* - targetLocale: The target language code (e.g., 'es')
|
94
|
+
* - fast: Optional boolean to enable fast mode (faster for bigger batches)
|
95
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
96
|
+
* @returns The localized text string
|
97
|
+
*/
|
98
|
+
localizeText(text: string, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<string>;
|
99
|
+
/**
|
100
|
+
* Localize a text string to multiple target locales
|
101
|
+
* @param text - The text string to be localized
|
102
|
+
* @param params - Localization parameters:
|
103
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
104
|
+
* - targetLocales: An array of target language codes (e.g., ['es', 'fr'])
|
105
|
+
* - fast: Optional boolean to enable fast mode (for bigger batches)
|
106
|
+
* @returns An array of localized text strings
|
107
|
+
*/
|
108
|
+
batchLocalizeText(text: string, params: {
|
109
|
+
sourceLocale: LocaleCode;
|
110
|
+
targetLocales: LocaleCode[];
|
111
|
+
fast?: boolean;
|
112
|
+
}): Promise<string[]>;
|
113
|
+
/**
|
114
|
+
* Localize a chat sequence while preserving speaker names
|
115
|
+
* @param chat - Array of chat messages, each with 'name' and 'text' properties
|
116
|
+
* @param params - Localization parameters:
|
117
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
118
|
+
* - targetLocale: The target language code (e.g., 'es')
|
119
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
120
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
121
|
+
* @returns Array of localized chat messages with preserved structure
|
122
|
+
*/
|
123
|
+
localizeChat(chat: Array<{
|
124
|
+
name: string;
|
125
|
+
text: string;
|
126
|
+
}>, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<Array<{
|
127
|
+
name: string;
|
128
|
+
text: string;
|
129
|
+
}>>;
|
130
|
+
/**
|
131
|
+
* Localize an HTML document while preserving structure and formatting
|
132
|
+
* Handles both text content and localizable attributes (alt, title, placeholder, meta content)
|
133
|
+
* @param html - The HTML document string to be localized
|
134
|
+
* @param params - Localization parameters:
|
135
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
136
|
+
* - targetLocale: The target language code (e.g., 'es')
|
137
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
138
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
139
|
+
* @returns The localized HTML document as a string, with updated lang attribute
|
140
|
+
*/
|
141
|
+
localizeHtml(html: string, params: Z.infer<typeof localizationParamsSchema>, progressCallback?: (progress: number) => void): Promise<string>;
|
142
|
+
/**
|
143
|
+
* Detect the language of a given text
|
144
|
+
* @param text - The text to analyze
|
145
|
+
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
146
|
+
*/
|
147
|
+
recognizeLocale(text: string): Promise<LocaleCode>;
|
148
|
+
}
|
149
|
+
|
150
|
+
export { ReplexicaEngine };
|
package/build/index.mjs
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
// src/index.ts
|
2
|
+
import Z from "zod";
|
3
|
+
import { localeCodeSchema } from "@lingo.dev/_spec";
|
4
|
+
import { createId } from "@paralleldrive/cuid2";
|
5
|
+
var engineParamsSchema = Z.object({
|
6
|
+
apiKey: Z.string(),
|
7
|
+
apiUrl: Z.string().url().default("https://engine.lingo.dev"),
|
8
|
+
batchSize: Z.number().int().gt(0).lte(250).default(25),
|
9
|
+
idealBatchItemSize: Z.number().int().gt(0).lte(2500).default(250)
|
10
|
+
}).passthrough();
|
11
|
+
var payloadSchema = Z.record(Z.string(), Z.any());
|
12
|
+
var localizationParamsSchema = Z.object({
|
13
|
+
sourceLocale: localeCodeSchema,
|
14
|
+
targetLocale: localeCodeSchema,
|
15
|
+
fast: Z.boolean().optional()
|
16
|
+
});
|
17
|
+
var referenceSchema = Z.record(localeCodeSchema, payloadSchema);
|
18
|
+
var ReplexicaEngine = class {
|
19
|
+
config;
|
20
|
+
/**
|
21
|
+
* Create a new ReplexicaEngine instance
|
22
|
+
* @param config - Configuration options for the Engine
|
23
|
+
*/
|
24
|
+
constructor(config) {
|
25
|
+
this.config = engineParamsSchema.parse(config);
|
26
|
+
}
|
27
|
+
/**
|
28
|
+
* Localize content using the Lingo.dev API
|
29
|
+
* @param payload - The content to be localized
|
30
|
+
* @param params - Localization parameters including source/target locales and fast mode option
|
31
|
+
* @param reference - Optional reference translations to maintain consistency
|
32
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
33
|
+
* @returns Localized content
|
34
|
+
* @internal
|
35
|
+
*/
|
36
|
+
async _localizeRaw(payload, params, reference, progressCallback) {
|
37
|
+
const finalPayload = payloadSchema.parse(payload);
|
38
|
+
const finalParams = localizationParamsSchema.parse(params);
|
39
|
+
const chunkedPayload = this.extractPayloadChunks(finalPayload);
|
40
|
+
const processedPayloadChunks = [];
|
41
|
+
const workflowId = createId();
|
42
|
+
for (let i = 0; i < chunkedPayload.length; i++) {
|
43
|
+
const chunk = chunkedPayload[i];
|
44
|
+
const percentageCompleted = Math.round((i + 1) / chunkedPayload.length * 100);
|
45
|
+
const processedPayloadChunk = await this.localizeChunk(
|
46
|
+
finalParams.sourceLocale,
|
47
|
+
finalParams.targetLocale,
|
48
|
+
{ data: chunk, reference },
|
49
|
+
workflowId,
|
50
|
+
params.fast || false
|
51
|
+
);
|
52
|
+
if (progressCallback) {
|
53
|
+
progressCallback(percentageCompleted, chunk, processedPayloadChunk);
|
54
|
+
}
|
55
|
+
processedPayloadChunks.push(processedPayloadChunk);
|
56
|
+
}
|
57
|
+
return Object.assign({}, ...processedPayloadChunks);
|
58
|
+
}
|
59
|
+
/**
|
60
|
+
* Localize a single chunk of content
|
61
|
+
* @param sourceLocale - Source locale
|
62
|
+
* @param targetLocale - Target locale
|
63
|
+
* @param payload - Payload containing the chunk to be localized
|
64
|
+
* @returns Localized chunk
|
65
|
+
*/
|
66
|
+
async localizeChunk(sourceLocale, targetLocale, payload, workflowId, fast) {
|
67
|
+
const res = await fetch(`${this.config.apiUrl}/i18n`, {
|
68
|
+
method: "POST",
|
69
|
+
headers: {
|
70
|
+
"Content-Type": "application/json",
|
71
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
72
|
+
},
|
73
|
+
body: JSON.stringify(
|
74
|
+
{
|
75
|
+
params: { workflowId, fast },
|
76
|
+
locale: {
|
77
|
+
source: sourceLocale,
|
78
|
+
target: targetLocale
|
79
|
+
},
|
80
|
+
data: payload.data,
|
81
|
+
reference: payload.reference
|
82
|
+
},
|
83
|
+
null,
|
84
|
+
2
|
85
|
+
)
|
86
|
+
});
|
87
|
+
if (!res.ok) {
|
88
|
+
if (res.status === 400) {
|
89
|
+
throw new Error(`Invalid request: ${res.statusText}`);
|
90
|
+
} else {
|
91
|
+
const errorText = await res.text();
|
92
|
+
throw new Error(errorText);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
const jsonResponse = await res.json();
|
96
|
+
return jsonResponse.data || {};
|
97
|
+
}
|
98
|
+
/**
|
99
|
+
* Extract payload chunks based on the ideal chunk size
|
100
|
+
* @param payload - The payload to be chunked
|
101
|
+
* @returns An array of payload chunks
|
102
|
+
*/
|
103
|
+
extractPayloadChunks(payload) {
|
104
|
+
const result = [];
|
105
|
+
let currentChunk = {};
|
106
|
+
let currentChunkItemCount = 0;
|
107
|
+
const payloadEntries = Object.entries(payload);
|
108
|
+
for (let i = 0; i < payloadEntries.length; i++) {
|
109
|
+
const [key, value] = payloadEntries[i];
|
110
|
+
currentChunk[key] = value;
|
111
|
+
currentChunkItemCount++;
|
112
|
+
const currentChunkSize = this.countWordsInRecord(currentChunk);
|
113
|
+
if (currentChunkSize > this.config.idealBatchItemSize || currentChunkItemCount >= this.config.batchSize || i === payloadEntries.length - 1) {
|
114
|
+
result.push(currentChunk);
|
115
|
+
currentChunk = {};
|
116
|
+
currentChunkItemCount = 0;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
return result;
|
120
|
+
}
|
121
|
+
/**
|
122
|
+
* Count words in a record or array
|
123
|
+
* @param payload - The payload to count words in
|
124
|
+
* @returns The total number of words
|
125
|
+
*/
|
126
|
+
countWordsInRecord(payload) {
|
127
|
+
if (Array.isArray(payload)) {
|
128
|
+
return payload.reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
|
129
|
+
} else if (typeof payload === "object" && payload !== null) {
|
130
|
+
return Object.values(payload).reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
|
131
|
+
} else if (typeof payload === "string") {
|
132
|
+
return payload.trim().split(/\s+/).filter(Boolean).length;
|
133
|
+
} else {
|
134
|
+
return 0;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
/**
|
138
|
+
* Localize a typical JavaScript object
|
139
|
+
* @param obj - The object to be localized (strings will be extracted and translated)
|
140
|
+
* @param params - Localization parameters:
|
141
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
142
|
+
* - targetLocale: The target language code (e.g., 'es')
|
143
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
144
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
145
|
+
* @returns A new object with the same structure but localized string values
|
146
|
+
*/
|
147
|
+
async localizeObject(obj, params, progressCallback) {
|
148
|
+
return this._localizeRaw(obj, params, void 0, progressCallback);
|
149
|
+
}
|
150
|
+
/**
|
151
|
+
* Localize a single text string
|
152
|
+
* @param text - The text string to be localized
|
153
|
+
* @param params - Localization parameters:
|
154
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
155
|
+
* - targetLocale: The target language code (e.g., 'es')
|
156
|
+
* - fast: Optional boolean to enable fast mode (faster for bigger batches)
|
157
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
158
|
+
* @returns The localized text string
|
159
|
+
*/
|
160
|
+
async localizeText(text, params, progressCallback) {
|
161
|
+
const response = await this._localizeRaw({ text }, params, void 0, progressCallback);
|
162
|
+
return response.text || "";
|
163
|
+
}
|
164
|
+
/**
|
165
|
+
* Localize a text string to multiple target locales
|
166
|
+
* @param text - The text string to be localized
|
167
|
+
* @param params - Localization parameters:
|
168
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
169
|
+
* - targetLocales: An array of target language codes (e.g., ['es', 'fr'])
|
170
|
+
* - fast: Optional boolean to enable fast mode (for bigger batches)
|
171
|
+
* @returns An array of localized text strings
|
172
|
+
*/
|
173
|
+
async batchLocalizeText(text, params) {
|
174
|
+
const responses = await Promise.all(
|
175
|
+
params.targetLocales.map(
|
176
|
+
(targetLocale) => this.localizeText(text, {
|
177
|
+
sourceLocale: params.sourceLocale,
|
178
|
+
targetLocale,
|
179
|
+
fast: params.fast
|
180
|
+
})
|
181
|
+
)
|
182
|
+
);
|
183
|
+
return responses;
|
184
|
+
}
|
185
|
+
/**
|
186
|
+
* Localize a chat sequence while preserving speaker names
|
187
|
+
* @param chat - Array of chat messages, each with 'name' and 'text' properties
|
188
|
+
* @param params - Localization parameters:
|
189
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
190
|
+
* - targetLocale: The target language code (e.g., 'es')
|
191
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
192
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
193
|
+
* @returns Array of localized chat messages with preserved structure
|
194
|
+
*/
|
195
|
+
async localizeChat(chat, params, progressCallback) {
|
196
|
+
const localized = await this._localizeRaw({ chat }, params, void 0, progressCallback);
|
197
|
+
return Object.entries(localized).map(([key, value]) => ({
|
198
|
+
name: chat[parseInt(key.split("_")[1])].name,
|
199
|
+
text: value
|
200
|
+
}));
|
201
|
+
}
|
202
|
+
/**
|
203
|
+
* Localize an HTML document while preserving structure and formatting
|
204
|
+
* Handles both text content and localizable attributes (alt, title, placeholder, meta content)
|
205
|
+
* @param html - The HTML document string to be localized
|
206
|
+
* @param params - Localization parameters:
|
207
|
+
* - sourceLocale: The source language code (e.g., 'en')
|
208
|
+
* - targetLocale: The target language code (e.g., 'es')
|
209
|
+
* - fast: Optional boolean to enable fast mode (faster but potentially lower quality)
|
210
|
+
* @param progressCallback - Optional callback function to report progress (0-100)
|
211
|
+
* @returns The localized HTML document as a string, with updated lang attribute
|
212
|
+
*/
|
213
|
+
async localizeHtml(html, params, progressCallback) {
|
214
|
+
const jsdomPackage = await import("jsdom");
|
215
|
+
const { JSDOM } = jsdomPackage;
|
216
|
+
const dom = new JSDOM(html);
|
217
|
+
const document = dom.window.document;
|
218
|
+
const LOCALIZABLE_ATTRIBUTES = {
|
219
|
+
meta: ["content"],
|
220
|
+
img: ["alt"],
|
221
|
+
input: ["placeholder"],
|
222
|
+
a: ["title"]
|
223
|
+
};
|
224
|
+
const UNLOCALIZABLE_TAGS = ["script", "style"];
|
225
|
+
const extractedContent = {};
|
226
|
+
const getPath = (node, attribute) => {
|
227
|
+
const indices = [];
|
228
|
+
let current = node;
|
229
|
+
let rootParent = "";
|
230
|
+
while (current) {
|
231
|
+
const parent = current.parentElement;
|
232
|
+
if (!parent) break;
|
233
|
+
if (parent === document.documentElement) {
|
234
|
+
rootParent = current.nodeName.toLowerCase();
|
235
|
+
break;
|
236
|
+
}
|
237
|
+
const siblings = Array.from(parent.childNodes).filter(
|
238
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
239
|
+
);
|
240
|
+
const index = siblings.indexOf(current);
|
241
|
+
if (index !== -1) {
|
242
|
+
indices.unshift(index);
|
243
|
+
}
|
244
|
+
current = parent;
|
245
|
+
}
|
246
|
+
const basePath = rootParent ? `${rootParent}/${indices.join("/")}` : indices.join("/");
|
247
|
+
return attribute ? `${basePath}#${attribute}` : basePath;
|
248
|
+
};
|
249
|
+
const processNode = (node) => {
|
250
|
+
let parent = node.parentElement;
|
251
|
+
while (parent) {
|
252
|
+
if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
parent = parent.parentElement;
|
256
|
+
}
|
257
|
+
if (node.nodeType === 3) {
|
258
|
+
const text = node.textContent?.trim() || "";
|
259
|
+
if (text) {
|
260
|
+
extractedContent[getPath(node)] = text;
|
261
|
+
}
|
262
|
+
} else if (node.nodeType === 1) {
|
263
|
+
const element = node;
|
264
|
+
const tagName = element.tagName.toLowerCase();
|
265
|
+
const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
|
266
|
+
attributes.forEach((attr) => {
|
267
|
+
const value = element.getAttribute(attr);
|
268
|
+
if (value) {
|
269
|
+
extractedContent[getPath(element, attr)] = value;
|
270
|
+
}
|
271
|
+
});
|
272
|
+
Array.from(element.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
|
273
|
+
}
|
274
|
+
};
|
275
|
+
Array.from(document.head.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
|
276
|
+
Array.from(document.body.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
|
277
|
+
const localizedContent = await this._localizeRaw(extractedContent, params, void 0, progressCallback);
|
278
|
+
document.documentElement.setAttribute("lang", params.targetLocale);
|
279
|
+
Object.entries(localizedContent).forEach(([path, value]) => {
|
280
|
+
const [nodePath, attribute] = path.split("#");
|
281
|
+
const [rootTag, ...indices] = nodePath.split("/");
|
282
|
+
let parent = rootTag === "head" ? document.head : document.body;
|
283
|
+
let current = parent;
|
284
|
+
for (const index of indices) {
|
285
|
+
const siblings = Array.from(parent.childNodes).filter(
|
286
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
287
|
+
);
|
288
|
+
current = siblings[parseInt(index)] || null;
|
289
|
+
if (current?.nodeType === 1) {
|
290
|
+
parent = current;
|
291
|
+
}
|
292
|
+
}
|
293
|
+
if (current) {
|
294
|
+
if (attribute) {
|
295
|
+
current.setAttribute(attribute, value);
|
296
|
+
} else {
|
297
|
+
current.textContent = value;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
});
|
301
|
+
return dom.serialize();
|
302
|
+
}
|
303
|
+
/**
|
304
|
+
* Detect the language of a given text
|
305
|
+
* @param text - The text to analyze
|
306
|
+
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
307
|
+
*/
|
308
|
+
async recognizeLocale(text) {
|
309
|
+
const response = await fetch(`${this.config.apiUrl}/recognize`, {
|
310
|
+
method: "POST",
|
311
|
+
headers: {
|
312
|
+
"Content-Type": "application/json",
|
313
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
314
|
+
},
|
315
|
+
body: JSON.stringify({ text })
|
316
|
+
});
|
317
|
+
if (!response.ok) {
|
318
|
+
throw new Error(`Error recognizing locale: ${response.statusText}`);
|
319
|
+
}
|
320
|
+
const jsonResponse = await response.json();
|
321
|
+
return jsonResponse.locale;
|
322
|
+
}
|
323
|
+
};
|
324
|
+
export {
|
325
|
+
ReplexicaEngine
|
326
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "@lingo.dev/_sdk",
|
3
|
+
"version": "0.7.12",
|
4
|
+
"description": "Lingo.dev JS SDK",
|
5
|
+
"private": false,
|
6
|
+
"publishConfig": {
|
7
|
+
"access": "public"
|
8
|
+
},
|
9
|
+
"type": "module",
|
10
|
+
"sideEffects": false,
|
11
|
+
"types": "build/index.d.ts",
|
12
|
+
"module": "build/index.mjs",
|
13
|
+
"main": "build/index.cjs",
|
14
|
+
"files": [
|
15
|
+
"build"
|
16
|
+
],
|
17
|
+
"scripts": {
|
18
|
+
"dev": "tsup --watch",
|
19
|
+
"build": "tsc --noEmit && tsup",
|
20
|
+
"test": "vitest run"
|
21
|
+
},
|
22
|
+
"keywords": [],
|
23
|
+
"author": "",
|
24
|
+
"license": "Apache-2.0",
|
25
|
+
"dependencies": {
|
26
|
+
"@paralleldrive/cuid2": "^2.2.2",
|
27
|
+
"@lingo.dev/_spec": "workspace:*",
|
28
|
+
"jsdom": "^25.0.1",
|
29
|
+
"typescript": "^5.7.2",
|
30
|
+
"vitest": "^2.1.8",
|
31
|
+
"zod": "^3.24.1"
|
32
|
+
},
|
33
|
+
"devDependencies": {
|
34
|
+
"@types/jsdom": "^21.1.7",
|
35
|
+
"tsup": "^8.3.5"
|
36
|
+
}
|
37
|
+
}
|