@lovalingo/lovalingo 0.5.4 → 0.5.5

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  LOVALINGO COMMERCIAL LICENSE
2
2
 
3
- Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
3
+ Copyright (c) 2026 Lovalingo Swiss. All rights reserved.
4
4
 
5
5
  NOTICE: This software and associated documentation files (the "Software") are the
6
6
  proprietary and confidential information of Lovalingo Swiss.
@@ -143,6 +143,6 @@ please contact Lovalingo Swiss.
143
143
 
144
144
  MERTENS ADVIES
145
145
  Lovalingo Translation Platform
146
- © 2025 All Rights Reserved
146
+ © 2026 All Rights Reserved
147
147
 
148
148
  Website: [Contact Lovalingo Swiss for licensing information]
package/README.md CHANGED
@@ -21,7 +21,10 @@ The runtime now marks translatable text nodes deterministically and exposes mark
21
21
  - Coverage is enforced server-side (jobs fail if marker coverage is below the threshold).
22
22
  - This is a breaking change: older runtimes will be rejected by the pipeline.
23
23
 
24
- Debug (runtime logs): set `window.__lovalingoDebug = true` before initializing `LovalingoProvider`.
24
+ Debug (runtime logs):
25
+ - append `?lovalingoDebug=1` to the URL (works across reloads)
26
+ - or run `localStorage.setItem("Lovalingo_debug","1")` and reload
27
+ - or set `window.__lovalingoDebug = true` (same-tab only; not persistent)
25
28
 
26
29
  ## Installation
27
30
 
@@ -137,7 +140,7 @@ You still need to serve `/sitemap.xml` on your own domain (recommended: reverse-
137
140
 
138
141
  ## License
139
142
 
140
- Commercial license (not open source). See `react-package/LICENSE`.
143
+ Commercial license (not open source). See `LICENSE`.
141
144
 
142
145
  Manual translation control:
143
146
 
@@ -349,7 +352,7 @@ export async function GET() {
349
352
 
350
353
  **COMMERCIAL LICENSE - NOT OPEN SOURCE**
351
354
 
352
- Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
355
+ Copyright (c) 2026 Lovalingo Swiss. All rights reserved.
353
356
 
354
357
  ### For Agencies & Developers
355
358
 
@@ -641,6 +641,7 @@ navigateRef, // For path mode routing
641
641
  if (previousLocale && previousLocale !== defaultLocale) {
642
642
  logDebug(`[Lovalingo] Switching from ${previousLocale} to ${targetLocale}`);
643
643
  }
644
+ let revealedViaCachedCritical = false;
644
645
  const cachedCritical = readCriticalCache(targetLocale, normalizedPath);
645
646
  if (cachedCritical?.loading_bg_color) {
646
647
  setCachedLoadingBgColor(cachedCritical.loading_bg_color);
@@ -656,6 +657,7 @@ navigateRef, // For path mode routing
656
657
  applyActiveTranslations(document.body);
657
658
  }
658
659
  disablePrehide();
660
+ revealedViaCachedCritical = true;
659
661
  }
660
662
  const bootstrap = await apiRef.current.fetchBootstrap(targetLocale, currentPath);
661
663
  const nextEntitlements = bootstrap?.entitlements || apiRef.current.getEntitlements();
@@ -694,6 +696,7 @@ navigateRef, // For path mode routing
694
696
  const criticalMap = bootstrap?.critical?.map && typeof bootstrap.critical.map === "object" && !Array.isArray(bootstrap.critical.map)
695
697
  ? bootstrap.critical.map
696
698
  : {};
699
+ const hasBootstrapCritical = Object.keys(criticalMap).length > 0;
697
700
  if (Object.keys(criticalMap).length > 0) {
698
701
  setActiveTranslations(toTranslations(criticalMap, targetLocale));
699
702
  if (mode === "dom") {
@@ -714,6 +717,37 @@ navigateRef, // For path mode routing
714
717
  exclusions,
715
718
  loading_bg_color: bootstrap?.loading_bg_color && /^#[0-9a-fA-F]{6}$/.test(bootstrap.loading_bg_color) ? bootstrap.loading_bg_color : null,
716
719
  });
720
+ const shouldWaitForBundle = !revealedViaCachedCritical && !hasBootstrapCritical;
721
+ if (shouldWaitForBundle) {
722
+ // Why: if there's no critical slice for first paint, wait for the bundle (within the prehide failsafe) to avoid a visible flash.
723
+ const bundle = await apiRef.current.fetchBundle(targetLocale, currentPath);
724
+ if (bundle?.map && typeof bundle.map === "object") {
725
+ const translations = toTranslations(bundle.map, targetLocale);
726
+ if (translations.length > 0) {
727
+ translationCacheRef.current.set(cacheKey, { translations });
728
+ setActiveTranslations(translations);
729
+ if (mode === "dom") {
730
+ applyActiveTranslations(document.body);
731
+ }
732
+ }
733
+ }
734
+ }
735
+ else {
736
+ // Lazy-load the full page bundle after first paint.
737
+ void (async () => {
738
+ const bundle = await apiRef.current.fetchBundle(targetLocale, currentPath);
739
+ if (!bundle || !bundle.map)
740
+ return;
741
+ const translations = toTranslations(bundle.map, targetLocale);
742
+ if (translations.length === 0)
743
+ return;
744
+ translationCacheRef.current.set(cacheKey, { translations });
745
+ setActiveTranslations(translations);
746
+ if (mode === "dom") {
747
+ applyActiveTranslations(document.body);
748
+ }
749
+ })();
750
+ }
717
751
  disablePrehide();
718
752
  // Delayed retry scan to catch late-rendering content
719
753
  retryTimeoutRef.current = setTimeout(() => {
@@ -730,20 +764,6 @@ navigateRef, // For path mode routing
730
764
  applyDomRules(rules);
731
765
  }
732
766
  }, 500);
