@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
|
-
|
|
680
|
-
|
|
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
|
-
|
|
727
|
+
safeOnNavigate();
|
|
701
728
|
return ret;
|
|
702
729
|
});
|
|
703
730
|
historyObj.replaceState = ((...args) => {
|
|
704
731
|
const ret = originalReplaceState(...args);
|
|
705
|
-
|
|
732
|
+
safeOnNavigate();
|
|
706
733
|
return ret;
|
|
707
734
|
});
|
|
708
|
-
window.addEventListener("popstate",
|
|
709
|
-
window.addEventListener("hashchange",
|
|
735
|
+
window.addEventListener("popstate", safeOnNavigate);
|
|
736
|
+
window.addEventListener("hashchange", safeOnNavigate);
|
|
710
737
|
return () => {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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