@lovalingo/lovalingo 0.0.26 → 0.0.27
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 +0 -1
- package/dist/components/AixsterProvider.js +94 -81
- package/dist/components/AutoTranslate.js +11 -19
- package/dist/types.d.ts +2 -4
- package/dist/utils/api.d.ts +4 -5
- package/dist/utils/api.js +33 -70
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -123,7 +123,6 @@ export default function RootLayout({ children }) {
|
|
|
123
123
|
```tsx
|
|
124
124
|
<LovalingoProvider
|
|
125
125
|
publicAnonKey="aix_xxx" // Required: Your Lovalingo Public Anon Key (safe to expose)
|
|
126
|
-
// Backwards compatible: apiKey="aix_xxx"
|
|
127
126
|
defaultLocale="en" // Required: Source language
|
|
128
127
|
locales={['en', 'de', 'fr']} // Required: Supported languages
|
|
129
128
|
apiBase="https://..." // Optional: Custom API endpoint
|
|
@@ -66,8 +66,8 @@ navigateRef, // For path mode routing
|
|
|
66
66
|
const domRulesCacheRef = useRef(new Map());
|
|
67
67
|
// NEW: Hash-based translation cache for React Context system
|
|
68
68
|
const [hashTranslations, setHashTranslations] = useState(new Map());
|
|
69
|
-
const
|
|
70
|
-
const
|
|
69
|
+
const contextMissQueueRef = useRef(new Set());
|
|
70
|
+
const contextMissFlushTimeoutRef = useRef(null);
|
|
71
71
|
const config = {
|
|
72
72
|
apiKey: resolvedApiKey,
|
|
73
73
|
publicAnonKey: resolvedApiKey,
|
|
@@ -236,6 +236,7 @@ navigateRef, // For path mode routing
|
|
|
236
236
|
if (targetLocale === defaultLocale) {
|
|
237
237
|
if (showOverlay)
|
|
238
238
|
setIsNavigationLoading(false);
|
|
239
|
+
setHashTranslations(new Map());
|
|
239
240
|
translatorRef.current.setTranslations([]);
|
|
240
241
|
translatorRef.current.restoreDOM(); // Safe to restore when going back to source language
|
|
241
242
|
translatorRef.current.restoreHead();
|
|
@@ -246,18 +247,22 @@ navigateRef, // For path mode routing
|
|
|
246
247
|
const currentPath = window.location.pathname;
|
|
247
248
|
const cacheKey = `${targetLocale}:${currentPath}`;
|
|
248
249
|
// Check if we have cached translations for this locale + path
|
|
249
|
-
const
|
|
250
|
+
const cachedEntry = translationCacheRef.current.get(cacheKey);
|
|
250
251
|
const cachedExclusions = exclusionsCacheRef.current;
|
|
251
252
|
const cachedDomRules = domRulesCacheRef.current.get(cacheKey);
|
|
252
|
-
if (
|
|
253
|
+
if (cachedEntry && cachedExclusions) {
|
|
253
254
|
// CACHE HIT - Use cached data immediately (FAST!)
|
|
254
255
|
console.log(`[Lovalingo] Using cached translations for ${targetLocale} on ${currentPath}`);
|
|
255
|
-
|
|
256
|
+
setHashTranslations(new Map(Object.entries(cachedEntry.hashMap || {})));
|
|
257
|
+
translatorRef.current.setTranslations(cachedEntry.translations);
|
|
256
258
|
translatorRef.current.setExclusions(cachedExclusions);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
if (mode === 'dom') {
|
|
260
|
+
translatorRef.current.translateDOM();
|
|
261
|
+
}
|
|
262
|
+
if (isSeoActive()) {
|
|
259
263
|
translatorRef.current.translateHead();
|
|
260
|
-
|
|
264
|
+
}
|
|
265
|
+
if (autoApplyRules) {
|
|
261
266
|
if (Array.isArray(cachedDomRules)) {
|
|
262
267
|
applyDomRules(cachedDomRules);
|
|
263
268
|
}
|
|
@@ -274,22 +279,27 @@ navigateRef, // For path mode routing
|
|
|
274
279
|
return;
|
|
275
280
|
}
|
|
276
281
|
console.log(`[Lovalingo] 🔄 Retry scan for late-rendering content`);
|
|
277
|
-
|
|
278
|
-
|
|
282
|
+
if (mode === 'dom') {
|
|
283
|
+
translatorRef.current.translateDOM();
|
|
284
|
+
}
|
|
285
|
+
if (isSeoActive()) {
|
|
279
286
|
translatorRef.current.translateHead();
|
|
280
|
-
|
|
287
|
+
}
|
|
288
|
+
if (autoApplyRules) {
|
|
281
289
|
const rules = domRulesCacheRef.current.get(cacheKey) || cachedDomRules || [];
|
|
282
290
|
applyDomRules(rules);
|
|
283
291
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
292
|
+
if (mode === "dom") {
|
|
293
|
+
// Immediately report any misses found
|
|
294
|
+
const missed = translatorRef.current.getMissedStrings();
|
|
295
|
+
if (missed.length > 0) {
|
|
296
|
+
console.log(`[Lovalingo] 📤 Reporting ${missed.length} misses immediately`);
|
|
297
|
+
apiRef.current.reportMisses(missed, defaultLocale, targetLocale);
|
|
298
|
+
translatorRef.current.clearMissedStrings();
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log(`[Lovalingo] ✅ No misses detected`);
|
|
302
|
+
}
|
|
293
303
|
}
|
|
294
304
|
}, 500);
|
|
295
305
|
if (showOverlay) {
|
|
@@ -305,26 +315,39 @@ navigateRef, // For path mode routing
|
|
|
305
315
|
if (previousLocale && previousLocale !== defaultLocale) {
|
|
306
316
|
console.log(`[Lovalingo] Switching from ${previousLocale} to ${targetLocale}`);
|
|
307
317
|
}
|
|
308
|
-
const [
|
|
309
|
-
apiRef.current.
|
|
318
|
+
const [bundle, exclusions, domRules] = await Promise.all([
|
|
319
|
+
apiRef.current.fetchBundle(targetLocale),
|
|
310
320
|
apiRef.current.fetchExclusions(),
|
|
311
|
-
autoApplyRules
|
|
321
|
+
autoApplyRules ? apiRef.current.fetchDomRules(targetLocale) : Promise.resolve([]),
|
|
312
322
|
]);
|
|
313
323
|
const nextEntitlements = apiRef.current.getEntitlements();
|
|
314
324
|
if (nextEntitlements)
|
|
315
325
|
setEntitlements(nextEntitlements);
|
|
326
|
+
const translations = bundle
|
|
327
|
+
? Object.entries(bundle.map).map(([source_text, translated_text]) => ({
|
|
328
|
+
source_text,
|
|
329
|
+
translated_text,
|
|
330
|
+
source_locale: defaultLocale,
|
|
331
|
+
target_locale: targetLocale,
|
|
332
|
+
}))
|
|
333
|
+
: [];
|
|
334
|
+
const hashMap = bundle?.hashMap || {};
|
|
335
|
+
setHashTranslations(new Map(Object.entries(hashMap)));
|
|
316
336
|
// Store in cache for next time
|
|
317
|
-
translationCacheRef.current.set(cacheKey, translations);
|
|
337
|
+
translationCacheRef.current.set(cacheKey, { translations, hashMap });
|
|
318
338
|
exclusionsCacheRef.current = exclusions;
|
|
319
|
-
if (autoApplyRules
|
|
339
|
+
if (autoApplyRules) {
|
|
320
340
|
domRulesCacheRef.current.set(cacheKey, domRules);
|
|
321
341
|
}
|
|
322
342
|
translatorRef.current.setTranslations(translations);
|
|
323
343
|
translatorRef.current.setExclusions(exclusions);
|
|
324
|
-
|
|
325
|
-
|
|
344
|
+
if (mode === 'dom') {
|
|
345
|
+
translatorRef.current.translateDOM();
|
|
346
|
+
}
|
|
347
|
+
if (isSeoActive()) {
|
|
326
348
|
translatorRef.current.translateHead();
|
|
327
|
-
|
|
349
|
+
}
|
|
350
|
+
if (autoApplyRules) {
|
|
328
351
|
applyDomRules(domRules);
|
|
329
352
|
}
|
|
330
353
|
// Delayed retry scan to catch late-rendering content
|
|
@@ -334,22 +357,27 @@ navigateRef, // For path mode routing
|
|
|
334
357
|
return;
|
|
335
358
|
}
|
|
336
359
|
console.log(`[Lovalingo] 🔄 Retry scan for late-rendering content`);
|
|
337
|
-
|
|
338
|
-
|
|
360
|
+
if (mode === "dom") {
|
|
361
|
+
translatorRef.current.translateDOM();
|
|
362
|
+
}
|
|
363
|
+
if (isSeoActive()) {
|
|
339
364
|
translatorRef.current.translateHead();
|
|
340
|
-
|
|
365
|
+
}
|
|
366
|
+
if (autoApplyRules) {
|
|
341
367
|
const rules = domRulesCacheRef.current.get(cacheKey) || domRules || [];
|
|
342
368
|
applyDomRules(rules);
|
|
343
369
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
370
|
+
if (mode === "dom") {
|
|
371
|
+
// Immediately report any misses found
|
|
372
|
+
const missed = translatorRef.current.getMissedStrings();
|
|
373
|
+
if (missed.length > 0) {
|
|
374
|
+
console.log(`[Lovalingo] 📤 Reporting ${missed.length} misses immediately`);
|
|
375
|
+
apiRef.current.reportMisses(missed, defaultLocale, targetLocale);
|
|
376
|
+
translatorRef.current.clearMissedStrings();
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
console.log(`[Lovalingo] ✅ No misses detected`);
|
|
380
|
+
}
|
|
353
381
|
}
|
|
354
382
|
}, 500);
|
|
355
383
|
if (showOverlay) {
|
|
@@ -426,43 +454,30 @@ navigateRef, // For path mode routing
|
|
|
426
454
|
const getTranslation = useCallback((hash, fallback) => {
|
|
427
455
|
return hashTranslations.get(hash) || null;
|
|
428
456
|
}, [hashTranslations]);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (translatingHashesRef.current.has(hash)) {
|
|
457
|
+
const queueMiss = useCallback((text) => {
|
|
458
|
+
const cleaned = (text || "").toString().trim();
|
|
459
|
+
if (!cleaned || cleaned.length < 2)
|
|
433
460
|
return;
|
|
434
|
-
|
|
435
|
-
// Already have translation
|
|
436
|
-
if (hashTranslations.has(hash)) {
|
|
461
|
+
if (locale === defaultLocale)
|
|
437
462
|
return;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
console.error(`[Lovalingo] Failed to translate hash ${hash}:`, error);
|
|
460
|
-
}
|
|
461
|
-
finally {
|
|
462
|
-
// Remove from translating set
|
|
463
|
-
translatingHashesRef.current.delete(hash);
|
|
464
|
-
}
|
|
465
|
-
}, [defaultLocale, locale, hashTranslations]);
|
|
463
|
+
contextMissQueueRef.current.add(cleaned);
|
|
464
|
+
if (contextMissFlushTimeoutRef.current)
|
|
465
|
+
return;
|
|
466
|
+
contextMissFlushTimeoutRef.current = setTimeout(() => {
|
|
467
|
+
contextMissFlushTimeoutRef.current = null;
|
|
468
|
+
const batch = Array.from(contextMissQueueRef.current);
|
|
469
|
+
contextMissQueueRef.current.clear();
|
|
470
|
+
if (batch.length === 0)
|
|
471
|
+
return;
|
|
472
|
+
const misses = batch.map((value) => ({
|
|
473
|
+
text: value,
|
|
474
|
+
raw: value,
|
|
475
|
+
placeholderMap: {},
|
|
476
|
+
semanticContext: "react",
|
|
477
|
+
}));
|
|
478
|
+
void apiRef.current.reportMisses(misses, defaultLocale, locale);
|
|
479
|
+
}, 800);
|
|
480
|
+
}, [defaultLocale, locale]);
|
|
466
481
|
// Selenium bridge: allows automation to force-flush misses instead of relying on the 5s interval.
|
|
467
482
|
useEffect(() => {
|
|
468
483
|
if (typeof window === "undefined")
|
|
@@ -506,10 +521,8 @@ navigateRef, // For path mode routing
|
|
|
506
521
|
if (next)
|
|
507
522
|
setEntitlements(next);
|
|
508
523
|
});
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
loadData(initialLocale);
|
|
512
|
-
}
|
|
524
|
+
// Always prefetch artifacts for the initial locale so context mode has hashMap ready.
|
|
525
|
+
loadData(initialLocale);
|
|
513
526
|
// Set up keyboard shortcut for edit mode
|
|
514
527
|
const handleKeyPress = (e) => {
|
|
515
528
|
if (e.code === editKey && (e.ctrlKey || e.metaKey)) {
|
|
@@ -530,7 +543,7 @@ navigateRef, // For path mode routing
|
|
|
530
543
|
useEffect(() => {
|
|
531
544
|
if (sitemap && resolvedApiKey && isSeoActive()) {
|
|
532
545
|
// Prefer same-origin /sitemap.xml so crawlers discover the canonical sitemap URL.
|
|
533
|
-
//
|
|
546
|
+
// Reminder: /sitemap.xml should be published by the host app (recommended: build-time copy from Lovalingo CDN).
|
|
534
547
|
const sitemapUrl = `${window.location.origin}/sitemap.xml`;
|
|
535
548
|
// Check if link already exists to avoid duplicates
|
|
536
549
|
const existingLink = document.querySelector(`link[rel="sitemap"][href="${sitemapUrl}"]`);
|
|
@@ -871,7 +884,7 @@ navigateRef, // For path mode routing
|
|
|
871
884
|
toggleEditMode,
|
|
872
885
|
excludeElement,
|
|
873
886
|
getTranslation,
|
|
874
|
-
|
|
887
|
+
queueMiss,
|
|
875
888
|
};
|
|
876
889
|
return (React.createElement(LovalingoContext.Provider, { value: contextValue },
|
|
877
890
|
children,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import { LovalingoContext } from '../context/LovalingoContext';
|
|
3
3
|
import { hashContent } from '../utils/hash';
|
|
4
4
|
/**
|
|
@@ -7,12 +7,11 @@ import { hashContent } from '../utils/hash';
|
|
|
7
7
|
*/
|
|
8
8
|
export const AutoTranslate = ({ children }) => {
|
|
9
9
|
const context = useContext(LovalingoContext);
|
|
10
|
-
const [isTranslating, setIsTranslating] = useState(false);
|
|
11
10
|
if (!context) {
|
|
12
11
|
// Not wrapped in LovalingoProvider - return children as-is
|
|
13
12
|
return React.createElement(React.Fragment, null, children);
|
|
14
13
|
}
|
|
15
|
-
const { locale, config, getTranslation,
|
|
14
|
+
const { locale, config, getTranslation, queueMiss } = context;
|
|
16
15
|
// If we're on default locale, no translation needed
|
|
17
16
|
if (locale === config.defaultLocale) {
|
|
18
17
|
return React.createElement(React.Fragment, null, children);
|
|
@@ -21,7 +20,11 @@ export const AutoTranslate = ({ children }) => {
|
|
|
21
20
|
const translateChildren = (node) => {
|
|
22
21
|
// Handle strings (text nodes)
|
|
23
22
|
if (typeof node === 'string') {
|
|
24
|
-
const
|
|
23
|
+
const match = node.match(/^(\s*)(.*?)(\s*)$/s);
|
|
24
|
+
const leading = match?.[1] ?? "";
|
|
25
|
+
const core = match?.[2] ?? node;
|
|
26
|
+
const trailing = match?.[3] ?? "";
|
|
27
|
+
const trimmed = core.trim();
|
|
25
28
|
// Skip empty or very short strings
|
|
26
29
|
if (trimmed.length === 0 || trimmed.length < 2) {
|
|
27
30
|
return node;
|
|
@@ -32,22 +35,11 @@ export const AutoTranslate = ({ children }) => {
|
|
|
32
35
|
const translation = getTranslation(contentHash, trimmed);
|
|
33
36
|
if (translation) {
|
|
34
37
|
// We have translation - return it
|
|
35
|
-
return translation
|
|
38
|
+
return `${leading}${translation}${trailing}`;
|
|
36
39
|
}
|
|
37
|
-
// No translation
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
queueRealTimeTranslation(contentHash, trimmed, () => {
|
|
41
|
-
setIsTranslating(false);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
// Return blurred original text while translating
|
|
45
|
-
return (React.createElement("span", { style: {
|
|
46
|
-
filter: 'blur(3px)',
|
|
47
|
-
opacity: 0.6,
|
|
48
|
-
transition: 'filter 0.3s ease, opacity 0.3s ease',
|
|
49
|
-
display: 'inline-block',
|
|
50
|
-
}, "data-translating": "true", "data-hash": contentHash }, trimmed));
|
|
40
|
+
// No translation yet: report miss (may enqueue deterministic pipeline job) and show original.
|
|
41
|
+
queueMiss(trimmed);
|
|
42
|
+
return node;
|
|
51
43
|
}
|
|
52
44
|
// Handle numbers
|
|
53
45
|
if (typeof node === 'number') {
|
package/dist/types.d.ts
CHANGED
|
@@ -2,12 +2,10 @@ import { PathNormalizationConfig } from './utils/pathNormalizer';
|
|
|
2
2
|
export interface LovalingoConfig {
|
|
3
3
|
/**
|
|
4
4
|
* Public project key (safe to expose in the browser).
|
|
5
|
-
* Backwards compatible alias: you can still pass `apiKey`.
|
|
6
5
|
*/
|
|
7
6
|
publicAnonKey?: string;
|
|
8
7
|
/**
|
|
9
|
-
*
|
|
10
|
-
* Prefer `publicAnonKey` in new installs.
|
|
8
|
+
* @deprecated Use `publicAnonKey`.
|
|
11
9
|
*/
|
|
12
10
|
apiKey?: string;
|
|
13
11
|
defaultLocale: string;
|
|
@@ -35,7 +33,7 @@ export interface LovalingoContextValue {
|
|
|
35
33
|
toggleEditMode: () => void;
|
|
36
34
|
excludeElement: (selector: string) => Promise<void>;
|
|
37
35
|
getTranslation: (hash: string, fallback: string) => string | null;
|
|
38
|
-
|
|
36
|
+
queueMiss: (text: string) => void;
|
|
39
37
|
}
|
|
40
38
|
export interface Translation {
|
|
41
39
|
source_text: string;
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -23,13 +23,12 @@ export declare class LovalingoAPI {
|
|
|
23
23
|
fetchEntitlements(localeHint: string): Promise<ProjectEntitlements | null>;
|
|
24
24
|
trackPageview(pathOrUrl: string): Promise<void>;
|
|
25
25
|
fetchTranslations(sourceLocale: string, targetLocale: string): Promise<Translation[]>;
|
|
26
|
+
fetchBundle(localeHint: string): Promise<{
|
|
27
|
+
map: Record<string, string>;
|
|
28
|
+
hashMap: Record<string, string>;
|
|
29
|
+
} | null>;
|
|
26
30
|
fetchExclusions(): Promise<Exclusion[]>;
|
|
27
31
|
fetchDomRules(targetLocale: string): Promise<DomRule[]>;
|
|
28
32
|
reportMisses(misses: MissedTranslation[], sourceLocale: string, targetLocale: string): Promise<void>;
|
|
29
33
|
saveExclusion(selector: string, type: 'css' | 'xpath'): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* Real-time translation - calls Groq directly for instant translation
|
|
32
|
-
* Used when translation cache misses
|
|
33
|
-
*/
|
|
34
|
-
translateRealtime(contentHash: string, sourceText: string, sourceLocale: string, targetLocale: string): Promise<string | null>;
|
|
35
34
|
}
|
package/dist/utils/api.js
CHANGED
|
@@ -16,7 +16,7 @@ export class LovalingoAPI {
|
|
|
16
16
|
logActivationRequired(context, response) {
|
|
17
17
|
console.error(`[Lovalingo] ${context} blocked (HTTP ${response.status}). ` +
|
|
18
18
|
`This project is not activated yet. ` +
|
|
19
|
-
`Publish a public
|
|
19
|
+
`Publish a public manifest at "/.well-known/lovalingo.json" on your domain, ` +
|
|
20
20
|
`then verify it in the Lovalingo dashboard to activate translations + SEO.`);
|
|
21
21
|
}
|
|
22
22
|
isActivationRequiredPayload(data) {
|
|
@@ -92,19 +92,39 @@ export class LovalingoAPI {
|
|
|
92
92
|
this.warnMissingApiKey('fetchTranslations');
|
|
93
93
|
return [];
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
const bundle = await this.fetchBundle(targetLocale);
|
|
96
|
+
if (!bundle)
|
|
97
|
+
return [];
|
|
98
|
+
return Object.entries(bundle.map).map(([source_text, translated_text]) => ({
|
|
99
|
+
source_text,
|
|
100
|
+
translated_text,
|
|
101
|
+
source_locale: sourceLocale,
|
|
102
|
+
target_locale: targetLocale,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error('Error fetching translations:', error);
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async fetchBundle(localeHint) {
|
|
111
|
+
try {
|
|
112
|
+
if (!this.hasApiKey()) {
|
|
113
|
+
this.warnMissingApiKey("fetchBundle");
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
96
116
|
const normalizedPath = processPath(window.location.pathname, this.pathConfig);
|
|
97
|
-
const response = await fetch(`${this.apiBase}/functions/v1/bundle?key=${this.apiKey}&locale=${
|
|
117
|
+
const response = await fetch(`${this.apiBase}/functions/v1/bundle?key=${this.apiKey}&locale=${localeHint}&path=${normalizedPath}`);
|
|
98
118
|
if (this.isActivationRequiredResponse(response)) {
|
|
99
|
-
this.logActivationRequired(
|
|
100
|
-
return
|
|
119
|
+
this.logActivationRequired("fetchBundle", response);
|
|
120
|
+
return null;
|
|
101
121
|
}
|
|
102
122
|
if (!response.ok)
|
|
103
|
-
|
|
123
|
+
return null;
|
|
104
124
|
const data = await response.json();
|
|
105
125
|
if (this.isActivationRequiredResponse(response, data)) {
|
|
106
|
-
this.logActivationRequired("
|
|
107
|
-
return
|
|
126
|
+
this.logActivationRequired("fetchBundle", response);
|
|
127
|
+
return null;
|
|
108
128
|
}
|
|
109
129
|
if (data?.entitlements) {
|
|
110
130
|
this.entitlements = {
|
|
@@ -112,20 +132,12 @@ export class LovalingoAPI {
|
|
|
112
132
|
seoEnabled: typeof data?.seoEnabled === "boolean" ? data.seoEnabled : undefined,
|
|
113
133
|
};
|
|
114
134
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
source_text,
|
|
119
|
-
translated_text: translated_text,
|
|
120
|
-
source_locale: sourceLocale,
|
|
121
|
-
target_locale: targetLocale,
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
return [];
|
|
135
|
+
const map = data?.map && typeof data.map === "object" ? data.map : {};
|
|
136
|
+
const hashMap = data?.hashMap && typeof data.hashMap === "object" ? data.hashMap : {};
|
|
137
|
+
return { map, hashMap };
|
|
125
138
|
}
|
|
126
|
-
catch
|
|
127
|
-
|
|
128
|
-
return [];
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
async fetchExclusions() {
|
|
@@ -257,53 +269,4 @@ export class LovalingoAPI {
|
|
|
257
269
|
throw error;
|
|
258
270
|
}
|
|
259
271
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Real-time translation - calls Groq directly for instant translation
|
|
262
|
-
* Used when translation cache misses
|
|
263
|
-
*/
|
|
264
|
-
async translateRealtime(contentHash, sourceText, sourceLocale, targetLocale) {
|
|
265
|
-
try {
|
|
266
|
-
if (!this.hasApiKey()) {
|
|
267
|
-
this.warnMissingApiKey('translateRealtime');
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
console.log(`[Lovalingo] 🚀 Real-time translation: "${sourceText.substring(0, 40)}..."`);
|
|
271
|
-
const response = await fetch(`${this.apiBase}/functions/v1/translate-realtime`, {
|
|
272
|
-
method: 'POST',
|
|
273
|
-
headers: {
|
|
274
|
-
'Content-Type': 'application/json',
|
|
275
|
-
'x-api-key': this.apiKey,
|
|
276
|
-
},
|
|
277
|
-
body: JSON.stringify({
|
|
278
|
-
contentHash,
|
|
279
|
-
sourceText,
|
|
280
|
-
sourceLocale,
|
|
281
|
-
targetLocale,
|
|
282
|
-
}),
|
|
283
|
-
});
|
|
284
|
-
if (this.isActivationRequiredResponse(response)) {
|
|
285
|
-
this.logActivationRequired('translateRealtime', response);
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
if (!response.ok) {
|
|
289
|
-
const errorText = await response.text();
|
|
290
|
-
console.error(`[Lovalingo] ❌ Real-time translation failed:`, errorText);
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
const result = await response.json();
|
|
294
|
-
if (this.isActivationRequiredResponse(response, result)) {
|
|
295
|
-
this.logActivationRequired("translateRealtime", response);
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
if (result.success && result.translation) {
|
|
299
|
-
console.log(`[Lovalingo] ✅ Translated: "${result.translation.substring(0, 40)}..." ${result.cached ? '(cached)' : '(new)'}`);
|
|
300
|
-
return result.translation;
|
|
301
|
-
}
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
console.error('[Lovalingo] ❌ Real-time translation error:', error);
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
272
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovalingo/lovalingo",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "React translation
|
|
3
|
+
"version": "0.0.27",
|
|
4
|
+
"description": "React translation runtime with i18n routing, deterministic bundles + DOM rules, and zero-flash rendering.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"publishConfig": {
|