733
- // Lazy-load the full page bundle after first paint.
734
- void (async () => {
735
- const bundle = await apiRef.current.fetchBundle(targetLocale, currentPath);
736
- if (!bundle || !bundle.map)
737
- return;
738
- const translations = toTranslations(bundle.map, targetLocale);
739
- if (translations.length === 0)
740
- return;
741
- translationCacheRef.current.set(cacheKey, { translations });
742
- setActiveTranslations(translations);
743
- if (mode === "dom") {
744
- applyActiveTranslations(document.body);
745
- }
746
- })();
747
767
  }
748
768
  catch (error) {
749
769
  errorDebug('Error loading translations:', error);
@@ -903,14 +923,23 @@ navigateRef, // For path mode routing
903
923
  });
904
924
  }, [allLocales, defaultLocale, locale, routing, loadData, navigateRef, routingConfig.nonLocalizedPaths]);
905
925
  // No string-level "misses" from the client: the pipeline (Browser Rendering) is source-of-truth for string discovery.
926
+ // Why: prevent init/load effects from re-running (and calling bootstrap/bundle again) when loadData changes due to state updates.
927
+ const loadDataRef = useRef(loadData);
928
+ useEffect(() => {
929
+ loadDataRef.current = loadData;
930
+ }, [loadData]);
931
+ const detectLocaleRef = useRef(detectLocale);
932
+ useEffect(() => {
933
+ detectLocaleRef.current = detectLocale;
934
+ }, [detectLocale]);
906
935
  // Initialize
907
936
  useEffect(() => {
908
- const initialLocale = detectLocale();
937
+ const initialLocale = detectLocaleRef.current();
909
938
  lastNormalizedPathRef.current = processPath(window.location.pathname, enhancedPathConfig);
910
939
  // Track initial page (fallback discovery for pages not present in the routes feed).
911
940
  trackPageviewOnce(window.location.pathname + window.location.search);
912
941
  // Always prefetch artifacts for the initial locale (pipeline-produced translations + rules).
913
- loadData(initialLocale);
942
+ loadDataRef.current(initialLocale);
914
943
  // Set up keyboard shortcut for edit mode
915
944
  const handleKeyPress = (e) => {
916
945
  if (e.code === editKey && (e.ctrlKey || e.metaKey)) {
@@ -926,7 +955,7 @@ navigateRef, // For path mode routing
926
955
  clearTimeout(retryTimeoutRef.current);
927
956
  }
928
957
  };
929
- }, [detectLocale, enhancedPathConfig, loadData, editKey, trackPageviewOnce]);
958
+ }, [editKey, enhancedPathConfig, trackPageviewOnce]);
930
959
  // Auto-inject sitemap link tag
931
960
  useEffect(() => {
932
961
  if (sitemap && resolvedApiKey && isSeoActive()) {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @Lovalingo/Lovalingo - Proprietary Translation Library
3
3
  *
4
- * Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
4
+ * Copyright (c) 2026 Lovalingo Swiss. All rights reserved.
5
5
  *
6
6
  * This software is the intellectual property of Lovalingo Swiss.
7
7
  * NOT OPEN SOURCE - All rights reserved.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @Lovalingo/Lovalingo - Proprietary Translation Library
3
3
  *
4
- * Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
4
+ * Copyright (c) 2026 Lovalingo Swiss. All rights reserved.
5
5
  *
6
6
  * This software is the intellectual property of Lovalingo Swiss.
7
7
  * NOT OPEN SOURCE - All rights reserved.
@@ -4,6 +4,23 @@ function isDebugEnabled() {
4
4
  const value = globalThis.__lovalingoDebug;
5
5
  if (value === true || value === "true" || value === 1)
6
6
  return true;
7
+ try {
8
+ const params = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
9
+ const query = params?.get("lovalingoDebug") || params?.get("lovalingo_debug") || "";
10
+ if (query === "1" || query === "true")
11
+ return true;
12
+ }
13
+ catch {
14
+ // ignore
15
+ }
16
+ try {
17
+ const stored = typeof window !== "undefined" ? window.localStorage?.getItem("Lovalingo_debug") : null;
18
+ if (stored === "1" || stored === "true")
19
+ return true;
20
+ }
21
+ catch {
22
+ // ignore
23
+ }
7
24
  return false;
8
25
  }
9
26
  export function logDebug(...args) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.4";
1
+ export declare const VERSION = "0.5.5";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.5.4";
1
+ export const VERSION = "0.5.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
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",