@lingo.dev/_sdk 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +410 -134
- package/build/index.mjs +409 -133
- package/package.json +3 -2
package/build/index.cjs
CHANGED
|
@@ -2,6 +2,97 @@
|
|
|
2
2
|
var _zod = require('zod'); var _zod2 = _interopRequireDefault(_zod);
|
|
3
3
|
var __spec = require('@lingo.dev/_spec');
|
|
4
4
|
var _cuid2 = require('@paralleldrive/cuid2');
|
|
5
|
+
|
|
6
|
+
// src/utils/observability.ts
|
|
7
|
+
var _crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
// src/utils/tracking-events.ts
|
|
10
|
+
var TRACKING_EVENTS = {
|
|
11
|
+
LOCALIZE_START: "sdk.localize.start",
|
|
12
|
+
LOCALIZE_SUCCESS: "sdk.localize.success",
|
|
13
|
+
LOCALIZE_ERROR: "sdk.localize.error",
|
|
14
|
+
RECOGNIZE_START: "sdk.recognize.start",
|
|
15
|
+
RECOGNIZE_SUCCESS: "sdk.recognize.success",
|
|
16
|
+
RECOGNIZE_ERROR: "sdk.recognize.error"
|
|
17
|
+
};
|
|
18
|
+
var TRACKING_VERSION = "1.0";
|
|
19
|
+
var SDK_PACKAGE = "@lingo.dev/_sdk";
|
|
20
|
+
|
|
21
|
+
// src/utils/observability.ts
|
|
22
|
+
var POSTHOG_API_KEY = "phc_eR0iSoQufBxNY36k0f0T15UvHJdTfHlh8rJcxsfhfXk";
|
|
23
|
+
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
24
|
+
var identityCache = /* @__PURE__ */ new Map();
|
|
25
|
+
function trackEvent(apiKey, apiUrl, event, properties) {
|
|
26
|
+
if (process.env.DO_NOT_TRACK === "1") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
resolveIdentityAndCapture(apiKey, apiUrl, event, properties).catch(
|
|
30
|
+
(error) => {
|
|
31
|
+
if (process.env.DEBUG === "true") {
|
|
32
|
+
console.error("[Tracking] Error:", error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
async function resolveIdentityAndCapture(apiKey, apiUrl, event, properties) {
|
|
38
|
+
const identityInfo = await getDistinctId(apiKey, apiUrl);
|
|
39
|
+
if (process.env.DEBUG === "true") {
|
|
40
|
+
console.log(
|
|
41
|
+
`[Tracking] Event: ${event}, ID: ${identityInfo.distinct_id}, Source: ${identityInfo.distinct_id_source}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const { PostHog } = await Promise.resolve().then(() => _interopRequireWildcard(require("posthog-node")));
|
|
45
|
+
const posthog = new PostHog(POSTHOG_API_KEY, {
|
|
46
|
+
host: POSTHOG_HOST,
|
|
47
|
+
flushAt: 1,
|
|
48
|
+
flushInterval: 0
|
|
49
|
+
});
|
|
50
|
+
await posthog.capture({
|
|
51
|
+
distinctId: identityInfo.distinct_id,
|
|
52
|
+
event,
|
|
53
|
+
properties: {
|
|
54
|
+
...properties,
|
|
55
|
+
tracking_version: TRACKING_VERSION,
|
|
56
|
+
sdk_package: SDK_PACKAGE,
|
|
57
|
+
distinct_id_source: identityInfo.distinct_id_source
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
await posthog.shutdown();
|
|
61
|
+
}
|
|
62
|
+
async function getDistinctId(apiKey, apiUrl) {
|
|
63
|
+
const cached = identityCache.get(apiKey);
|
|
64
|
+
if (cached) return cached;
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`${apiUrl}/whoami`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${apiKey}`,
|
|
70
|
+
"Content-Type": "application/json"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (res.ok) {
|
|
74
|
+
const payload = await res.json();
|
|
75
|
+
if (_optionalChain([payload, 'optionalAccess', _ => _.email])) {
|
|
76
|
+
const identity2 = {
|
|
77
|
+
distinct_id: payload.email,
|
|
78
|
+
distinct_id_source: "email"
|
|
79
|
+
};
|
|
80
|
+
identityCache.set(apiKey, identity2);
|
|
81
|
+
return identity2;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
const hash = _crypto.createHash.call(void 0, "sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
87
|
+
const identity = {
|
|
88
|
+
distinct_id: `apikey-${hash}`,
|
|
89
|
+
distinct_id_source: "api_key_hash"
|
|
90
|
+
};
|
|
91
|
+
identityCache.set(apiKey, identity);
|
|
92
|
+
return identity;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/index.ts
|
|
5
96
|
var engineParamsSchema = _zod2.default.object({
|
|
6
97
|
apiKey: _zod2.default.string(),
|
|
7
98
|
apiUrl: _zod2.default.string().url().default("https://engine.lingo.dev"),
|
|
@@ -171,7 +262,43 @@ var LingoDotDevEngine = class {
|
|
|
171
262
|
* @returns A new object with the same structure but localized string values
|
|
172
263
|
*/
|
|
173
264
|
async localizeObject(obj, params, progressCallback, signal) {
|
|
174
|
-
|
|
265
|
+
const trackProps = {
|
|
266
|
+
method: "localizeObject",
|
|
267
|
+
sourceLocale: params.sourceLocale,
|
|
268
|
+
targetLocale: params.targetLocale
|
|
269
|
+
};
|
|
270
|
+
trackEvent(
|
|
271
|
+
this.config.apiKey,
|
|
272
|
+
this.config.apiUrl,
|
|
273
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
274
|
+
trackProps
|
|
275
|
+
);
|
|
276
|
+
try {
|
|
277
|
+
const result = await this._localizeRaw(
|
|
278
|
+
obj,
|
|
279
|
+
params,
|
|
280
|
+
progressCallback,
|
|
281
|
+
signal
|
|
282
|
+
);
|
|
283
|
+
trackEvent(
|
|
284
|
+
this.config.apiKey,
|
|
285
|
+
this.config.apiUrl,
|
|
286
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
287
|
+
trackProps
|
|
288
|
+
);
|
|
289
|
+
return result;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
trackEvent(
|
|
292
|
+
this.config.apiKey,
|
|
293
|
+
this.config.apiUrl,
|
|
294
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
295
|
+
{
|
|
296
|
+
...trackProps,
|
|
297
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
175
302
|
}
|
|
176
303
|
/**
|
|
177
304
|
* Localize a single text string
|
|
@@ -185,13 +312,44 @@ var LingoDotDevEngine = class {
|
|
|
185
312
|
* @returns The localized text string
|
|
186
313
|
*/
|
|
187
314
|
async localizeText(text, params, progressCallback, signal) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
params,
|
|
191
|
-
|
|
192
|
-
|
|
315
|
+
const trackProps = {
|
|
316
|
+
method: "localizeText",
|
|
317
|
+
sourceLocale: params.sourceLocale,
|
|
318
|
+
targetLocale: params.targetLocale
|
|
319
|
+
};
|
|
320
|
+
trackEvent(
|
|
321
|
+
this.config.apiKey,
|
|
322
|
+
this.config.apiUrl,
|
|
323
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
324
|
+
trackProps
|
|
193
325
|
);
|
|
194
|
-
|
|
326
|
+
try {
|
|
327
|
+
const response = await this._localizeRaw(
|
|
328
|
+
{ text },
|
|
329
|
+
params,
|
|
330
|
+
progressCallback,
|
|
331
|
+
signal
|
|
332
|
+
);
|
|
333
|
+
const result = response.text || "";
|
|
334
|
+
trackEvent(
|
|
335
|
+
this.config.apiKey,
|
|
336
|
+
this.config.apiUrl,
|
|
337
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
338
|
+
trackProps
|
|
339
|
+
);
|
|
340
|
+
return result;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
trackEvent(
|
|
343
|
+
this.config.apiKey,
|
|
344
|
+
this.config.apiUrl,
|
|
345
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
346
|
+
{
|
|
347
|
+
...trackProps,
|
|
348
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
195
353
|
}
|
|
196
354
|
/**
|
|
197
355
|
* Localize a text string to multiple target locales
|
|
@@ -230,15 +388,45 @@ var LingoDotDevEngine = class {
|
|
|
230
388
|
* @returns An array of localized strings in the same order
|
|
231
389
|
*/
|
|
232
390
|
async localizeStringArray(strings, params) {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
391
|
+
const trackProps = {
|
|
392
|
+
method: "localizeStringArray",
|
|
393
|
+
sourceLocale: params.sourceLocale,
|
|
394
|
+
targetLocale: params.targetLocale
|
|
395
|
+
};
|
|
396
|
+
trackEvent(
|
|
397
|
+
this.config.apiKey,
|
|
398
|
+
this.config.apiUrl,
|
|
399
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
400
|
+
trackProps
|
|
239
401
|
);
|
|
240
|
-
|
|
241
|
-
|
|
402
|
+
try {
|
|
403
|
+
const mapped = strings.reduce(
|
|
404
|
+
(acc, str, i) => {
|
|
405
|
+
acc[`item_${i}`] = str;
|
|
406
|
+
return acc;
|
|
407
|
+
},
|
|
408
|
+
{}
|
|
409
|
+
);
|
|
410
|
+
const result = await this._localizeRaw(mapped, params);
|
|
411
|
+
trackEvent(
|
|
412
|
+
this.config.apiKey,
|
|
413
|
+
this.config.apiUrl,
|
|
414
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
415
|
+
trackProps
|
|
416
|
+
);
|
|
417
|
+
return Object.values(result);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
trackEvent(
|
|
420
|
+
this.config.apiKey,
|
|
421
|
+
this.config.apiUrl,
|
|
422
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
423
|
+
{
|
|
424
|
+
...trackProps,
|
|
425
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
242
430
|
}
|
|
243
431
|
/**
|
|
244
432
|
* Localize a chat sequence while preserving speaker names
|
|
@@ -252,16 +440,47 @@ var LingoDotDevEngine = class {
|
|
|
252
440
|
* @returns Array of localized chat messages with preserved structure
|
|
253
441
|
*/
|
|
254
442
|
async localizeChat(chat, params, progressCallback, signal) {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
params,
|
|
258
|
-
|
|
259
|
-
|
|
443
|
+
const trackProps = {
|
|
444
|
+
method: "localizeChat",
|
|
445
|
+
sourceLocale: params.sourceLocale,
|
|
446
|
+
targetLocale: params.targetLocale
|
|
447
|
+
};
|
|
448
|
+
trackEvent(
|
|
449
|
+
this.config.apiKey,
|
|
450
|
+
this.config.apiUrl,
|
|
451
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
452
|
+
trackProps
|
|
260
453
|
);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
454
|
+
try {
|
|
455
|
+
const localized = await this._localizeRaw(
|
|
456
|
+
{ chat },
|
|
457
|
+
params,
|
|
458
|
+
progressCallback,
|
|
459
|
+
signal
|
|
460
|
+
);
|
|
461
|
+
const result = Object.entries(localized).map(([key, value]) => ({
|
|
462
|
+
name: chat[parseInt(key.split("_")[1])].name,
|
|
463
|
+
text: value
|
|
464
|
+
}));
|
|
465
|
+
trackEvent(
|
|
466
|
+
this.config.apiKey,
|
|
467
|
+
this.config.apiUrl,
|
|
468
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
469
|
+
trackProps
|
|
470
|
+
);
|
|
471
|
+
return result;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
trackEvent(
|
|
474
|
+
this.config.apiKey,
|
|
475
|
+
this.config.apiUrl,
|
|
476
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
477
|
+
{
|
|
478
|
+
...trackProps,
|
|
479
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
265
484
|
}
|
|
266
485
|
/**
|
|
267
486
|
* Localize an HTML document while preserving structure and formatting
|
|
@@ -276,105 +495,136 @@ var LingoDotDevEngine = class {
|
|
|
276
495
|
* @returns The localized HTML document as a string, with updated lang attribute
|
|
277
496
|
*/
|
|
278
497
|
async localizeHtml(html, params, progressCallback, signal) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const LOCALIZABLE_ATTRIBUTES = {
|
|
284
|
-
meta: ["content"],
|
|
285
|
-
img: ["alt"],
|
|
286
|
-
input: ["placeholder"],
|
|
287
|
-
a: ["title"]
|
|
498
|
+
const trackProps = {
|
|
499
|
+
method: "localizeHtml",
|
|
500
|
+
sourceLocale: params.sourceLocale,
|
|
501
|
+
targetLocale: params.targetLocale
|
|
288
502
|
};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
503
|
+
trackEvent(
|
|
504
|
+
this.config.apiKey,
|
|
505
|
+
this.config.apiUrl,
|
|
506
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
507
|
+
trackProps
|
|
508
|
+
);
|
|
509
|
+
try {
|
|
510
|
+
const jsdomPackage = await Promise.resolve().then(() => _interopRequireWildcard(require("jsdom")));
|
|
511
|
+
const { JSDOM } = jsdomPackage;
|
|
512
|
+
const dom = new JSDOM(html);
|
|
513
|
+
const document = dom.window.document;
|
|
514
|
+
const LOCALIZABLE_ATTRIBUTES = {
|
|
515
|
+
meta: ["content"],
|
|
516
|
+
img: ["alt"],
|
|
517
|
+
input: ["placeholder"],
|
|
518
|
+
a: ["title"]
|
|
519
|
+
};
|
|
520
|
+
const UNLOCALIZABLE_TAGS = ["script", "style"];
|
|
521
|
+
const extractedContent = {};
|
|
522
|
+
const getPath = (node, attribute) => {
|
|
523
|
+
const indices = [];
|
|
524
|
+
let current = node;
|
|
525
|
+
let rootParent = "";
|
|
526
|
+
while (current) {
|
|
527
|
+
const parent = current.parentElement;
|
|
528
|
+
if (!parent) break;
|
|
529
|
+
if (parent === document.documentElement) {
|
|
530
|
+
rootParent = current.nodeName.toLowerCase();
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
const siblings = Array.from(parent.childNodes).filter(
|
|
534
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _2 => _2.textContent, 'optionalAccess', _3 => _3.trim, 'call', _4 => _4()])
|
|
535
|
+
);
|
|
536
|
+
const index = siblings.indexOf(current);
|
|
537
|
+
if (index !== -1) {
|
|
538
|
+
indices.unshift(index);
|
|
539
|
+
}
|
|
540
|
+
current = parent;
|
|
301
541
|
}
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
542
|
+
const basePath = rootParent ? `${rootParent}/${indices.join("/")}` : indices.join("/");
|
|
543
|
+
return attribute ? `${basePath}#${attribute}` : basePath;
|
|
544
|
+
};
|
|
545
|
+
const processNode = (node) => {
|
|
546
|
+
let parent = node.parentElement;
|
|
547
|
+
while (parent) {
|
|
548
|
+
if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
parent = parent.parentElement;
|
|
308
552
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
553
|
+
if (node.nodeType === 3) {
|
|
554
|
+
const text = _optionalChain([node, 'access', _5 => _5.textContent, 'optionalAccess', _6 => _6.trim, 'call', _7 => _7()]) || "";
|
|
555
|
+
if (text) {
|
|
556
|
+
extractedContent[getPath(node)] = text;
|
|
557
|
+
}
|
|
558
|
+
} else if (node.nodeType === 1) {
|
|
559
|
+
const element = node;
|
|
560
|
+
const tagName = element.tagName.toLowerCase();
|
|
561
|
+
const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
|
|
562
|
+
attributes.forEach((attr) => {
|
|
563
|
+
const value = element.getAttribute(attr);
|
|
564
|
+
if (value) {
|
|
565
|
+
extractedContent[getPath(element, attr)] = value;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
Array.from(element.childNodes).filter(
|
|
569
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _8 => _8.textContent, 'optionalAccess', _9 => _9.trim, 'call', _10 => _10()])
|
|
570
|
+
).forEach(processNode);
|
|
319
571
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
572
|
+
};
|
|
573
|
+
Array.from(document.head.childNodes).filter(
|
|
574
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _11 => _11.textContent, 'optionalAccess', _12 => _12.trim, 'call', _13 => _13()])
|
|
575
|
+
).forEach(processNode);
|
|
576
|
+
Array.from(document.body.childNodes).filter(
|
|
577
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _14 => _14.textContent, 'optionalAccess', _15 => _15.trim, 'call', _16 => _16()])
|
|
578
|
+
).forEach(processNode);
|
|
579
|
+
const localizedContent = await this._localizeRaw(
|
|
580
|
+
extractedContent,
|
|
581
|
+
params,
|
|
582
|
+
progressCallback,
|
|
583
|
+
signal
|
|
584
|
+
);
|
|
585
|
+
document.documentElement.setAttribute("lang", params.targetLocale);
|
|
586
|
+
Object.entries(localizedContent).forEach(([path, value]) => {
|
|
587
|
+
const [nodePath, attribute] = path.split("#");
|
|
588
|
+
const [rootTag, ...indices] = nodePath.split("/");
|
|
589
|
+
let parent = rootTag === "head" ? document.head : document.body;
|
|
590
|
+
let current = parent;
|
|
591
|
+
for (const index of indices) {
|
|
592
|
+
const siblings = Array.from(parent.childNodes).filter(
|
|
593
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _17 => _17.textContent, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()])
|
|
594
|
+
);
|
|
595
|
+
current = siblings[parseInt(index)] || null;
|
|
596
|
+
if (_optionalChain([current, 'optionalAccess', _20 => _20.nodeType]) === 1) {
|
|
597
|
+
parent = current;
|
|
598
|
+
}
|
|
326
599
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const value = element.getAttribute(attr);
|
|
333
|
-
if (value) {
|
|
334
|
-
extractedContent[getPath(element, attr)] = value;
|
|
600
|
+
if (current) {
|
|
601
|
+
if (attribute) {
|
|
602
|
+
current.setAttribute(attribute, value);
|
|
603
|
+
} else {
|
|
604
|
+
current.textContent = value;
|
|
335
605
|
}
|
|
336
|
-
});
|
|
337
|
-
Array.from(element.childNodes).filter(
|
|
338
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _7 => _7.textContent, 'optionalAccess', _8 => _8.trim, 'call', _9 => _9()])
|
|
339
|
-
).forEach(processNode);
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
Array.from(document.head.childNodes).filter(
|
|
343
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _10 => _10.textContent, 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()])
|
|
344
|
-
).forEach(processNode);
|
|
345
|
-
Array.from(document.body.childNodes).filter(
|
|
346
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _13 => _13.textContent, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()])
|
|
347
|
-
).forEach(processNode);
|
|
348
|
-
const localizedContent = await this._localizeRaw(
|
|
349
|
-
extractedContent,
|
|
350
|
-
params,
|
|
351
|
-
progressCallback,
|
|
352
|
-
signal
|
|
353
|
-
);
|
|
354
|
-
document.documentElement.setAttribute("lang", params.targetLocale);
|
|
355
|
-
Object.entries(localizedContent).forEach(([path, value]) => {
|
|
356
|
-
const [nodePath, attribute] = path.split("#");
|
|
357
|
-
const [rootTag, ...indices] = nodePath.split("/");
|
|
358
|
-
let parent = rootTag === "head" ? document.head : document.body;
|
|
359
|
-
let current = parent;
|
|
360
|
-
for (const index of indices) {
|
|
361
|
-
const siblings = Array.from(parent.childNodes).filter(
|
|
362
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _16 => _16.textContent, 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()])
|
|
363
|
-
);
|
|
364
|
-
current = siblings[parseInt(index)] || null;
|
|
365
|
-
if (_optionalChain([current, 'optionalAccess', _19 => _19.nodeType]) === 1) {
|
|
366
|
-
parent = current;
|
|
367
606
|
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
607
|
+
});
|
|
608
|
+
const result = dom.serialize();
|
|
609
|
+
trackEvent(
|
|
610
|
+
this.config.apiKey,
|
|
611
|
+
this.config.apiUrl,
|
|
612
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
613
|
+
trackProps
|
|
614
|
+
);
|
|
615
|
+
return result;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
trackEvent(
|
|
618
|
+
this.config.apiKey,
|
|
619
|
+
this.config.apiUrl,
|
|
620
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
621
|
+
{
|
|
622
|
+
...trackProps,
|
|
623
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
374
624
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
625
|
+
);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
378
628
|
}
|
|
379
629
|
/**
|
|
380
630
|
* Detect the language of a given text
|
|
@@ -383,25 +633,51 @@ var LingoDotDevEngine = class {
|
|
|
383
633
|
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
|
384
634
|
*/
|
|
385
635
|
async recognizeLocale(text, signal) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`
|
|
399
|
-
|
|
636
|
+
const trackProps = { method: "recognizeLocale" };
|
|
637
|
+
trackEvent(
|
|
638
|
+
this.config.apiKey,
|
|
639
|
+
this.config.apiUrl,
|
|
640
|
+
TRACKING_EVENTS.RECOGNIZE_START,
|
|
641
|
+
trackProps
|
|
642
|
+
);
|
|
643
|
+
try {
|
|
644
|
+
const response = await fetch(`${this.config.apiUrl}/recognize`, {
|
|
645
|
+
method: "POST",
|
|
646
|
+
headers: {
|
|
647
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
648
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
649
|
+
},
|
|
650
|
+
body: JSON.stringify({ text }),
|
|
651
|
+
signal
|
|
652
|
+
});
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
if (response.status >= 500 && response.status < 600) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
`Server error (${response.status}): ${response.statusText}. This may be due to temporary service issues.`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
throw new Error(`Error recognizing locale: ${response.statusText}`);
|
|
400
660
|
}
|
|
401
|
-
|
|
661
|
+
const jsonResponse = await response.json();
|
|
662
|
+
trackEvent(
|
|
663
|
+
this.config.apiKey,
|
|
664
|
+
this.config.apiUrl,
|
|
665
|
+
TRACKING_EVENTS.RECOGNIZE_SUCCESS,
|
|
666
|
+
trackProps
|
|
667
|
+
);
|
|
668
|
+
return jsonResponse.locale;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
trackEvent(
|
|
671
|
+
this.config.apiKey,
|
|
672
|
+
this.config.apiUrl,
|
|
673
|
+
TRACKING_EVENTS.RECOGNIZE_ERROR,
|
|
674
|
+
{
|
|
675
|
+
...trackProps,
|
|
676
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
throw error;
|
|
402
680
|
}
|
|
403
|
-
const jsonResponse = await response.json();
|
|
404
|
-
return jsonResponse.locale;
|
|
405
681
|
}
|
|
406
682
|
async whoami(signal) {
|
|
407
683
|
try {
|
|
@@ -409,13 +685,13 @@ var LingoDotDevEngine = class {
|
|
|
409
685
|
method: "POST",
|
|
410
686
|
headers: {
|
|
411
687
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
412
|
-
|
|
688
|
+
"Content-Type": "application/json"
|
|
413
689
|
},
|
|
414
690
|
signal
|
|
415
691
|
});
|
|
416
692
|
if (res.ok) {
|
|
417
693
|
const payload = await res.json();
|
|
418
|
-
if (!_optionalChain([payload, 'optionalAccess',
|
|
694
|
+
if (!_optionalChain([payload, 'optionalAccess', _21 => _21.email])) {
|
|
419
695
|
return null;
|
|
420
696
|
}
|
|
421
697
|
return {
|
package/build/index.mjs
CHANGED
|
@@ -2,6 +2,97 @@
|
|
|
2
2
|
import Z from "zod";
|
|
3
3
|
import { localeCodeSchema } from "@lingo.dev/_spec";
|
|
4
4
|
import { createId } from "@paralleldrive/cuid2";
|
|
5
|
+
|
|
6
|
+
// src/utils/observability.ts
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
|
|
9
|
+
// src/utils/tracking-events.ts
|
|
10
|
+
var TRACKING_EVENTS = {
|
|
11
|
+
LOCALIZE_START: "sdk.localize.start",
|
|
12
|
+
LOCALIZE_SUCCESS: "sdk.localize.success",
|
|
13
|
+
LOCALIZE_ERROR: "sdk.localize.error",
|
|
14
|
+
RECOGNIZE_START: "sdk.recognize.start",
|
|
15
|
+
RECOGNIZE_SUCCESS: "sdk.recognize.success",
|
|
16
|
+
RECOGNIZE_ERROR: "sdk.recognize.error"
|
|
17
|
+
};
|
|
18
|
+
var TRACKING_VERSION = "1.0";
|
|
19
|
+
var SDK_PACKAGE = "@lingo.dev/_sdk";
|
|
20
|
+
|
|
21
|
+
// src/utils/observability.ts
|
|
22
|
+
var POSTHOG_API_KEY = "phc_eR0iSoQufBxNY36k0f0T15UvHJdTfHlh8rJcxsfhfXk";
|
|
23
|
+
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
24
|
+
var identityCache = /* @__PURE__ */ new Map();
|
|
25
|
+
function trackEvent(apiKey, apiUrl, event, properties) {
|
|
26
|
+
if (process.env.DO_NOT_TRACK === "1") {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
resolveIdentityAndCapture(apiKey, apiUrl, event, properties).catch(
|
|
30
|
+
(error) => {
|
|
31
|
+
if (process.env.DEBUG === "true") {
|
|
32
|
+
console.error("[Tracking] Error:", error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
async function resolveIdentityAndCapture(apiKey, apiUrl, event, properties) {
|
|
38
|
+
const identityInfo = await getDistinctId(apiKey, apiUrl);
|
|
39
|
+
if (process.env.DEBUG === "true") {
|
|
40
|
+
console.log(
|
|
41
|
+
`[Tracking] Event: ${event}, ID: ${identityInfo.distinct_id}, Source: ${identityInfo.distinct_id_source}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const { PostHog } = await import("posthog-node");
|
|
45
|
+
const posthog = new PostHog(POSTHOG_API_KEY, {
|
|
46
|
+
host: POSTHOG_HOST,
|
|
47
|
+
flushAt: 1,
|
|
48
|
+
flushInterval: 0
|
|
49
|
+
});
|
|
50
|
+
await posthog.capture({
|
|
51
|
+
distinctId: identityInfo.distinct_id,
|
|
52
|
+
event,
|
|
53
|
+
properties: {
|
|
54
|
+
...properties,
|
|
55
|
+
tracking_version: TRACKING_VERSION,
|
|
56
|
+
sdk_package: SDK_PACKAGE,
|
|
57
|
+
distinct_id_source: identityInfo.distinct_id_source
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
await posthog.shutdown();
|
|
61
|
+
}
|
|
62
|
+
async function getDistinctId(apiKey, apiUrl) {
|
|
63
|
+
const cached = identityCache.get(apiKey);
|
|
64
|
+
if (cached) return cached;
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`${apiUrl}/whoami`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${apiKey}`,
|
|
70
|
+
"Content-Type": "application/json"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (res.ok) {
|
|
74
|
+
const payload = await res.json();
|
|
75
|
+
if (payload?.email) {
|
|
76
|
+
const identity2 = {
|
|
77
|
+
distinct_id: payload.email,
|
|
78
|
+
distinct_id_source: "email"
|
|
79
|
+
};
|
|
80
|
+
identityCache.set(apiKey, identity2);
|
|
81
|
+
return identity2;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
|
|
87
|
+
const identity = {
|
|
88
|
+
distinct_id: `apikey-${hash}`,
|
|
89
|
+
distinct_id_source: "api_key_hash"
|
|
90
|
+
};
|
|
91
|
+
identityCache.set(apiKey, identity);
|
|
92
|
+
return identity;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/index.ts
|
|
5
96
|
var engineParamsSchema = Z.object({
|
|
6
97
|
apiKey: Z.string(),
|
|
7
98
|
apiUrl: Z.string().url().default("https://engine.lingo.dev"),
|
|
@@ -171,7 +262,43 @@ var LingoDotDevEngine = class {
|
|
|
171
262
|
* @returns A new object with the same structure but localized string values
|
|
172
263
|
*/
|
|
173
264
|
async localizeObject(obj, params, progressCallback, signal) {
|
|
174
|
-
|
|
265
|
+
const trackProps = {
|
|
266
|
+
method: "localizeObject",
|
|
267
|
+
sourceLocale: params.sourceLocale,
|
|
268
|
+
targetLocale: params.targetLocale
|
|
269
|
+
};
|
|
270
|
+
trackEvent(
|
|
271
|
+
this.config.apiKey,
|
|
272
|
+
this.config.apiUrl,
|
|
273
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
274
|
+
trackProps
|
|
275
|
+
);
|
|
276
|
+
try {
|
|
277
|
+
const result = await this._localizeRaw(
|
|
278
|
+
obj,
|
|
279
|
+
params,
|
|
280
|
+
progressCallback,
|
|
281
|
+
signal
|
|
282
|
+
);
|
|
283
|
+
trackEvent(
|
|
284
|
+
this.config.apiKey,
|
|
285
|
+
this.config.apiUrl,
|
|
286
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
287
|
+
trackProps
|
|
288
|
+
);
|
|
289
|
+
return result;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
trackEvent(
|
|
292
|
+
this.config.apiKey,
|
|
293
|
+
this.config.apiUrl,
|
|
294
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
295
|
+
{
|
|
296
|
+
...trackProps,
|
|
297
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
175
302
|
}
|
|
176
303
|
/**
|
|
177
304
|
* Localize a single text string
|
|
@@ -185,13 +312,44 @@ var LingoDotDevEngine = class {
|
|
|
185
312
|
* @returns The localized text string
|
|
186
313
|
*/
|
|
187
314
|
async localizeText(text, params, progressCallback, signal) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
params,
|
|
191
|
-
|
|
192
|
-
|
|
315
|
+
const trackProps = {
|
|
316
|
+
method: "localizeText",
|
|
317
|
+
sourceLocale: params.sourceLocale,
|
|
318
|
+
targetLocale: params.targetLocale
|
|
319
|
+
};
|
|
320
|
+
trackEvent(
|
|
321
|
+
this.config.apiKey,
|
|
322
|
+
this.config.apiUrl,
|
|
323
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
324
|
+
trackProps
|
|
193
325
|
);
|
|
194
|
-
|
|
326
|
+
try {
|
|
327
|
+
const response = await this._localizeRaw(
|
|
328
|
+
{ text },
|
|
329
|
+
params,
|
|
330
|
+
progressCallback,
|
|
331
|
+
signal
|
|
332
|
+
);
|
|
333
|
+
const result = response.text || "";
|
|
334
|
+
trackEvent(
|
|
335
|
+
this.config.apiKey,
|
|
336
|
+
this.config.apiUrl,
|
|
337
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
338
|
+
trackProps
|
|
339
|
+
);
|
|
340
|
+
return result;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
trackEvent(
|
|
343
|
+
this.config.apiKey,
|
|
344
|
+
this.config.apiUrl,
|
|
345
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
346
|
+
{
|
|
347
|
+
...trackProps,
|
|
348
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
195
353
|
}
|
|
196
354
|
/**
|
|
197
355
|
* Localize a text string to multiple target locales
|
|
@@ -230,15 +388,45 @@ var LingoDotDevEngine = class {
|
|
|
230
388
|
* @returns An array of localized strings in the same order
|
|
231
389
|
*/
|
|
232
390
|
async localizeStringArray(strings, params) {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
391
|
+
const trackProps = {
|
|
392
|
+
method: "localizeStringArray",
|
|
393
|
+
sourceLocale: params.sourceLocale,
|
|
394
|
+
targetLocale: params.targetLocale
|
|
395
|
+
};
|
|
396
|
+
trackEvent(
|
|
397
|
+
this.config.apiKey,
|
|
398
|
+
this.config.apiUrl,
|
|
399
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
400
|
+
trackProps
|
|
239
401
|
);
|
|
240
|
-
|
|
241
|
-
|
|
402
|
+
try {
|
|
403
|
+
const mapped = strings.reduce(
|
|
404
|
+
(acc, str, i) => {
|
|
405
|
+
acc[`item_${i}`] = str;
|
|
406
|
+
return acc;
|
|
407
|
+
},
|
|
408
|
+
{}
|
|
409
|
+
);
|
|
410
|
+
const result = await this._localizeRaw(mapped, params);
|
|
411
|
+
trackEvent(
|
|
412
|
+
this.config.apiKey,
|
|
413
|
+
this.config.apiUrl,
|
|
414
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
415
|
+
trackProps
|
|
416
|
+
);
|
|
417
|
+
return Object.values(result);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
trackEvent(
|
|
420
|
+
this.config.apiKey,
|
|
421
|
+
this.config.apiUrl,
|
|
422
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
423
|
+
{
|
|
424
|
+
...trackProps,
|
|
425
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
242
430
|
}
|
|
243
431
|
/**
|
|
244
432
|
* Localize a chat sequence while preserving speaker names
|
|
@@ -252,16 +440,47 @@ var LingoDotDevEngine = class {
|
|
|
252
440
|
* @returns Array of localized chat messages with preserved structure
|
|
253
441
|
*/
|
|
254
442
|
async localizeChat(chat, params, progressCallback, signal) {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
params,
|
|
258
|
-
|
|
259
|
-
|
|
443
|
+
const trackProps = {
|
|
444
|
+
method: "localizeChat",
|
|
445
|
+
sourceLocale: params.sourceLocale,
|
|
446
|
+
targetLocale: params.targetLocale
|
|
447
|
+
};
|
|
448
|
+
trackEvent(
|
|
449
|
+
this.config.apiKey,
|
|
450
|
+
this.config.apiUrl,
|
|
451
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
452
|
+
trackProps
|
|
260
453
|
);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
454
|
+
try {
|
|
455
|
+
const localized = await this._localizeRaw(
|
|
456
|
+
{ chat },
|
|
457
|
+
params,
|
|
458
|
+
progressCallback,
|
|
459
|
+
signal
|
|
460
|
+
);
|
|
461
|
+
const result = Object.entries(localized).map(([key, value]) => ({
|
|
462
|
+
name: chat[parseInt(key.split("_")[1])].name,
|
|
463
|
+
text: value
|
|
464
|
+
}));
|
|
465
|
+
trackEvent(
|
|
466
|
+
this.config.apiKey,
|
|
467
|
+
this.config.apiUrl,
|
|
468
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
469
|
+
trackProps
|
|
470
|
+
);
|
|
471
|
+
return result;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
trackEvent(
|
|
474
|
+
this.config.apiKey,
|
|
475
|
+
this.config.apiUrl,
|
|
476
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
477
|
+
{
|
|
478
|
+
...trackProps,
|
|
479
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
480
|
+
}
|
|
481
|
+
);
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
265
484
|
}
|
|
266
485
|
/**
|
|
267
486
|
* Localize an HTML document while preserving structure and formatting
|
|
@@ -276,105 +495,136 @@ var LingoDotDevEngine = class {
|
|
|
276
495
|
* @returns The localized HTML document as a string, with updated lang attribute
|
|
277
496
|
*/
|
|
278
497
|
async localizeHtml(html, params, progressCallback, signal) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const LOCALIZABLE_ATTRIBUTES = {
|
|
284
|
-
meta: ["content"],
|
|
285
|
-
img: ["alt"],
|
|
286
|
-
input: ["placeholder"],
|
|
287
|
-
a: ["title"]
|
|
498
|
+
const trackProps = {
|
|
499
|
+
method: "localizeHtml",
|
|
500
|
+
sourceLocale: params.sourceLocale,
|
|
501
|
+
targetLocale: params.targetLocale
|
|
288
502
|
};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
503
|
+
trackEvent(
|
|
504
|
+
this.config.apiKey,
|
|
505
|
+
this.config.apiUrl,
|
|
506
|
+
TRACKING_EVENTS.LOCALIZE_START,
|
|
507
|
+
trackProps
|
|
508
|
+
);
|
|
509
|
+
try {
|
|
510
|
+
const jsdomPackage = await import("jsdom");
|
|
511
|
+
const { JSDOM } = jsdomPackage;
|
|
512
|
+
const dom = new JSDOM(html);
|
|
513
|
+
const document = dom.window.document;
|
|
514
|
+
const LOCALIZABLE_ATTRIBUTES = {
|
|
515
|
+
meta: ["content"],
|
|
516
|
+
img: ["alt"],
|
|
517
|
+
input: ["placeholder"],
|
|
518
|
+
a: ["title"]
|
|
519
|
+
};
|
|
520
|
+
const UNLOCALIZABLE_TAGS = ["script", "style"];
|
|
521
|
+
const extractedContent = {};
|
|
522
|
+
const getPath = (node, attribute) => {
|
|
523
|
+
const indices = [];
|
|
524
|
+
let current = node;
|
|
525
|
+
let rootParent = "";
|
|
526
|
+
while (current) {
|
|
527
|
+
const parent = current.parentElement;
|
|
528
|
+
if (!parent) break;
|
|
529
|
+
if (parent === document.documentElement) {
|
|
530
|
+
rootParent = current.nodeName.toLowerCase();
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
const siblings = Array.from(parent.childNodes).filter(
|
|
534
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
535
|
+
);
|
|
536
|
+
const index = siblings.indexOf(current);
|
|
537
|
+
if (index !== -1) {
|
|
538
|
+
indices.unshift(index);
|
|
539
|
+
}
|
|
540
|
+
current = parent;
|
|
301
541
|
}
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
542
|
+
const basePath = rootParent ? `${rootParent}/${indices.join("/")}` : indices.join("/");
|
|
543
|
+
return attribute ? `${basePath}#${attribute}` : basePath;
|
|
544
|
+
};
|
|
545
|
+
const processNode = (node) => {
|
|
546
|
+
let parent = node.parentElement;
|
|
547
|
+
while (parent) {
|
|
548
|
+
if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
parent = parent.parentElement;
|
|
308
552
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
553
|
+
if (node.nodeType === 3) {
|
|
554
|
+
const text = node.textContent?.trim() || "";
|
|
555
|
+
if (text) {
|
|
556
|
+
extractedContent[getPath(node)] = text;
|
|
557
|
+
}
|
|
558
|
+
} else if (node.nodeType === 1) {
|
|
559
|
+
const element = node;
|
|
560
|
+
const tagName = element.tagName.toLowerCase();
|
|
561
|
+
const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
|
|
562
|
+
attributes.forEach((attr) => {
|
|
563
|
+
const value = element.getAttribute(attr);
|
|
564
|
+
if (value) {
|
|
565
|
+
extractedContent[getPath(element, attr)] = value;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
Array.from(element.childNodes).filter(
|
|
569
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
570
|
+
).forEach(processNode);
|
|
319
571
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
572
|
+
};
|
|
573
|
+
Array.from(document.head.childNodes).filter(
|
|
574
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
575
|
+
).forEach(processNode);
|
|
576
|
+
Array.from(document.body.childNodes).filter(
|
|
577
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
578
|
+
).forEach(processNode);
|
|
579
|
+
const localizedContent = await this._localizeRaw(
|
|
580
|
+
extractedContent,
|
|
581
|
+
params,
|
|
582
|
+
progressCallback,
|
|
583
|
+
signal
|
|
584
|
+
);
|
|
585
|
+
document.documentElement.setAttribute("lang", params.targetLocale);
|
|
586
|
+
Object.entries(localizedContent).forEach(([path, value]) => {
|
|
587
|
+
const [nodePath, attribute] = path.split("#");
|
|
588
|
+
const [rootTag, ...indices] = nodePath.split("/");
|
|
589
|
+
let parent = rootTag === "head" ? document.head : document.body;
|
|
590
|
+
let current = parent;
|
|
591
|
+
for (const index of indices) {
|
|
592
|
+
const siblings = Array.from(parent.childNodes).filter(
|
|
593
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
594
|
+
);
|
|
595
|
+
current = siblings[parseInt(index)] || null;
|
|
596
|
+
if (current?.nodeType === 1) {
|
|
597
|
+
parent = current;
|
|
598
|
+
}
|
|
326
599
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const value = element.getAttribute(attr);
|
|
333
|
-
if (value) {
|
|
334
|
-
extractedContent[getPath(element, attr)] = value;
|
|
600
|
+
if (current) {
|
|
601
|
+
if (attribute) {
|
|
602
|
+
current.setAttribute(attribute, value);
|
|
603
|
+
} else {
|
|
604
|
+
current.textContent = value;
|
|
335
605
|
}
|
|
336
|
-
});
|
|
337
|
-
Array.from(element.childNodes).filter(
|
|
338
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
339
|
-
).forEach(processNode);
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
Array.from(document.head.childNodes).filter(
|
|
343
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
344
|
-
).forEach(processNode);
|
|
345
|
-
Array.from(document.body.childNodes).filter(
|
|
346
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
347
|
-
).forEach(processNode);
|
|
348
|
-
const localizedContent = await this._localizeRaw(
|
|
349
|
-
extractedContent,
|
|
350
|
-
params,
|
|
351
|
-
progressCallback,
|
|
352
|
-
signal
|
|
353
|
-
);
|
|
354
|
-
document.documentElement.setAttribute("lang", params.targetLocale);
|
|
355
|
-
Object.entries(localizedContent).forEach(([path, value]) => {
|
|
356
|
-
const [nodePath, attribute] = path.split("#");
|
|
357
|
-
const [rootTag, ...indices] = nodePath.split("/");
|
|
358
|
-
let parent = rootTag === "head" ? document.head : document.body;
|
|
359
|
-
let current = parent;
|
|
360
|
-
for (const index of indices) {
|
|
361
|
-
const siblings = Array.from(parent.childNodes).filter(
|
|
362
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
|
|
363
|
-
);
|
|
364
|
-
current = siblings[parseInt(index)] || null;
|
|
365
|
-
if (current?.nodeType === 1) {
|
|
366
|
-
parent = current;
|
|
367
606
|
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
607
|
+
});
|
|
608
|
+
const result = dom.serialize();
|
|
609
|
+
trackEvent(
|
|
610
|
+
this.config.apiKey,
|
|
611
|
+
this.config.apiUrl,
|
|
612
|
+
TRACKING_EVENTS.LOCALIZE_SUCCESS,
|
|
613
|
+
trackProps
|
|
614
|
+
);
|
|
615
|
+
return result;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
trackEvent(
|
|
618
|
+
this.config.apiKey,
|
|
619
|
+
this.config.apiUrl,
|
|
620
|
+
TRACKING_EVENTS.LOCALIZE_ERROR,
|
|
621
|
+
{
|
|
622
|
+
...trackProps,
|
|
623
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
374
624
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
625
|
+
);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
378
628
|
}
|
|
379
629
|
/**
|
|
380
630
|
* Detect the language of a given text
|
|
@@ -383,25 +633,51 @@ var LingoDotDevEngine = class {
|
|
|
383
633
|
* @returns Promise resolving to a locale code (e.g., 'en', 'es', 'fr')
|
|
384
634
|
*/
|
|
385
635
|
async recognizeLocale(text, signal) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`
|
|
399
|
-
|
|
636
|
+
const trackProps = { method: "recognizeLocale" };
|
|
637
|
+
trackEvent(
|
|
638
|
+
this.config.apiKey,
|
|
639
|
+
this.config.apiUrl,
|
|
640
|
+
TRACKING_EVENTS.RECOGNIZE_START,
|
|
641
|
+
trackProps
|
|
642
|
+
);
|
|
643
|
+
try {
|
|
644
|
+
const response = await fetch(`${this.config.apiUrl}/recognize`, {
|
|
645
|
+
method: "POST",
|
|
646
|
+
headers: {
|
|
647
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
648
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
649
|
+
},
|
|
650
|
+
body: JSON.stringify({ text }),
|
|
651
|
+
signal
|
|
652
|
+
});
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
if (response.status >= 500 && response.status < 600) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
`Server error (${response.status}): ${response.statusText}. This may be due to temporary service issues.`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
throw new Error(`Error recognizing locale: ${response.statusText}`);
|
|
400
660
|
}
|
|
401
|
-
|
|
661
|
+
const jsonResponse = await response.json();
|
|
662
|
+
trackEvent(
|
|
663
|
+
this.config.apiKey,
|
|
664
|
+
this.config.apiUrl,
|
|
665
|
+
TRACKING_EVENTS.RECOGNIZE_SUCCESS,
|
|
666
|
+
trackProps
|
|
667
|
+
);
|
|
668
|
+
return jsonResponse.locale;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
trackEvent(
|
|
671
|
+
this.config.apiKey,
|
|
672
|
+
this.config.apiUrl,
|
|
673
|
+
TRACKING_EVENTS.RECOGNIZE_ERROR,
|
|
674
|
+
{
|
|
675
|
+
...trackProps,
|
|
676
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
throw error;
|
|
402
680
|
}
|
|
403
|
-
const jsonResponse = await response.json();
|
|
404
|
-
return jsonResponse.locale;
|
|
405
681
|
}
|
|
406
682
|
async whoami(signal) {
|
|
407
683
|
try {
|
|
@@ -409,7 +685,7 @@ var LingoDotDevEngine = class {
|
|
|
409
685
|
method: "POST",
|
|
410
686
|
headers: {
|
|
411
687
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
412
|
-
|
|
688
|
+
"Content-Type": "application/json"
|
|
413
689
|
},
|
|
414
690
|
signal
|
|
415
691
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingo.dev/_sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Lingo.dev JS SDK",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@paralleldrive/cuid2": "2.2.2",
|
|
28
28
|
"jsdom": "25.0.1",
|
|
29
|
+
"posthog-node": "5.14.0",
|
|
29
30
|
"zod": "4.1.12",
|
|
30
|
-
"@lingo.dev/_spec": "0.
|
|
31
|
+
"@lingo.dev/_spec": "0.48.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@types/jsdom": "21.1.7",
|