@lovalingo/lovalingo 0.5.25 → 0.5.28

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.
Files changed (67) hide show
  1. package/dist/__tests__/languageFlags.test.d.ts +1 -0
  2. package/dist/__tests__/languageFlags.test.js +42 -0
  3. package/dist/__tests__/mergeEntitlements.test.d.ts +1 -0
  4. package/dist/__tests__/mergeEntitlements.test.js +27 -0
  5. package/dist/components/LanguageSwitcher.js +80 -53
  6. package/dist/components/LovalingoProvider.js +18 -473
  7. package/dist/components/provider/__tests__/seoUtils.test.d.ts +1 -0
  8. package/dist/components/provider/__tests__/seoUtils.test.js +13 -0
  9. package/dist/components/provider/editModeUtils.d.ts +6 -0
  10. package/dist/components/provider/editModeUtils.js +59 -0
  11. package/dist/components/provider/localeUtils.d.ts +8 -0
  12. package/dist/components/provider/localeUtils.js +46 -0
  13. package/dist/components/provider/providerConstants.d.ts +12 -0
  14. package/dist/components/provider/providerConstants.js +11 -0
  15. package/dist/components/provider/seoUtils.d.ts +8 -0
  16. package/dist/components/provider/seoUtils.js +118 -0
  17. package/dist/components/provider/useEditModeOverlay.d.ts +7 -0
  18. package/dist/components/provider/useEditModeOverlay.js +134 -0
  19. package/dist/components/provider/useHistoryNavigationPatch.d.ts +3 -0
  20. package/dist/components/provider/useHistoryNavigationPatch.js +47 -0
  21. package/dist/components/provider/useProviderCache.d.ts +12 -0
  22. package/dist/components/provider/useProviderCache.js +82 -0
  23. package/dist/hooks/provider/useBundleLoading.d.ts +2 -1
  24. package/dist/hooks/provider/useBundleLoading.js +15 -3
  25. package/dist/utils/api.d.ts +3 -78
  26. package/dist/utils/api.js +1 -53
  27. package/dist/utils/apiTypes.d.ts +78 -0
  28. package/dist/utils/apiTypes.js +1 -0
  29. package/dist/utils/apiUtils.d.ts +4 -0
  30. package/dist/utils/apiUtils.js +54 -0
  31. package/dist/utils/languageFlags.d.ts +7 -0
  32. package/dist/utils/languageFlags.js +90 -0
  33. package/dist/utils/markerEngine.d.ts +8 -66
  34. package/dist/utils/markerEngine.js +19 -703
  35. package/dist/utils/markerEngineApply.d.ts +3 -0
  36. package/dist/utils/markerEngineApply.js +136 -0
  37. package/dist/utils/markerEngineConstants.d.ts +10 -0
  38. package/dist/utils/markerEngineConstants.js +12 -0
  39. package/dist/utils/markerEngineCritical.d.ts +2 -0
  40. package/dist/utils/markerEngineCritical.js +98 -0
  41. package/dist/utils/markerEngineDomUtils.d.ts +8 -0
  42. package/dist/utils/markerEngineDomUtils.js +74 -0
  43. package/dist/utils/markerEngineFilters.d.ts +2 -0
  44. package/dist/utils/markerEngineFilters.js +26 -0
  45. package/dist/utils/markerEngineMisses.d.ts +5 -0
  46. package/dist/utils/markerEngineMisses.js +81 -0
  47. package/dist/utils/markerEngineOriginals.d.ts +5 -0
  48. package/dist/utils/markerEngineOriginals.js +29 -0
  49. package/dist/utils/markerEngineScan.d.ts +5 -0
  50. package/dist/utils/markerEngineScan.js +162 -0
  51. package/dist/utils/markerEngineState.d.ts +4 -0
  52. package/dist/utils/markerEngineState.js +14 -0
  53. package/dist/utils/markerEngineStats.d.ts +3 -0
  54. package/dist/utils/markerEngineStats.js +28 -0
  55. package/dist/utils/markerEngineTranslations.d.ts +3 -0
  56. package/dist/utils/markerEngineTranslations.js +49 -0
  57. package/dist/utils/markerEngineTypes.d.ts +62 -0
  58. package/dist/utils/markerEngineTypes.js +1 -0
  59. package/dist/utils/markerEngineViewport.d.ts +2 -0
  60. package/dist/utils/markerEngineViewport.js +27 -0
  61. package/dist/utils/mergeEntitlements.d.ts +2 -0
  62. package/dist/utils/mergeEntitlements.js +7 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.js +1 -1
  65. package/package.json +1 -1
  66. package/dist/utils/translator.d.ts +0 -80
  67. package/dist/utils/translator.js +0 -802
