@lovalingo/lovalingo 0.3.3 → 0.3.4

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.
@@ -74,6 +74,10 @@ navigateRef, // For path mode routing
74
74
  apiRef.current = new LovalingoAPI(resolvedApiKey, apiBase, enhancedPathConfig);
75
75
  }, [apiBase, enhancedPathConfig, resolvedApiKey]);
76
76
  const [entitlements, setEntitlements] = useState(() => apiRef.current.getEntitlements());
77
+ const lastPageviewRef = useRef("");
78
+ const historyPatchedRef = useRef(false);
79
+ const originalHistoryRef = useRef(null);
80
+ const onNavigateRef = useRef(() => undefined);
77
81
  const retryTimeoutRef = useRef(null);
78
82
  const isNavigatingRef = useRef(false);
79
83
  const isInternalNavigationRef = useRef(false);
@@ -138,6 +142,18 @@ navigateRef, // For path mode routing
138
142
  setBrandingEnabled(readBrandingCache());
139
143
  // eslint-disable-next-line react-hooks/exhaustive-deps
140
144
  }, [brandingStorageKey]);
145
+ useEffect(() => {
146
+ lastPageviewRef.current = "";
147
+ }, [resolvedApiKey]);
148
+ const trackPageviewOnce = useCallback((path) => {
149
+ const next = (path || "").toString();
150
+ if (!next)
151
+ return;
152
+ if (lastPageviewRef.current === next)
153
+ return;
154
+ lastPageviewRef.current = next;
155
+ apiRef.current.trackPageview(next);
156
+ }, []);
141
157
  const enablePrehide = useCallback((bgColor) => {
142
158
  if (typeof document === "undefined")
143
159
  return;
@@ -674,18 +690,9 @@ navigateRef, // For path mode routing
674
690
  toTranslations,
675
691
  writeCriticalCache,
676
692
  ]);
677
- // SPA router hook-in: track History API navigations (React Router/Next/etc) without app changes.
678
693
  useEffect(() => {
679
- const historyObj = window.history;
680
- const originalPushState = historyObj.pushState.bind(historyObj);
681
- const originalReplaceState = historyObj.replaceState.bind(historyObj);
682
- const onNavigate = () => {
683
- try {
684
- apiRef.current.trackPageview(window.location.pathname + window.location.search);
685
- }
686
- catch {
687
- // ignore
688
- }
694
+ onNavigateRef.current = () => {
695
+ trackPageviewOnce(window.location.pathname + window.location.search);
689
696
  const nextLocale = detectLocale();
690
697
  if (nextLocale !== locale) {
691
698
  setLocaleState(nextLocale);
@@ -695,25 +702,50 @@ navigateRef, // For path mode routing
695
702
  applyActiveTranslations(document.body);
696
703
  }
697
704
  };
705
+ }, [defaultLocale, detectLocale, loadData, locale, mode, trackPageviewOnce]);
706
+ // SPA router hook-in: patch History API once (prevents stacked wrappers → request storms).
707
+ useEffect(() => {
708
+ if (typeof window === "undefined")
709
+ return;
710
+ if (historyPatchedRef.current)
711
+ return;
712
+ historyPatchedRef.current = true;
713
+ const historyObj = window.history;
714
+ const originalPushState = historyObj.pushState.bind(historyObj);
715
+ const originalReplaceState = historyObj.replaceState.bind(historyObj);
716
+ originalHistoryRef.current = { pushState: originalPushState, replaceState: originalReplaceState };
717
+ const safeOnNavigate = () => {
718
+ try {
719
+ onNavigateRef.current();
720
+ }
721
+ catch {
722
+ // ignore
723
+ }
724
+ };
698
725
  historyObj.pushState = ((...args) => {
699
726
  const ret = originalPushState(...args);
700
- onNavigate();
727
+ safeOnNavigate();
701
728
  return ret;
702
729
  });
703
730
  historyObj.replaceState = ((...args) => {
704
731
  const ret = originalReplaceState(...args);
705
- onNavigate();
732
+ safeOnNavigate();
706
733
  return ret;
707
734
  });
708
- window.addEventListener("popstate", onNavigate);
709
- window.addEventListener("hashchange", onNavigate);
735
+ window.addEventListener("popstate", safeOnNavigate);
736
+ window.addEventListener("hashchange", safeOnNavigate);
710
737
  return () => {
711
- historyObj.pushState = originalPushState;
712
- historyObj.replaceState = originalReplaceState;
713
- window.removeEventListener("popstate", onNavigate);
714
- window.removeEventListener("hashchange", onNavigate);
738
+ const originals = originalHistoryRef.current;
739
+ if (originals) {
740
+ historyObj.pushState = originals.pushState;
741
+ historyObj.replaceState = originals.replaceState;
742
+ }
743
+ window.removeEventListener("popstate", safeOnNavigate);
744
+ window.removeEventListener("hashchange", safeOnNavigate);
745
+ originalHistoryRef.current = null;
746
+ historyPatchedRef.current = false;
715
747
  };
716
- }, [defaultLocale, detectLocale, loadData, locale, mode]);
748
+ }, []);
717
749
  // Change locale
718
750
  const setLocale = useCallback((newLocale) => {
719
751
  void (async () => {
@@ -770,7 +802,7 @@ navigateRef, // For path mode routing
770
802
  useEffect(() => {
771
803
  const initialLocale = detectLocale();
772
804
  // Track initial page (fallback discovery for pages not present in the routes feed).
773
- apiRef.current.trackPageview(window.location.pathname + window.location.search);
805
+ trackPageviewOnce(window.location.pathname + window.location.search);
774
806
  // Always prefetch artifacts for the initial locale (pipeline-produced translations + rules).
775
807
  loadData(initialLocale);
776
808
  // Set up keyboard shortcut for edit mode
@@ -788,7 +820,7 @@ navigateRef, // For path mode routing
788
820
  clearTimeout(retryTimeoutRef.current);
789
821
  }
790
822
  };
791
- }, [detectLocale, loadData, editKey]);
823
+ }, [detectLocale, loadData, editKey, trackPageviewOnce]);
792
824
  // Auto-inject sitemap link tag
793
825
  useEffect(() => {
794
826
  if (sitemap && resolvedApiKey && isSeoActive()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
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",