@lingo.dev/_sdk 0.7.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|