@@ -0,0 +1,4 @@
1
+ export declare function getCustomExcludeSelector(): string | null;
2
+ export declare function setCustomExcludeSelector(value: string | null): void;
3
+ export declare function getActiveTranslationMap(): Map<string, string> | null;
4
+ export declare function setActiveTranslationMap(value: Map<string, string> | null): void;
@@ -0,0 +1,14 @@
1
+ let customExcludeSelector = null;
2
+ let activeTranslationMap = null;
3
+ export function getCustomExcludeSelector() {
4
+ return customExcludeSelector;
5
+ }
6
+ export function setCustomExcludeSelector(value) {
7
+ customExcludeSelector = value;
8
+ }
9
+ export function getActiveTranslationMap() {
10
+ return activeTranslationMap;
11
+ }
12
+ export function setActiveTranslationMap(value) {
13
+ activeTranslationMap = value;
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { MarkerStats } from "./markerEngineTypes";
2
+ export declare function buildEmptyStats(): MarkerStats;
3
+ export declare function finalizeStats(stats: MarkerStats): void;
@@ -0,0 +1,28 @@
1
+ export function buildEmptyStats() {
2
+ return {
3
+ totalTextNodes: 0,
4
+ markedNodes: 0,
5
+ skippedUnsafeNodes: 0,
6
+ skippedExcludedNodes: 0,
7
+ skippedNonTranslatableNodes: 0,
8
+ totalChars: 0,
9
+ markedChars: 0,
10
+ skippedUnsafeChars: 0,
11
+ skippedExcludedChars: 0,
12
+ skippedNonTranslatableChars: 0,
13
+ coverageRatio: 0,
14
+ coverageRatioChars: 0,
15
+ };
16
+ }
17
+ export function finalizeStats(stats) {
18
+ const eligibleNodes = stats.totalTextNodes -
19
+ stats.skippedUnsafeNodes -
20
+ stats.skippedExcludedNodes -
21
+ stats.skippedNonTranslatableNodes;
22
+ const eligibleChars = stats.totalChars -
23
+ stats.skippedUnsafeChars -
24
+ stats.skippedExcludedChars -
25
+ stats.skippedNonTranslatableChars;
26
+ stats.coverageRatio = eligibleNodes > 0 ? stats.markedNodes / eligibleNodes : 1;
27
+ stats.coverageRatioChars = eligibleChars > 0 ? stats.markedChars / eligibleChars : 1;
28
+ }
@@ -0,0 +1,3 @@
1
+ import type { Translation } from "../types";
2
+ export declare function setActiveTranslations(translations: Translation[] | null): void;
3
+ export declare function addActiveTranslations(translations: Translation[] | Record<string, string> | null): number;
@@ -0,0 +1,49 @@
1
+ import { normalizeWhitespace } from "./markerEngineDomUtils";
2
+ import { getActiveTranslationMap, setActiveTranslationMap } from "./markerEngineState";
3
+ export function setActiveTranslations(translations) {
4
+ if (!translations || translations.length === 0) {
5
+ setActiveTranslationMap(null);
6
+ return;
7
+ }
8
+ const map = new Map();
9
+ for (const t of translations) {
10
+ const source = normalizeWhitespace((t?.source_text || "").toString());
11
+ const translated = (t?.translated_text ?? "").toString();
12
+ if (!source || !translated)
13
+ continue;
14
+ map.set(source, translated);
15
+ }
16
+ setActiveTranslationMap(map);
17
+ }
18
+ export function addActiveTranslations(translations) {
19
+ if (!translations)
20
+ return 0;
21
+ const map = getActiveTranslationMap() ?? new Map();
22
+ let added = 0;
23
+ if (Array.isArray(translations)) {
24
+ for (const t of translations) {
25
+ const source = normalizeWhitespace((t?.source_text || "").toString());
26
+ const translated = (t?.translated_text ?? "").toString();
27
+ if (!source || !translated)
28
+ continue;
29
+ if (map.get(source) === translated)
30
+ continue;
31
+ map.set(source, translated);
32
+ added += 1;
33
+ }
34
+ }
35
+ else {
36
+ for (const [keyRaw, valueRaw] of Object.entries(translations || {})) {
37
+ const source = normalizeWhitespace((keyRaw || "").toString());
38
+ const translated = (valueRaw ?? "").toString();
39
+ if (!source || !translated)
40
+ continue;
41
+ if (map.get(source) === translated)
42
+ continue;
43
+ map.set(source, translated);
44
+ added += 1;
45
+ }
46
+ }
47
+ setActiveTranslationMap(map);
48
+ return added;
49
+ }
@@ -0,0 +1,62 @@
1
+ export type MarkerStats = {
2
+ totalTextNodes: number;
3
+ markedNodes: number;
4
+ skippedUnsafeNodes: number;
5
+ skippedExcludedNodes: number;
6
+ skippedNonTranslatableNodes: number;
7
+ totalChars: number;
8
+ markedChars: number;
9
+ skippedUnsafeChars: number;
10
+ skippedExcludedChars: number;
11
+ skippedNonTranslatableChars: number;
12
+ coverageRatio: number;
13
+ coverageRatioChars: number;
14
+ };
15
+ export type MarkerEngineOptions = {
16
+ throttleMs?: number;
17
+ };
18
+ export type DomScanOccurrence = {
19
+ source_text: string;
20
+ semantic_context?: string;
21
+ };
22
+ export type DomScanSegment = {
23
+ kind: "text" | "title" | "aria-label" | "placeholder";
24
+ selector: string | null;
25
+ original: string | null;
26
+ current: string | null;
27
+ html: null;
28
+ };
29
+ export type DomScanResult = {
30
+ version: 1;
31
+ stats: MarkerStats;
32
+ segments: DomScanSegment[];
33
+ occurrences: DomScanOccurrence[];
34
+ critical_occurrences?: DomScanOccurrence[];
35
+ viewport?: {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ truncated: boolean;
40
+ };
41
+ export type DomMiss = {
42
+ source_text: string;
43
+ semantic_context: string;
44
+ };
45
+ export type DomMissScanResult = {
46
+ misses: DomMiss[];
47
+ };
48
+ export type CriticalFingerprint = {
49
+ critical_count: number;
50
+ critical_hash: string;
51
+ viewport: {
52
+ width: number;
53
+ height: number;
54
+ };
55
+ };
56
+ export type TextOriginal = {
57
+ raw: string;
58
+ trimmed: string;
59
+ leading: string;
60
+ trailing: string;
61
+ id: string;
62
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function isInViewport(rect: DOMRect | null, viewportHeight: number, bufferPx: number): boolean;
2
+ export declare function getTextNodeRect(node: Text): DOMRect | null;
@@ -0,0 +1,27 @@
1
+ export function isInViewport(rect, viewportHeight, bufferPx) {
2
+ if (!rect)
3
+ return false;
4
+ if (!Number.isFinite(rect.top) || !Number.isFinite(rect.bottom))
5
+ return false;
6
+ if (rect.width <= 0 || rect.height <= 0)
7
+ return false;
8
+ return rect.bottom > -bufferPx && rect.top < viewportHeight + bufferPx;
9
+ }
10
+ export function getTextNodeRect(node) {
11
+ try {
12
+ const range = document.createRange();
13
+ range.selectNodeContents(node);
14
+ const rect = range.getBoundingClientRect();
15
+ if (rect && rect.width > 0 && rect.height > 0)
16
+ return rect;
17
+ }
18
+ catch {
19
+ // ignore
20
+ }
21
+ try {
22
+ return node.parentElement ? node.parentElement.getBoundingClientRect() : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProjectEntitlements } from "./apiTypes";
2
+ export declare function mergeEntitlementsSeoEnabled(entitlements: ProjectEntitlements | null | undefined, seoEnabled: unknown): ProjectEntitlements | null;
@@ -0,0 +1,7 @@
1
+ export function mergeEntitlementsSeoEnabled(entitlements, seoEnabled) {
2
+ if (!entitlements)
3
+ return null;
4
+ if (typeof seoEnabled !== "boolean")
5
+ return entitlements;
6
+ return { ...entitlements, seoEnabled };
7
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.16";
1
+ export declare const VERSION = "0.5.28";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.5.16";
1
+ export const VERSION = "0.5.28";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.25",
3
+ "version": "0.5.28",
4
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",
@@ -1,80 +0,0 @@
1
- import { Translation, Exclusion } from '../types';
2
- export declare class Translator {
3
- private translationMap;
4
- private exclusions;
5
- private nonTranslatableTerms;
6
- constructor();
7
- /**
8
- * Check if element is interactive (should be preserved as island)
9
- */
10
- private isInteractive;
11
- /**
12
- * Check if text contains actual translatable content
13
- */
14
- private isTranslatableText;
15
- /**
16
- * Check if element should be treated as semantic boundary
17
- * even though it's not in the predefined SEMANTIC_BOUNDARIES set
18
- *
19
- * Phase 1: Conservative approach - only leaf elements (no element children)
20
- * This catches cases like: <span>text</span>, <div>text</div>, etc.
21
- */
22
- private shouldTreatAsSemantic;
23
- private isLargeInteractiveContainer;
24
- private sanitizePlaceholderMap;
25
- private cleanupPreserveIds;
26
- setTranslations(translations: Translation[]): void;
27
- setExclusions(exclusions: Exclusion[]): void;
28
- private isExcluded;
29
- /**
30
- * DOM-BASED EXTRACTION
31
- * Uses DOM APIs to reliably parse and tokenize HTML
32
- */
33
- private extractTranslatableContent;
34
- /**
35
- * RECONSTRUCTION: Replace tokens with original HTML
36
- */
37
- private reconstructHTML;
38
- /**
39
- * Restore element to original HTML while preserving event listeners
40
- */
41
- private restoreElement;
42
- /**
43
- * Restore all elements to original state
44
- */
45
- restoreDOM(): void;
46
- /**
47
- * Mark all descendant elements with unique IDs before translation
48
- * This allows us to reuse original DOM nodes (preserving event listeners)
49
- */
50
- private markElements;
51
- /**
52
- * After reconstruction, transplant original LIVE elements into new structure
53
- * This preserves event listeners and framework connections
54
- */
55
- private transplantOriginalElements;
56
- /**
57
- * Directly update the parent element's children by transplanting from temp
58
- * This avoids using innerHTML which would destroy event listeners
59
- */
60
- private updateElementChildren;
61
- /**
62
- * Update only text nodes within an element, preserving child elements
63
- * Enhanced to handle nested structures recursively
64
- */
65
- private updateTextNodesOnly;
66
- translateElement(element: HTMLElement): void;
67
- /**
68
- * Restore attribute to original value
69
- */
70
- private restoreAttribute;
71
- /**
72
- * Translate interactive element safely (preserves event handlers)
73
- */
74
- private translateInteractive;
75
- /**
76
- * Translate individual attributes
77
- */
78
- private translateAttribute;
79
- translateDOM(): void;
80
- }