@lovalingo/lovalingo 0.5.7 → 0.5.9
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { LovalingoContext } from '../context/LovalingoContext';
|
|
3
3
|
import { LangRoutingContext } from '../context/LangRoutingContext';
|
|
4
4
|
import { LovalingoAPI } from '../utils/api';
|
|
@@ -15,6 +15,8 @@ import { LanguageSwitcher } from './LanguageSwitcher';
|
|
|
15
15
|
const LOCALE_STORAGE_KEY = 'Lovalingo_locale';
|
|
16
16
|
const LOADING_BG_STORAGE_PREFIX = "Lovalingo_loading_bg_color";
|
|
17
17
|
const BRANDING_STORAGE_PREFIX = "Lovalingo_branding_enabled";
|
|
18
|
+
// Why: run initial load before first paint on the client to avoid a prehide flash; useEffect on SSR.
|
|
19
|
+
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
18
20
|
export const LovalingoProvider = ({ children, apiKey: apiKeyProp, publicAnonKey, defaultLocale, locales, apiBase = 'https://cdn.lovalingo.com', routing = 'path', // Default to path mode (SEO-friendly, recommended)
|
|
19
21
|
autoPrefixLinks = true, overlayBgColor, autoApplyRules = true, switcherPosition = 'bottom-right', switcherOffsetY = 20, switcherTheme = 'dark', editMode: initialEditMode = false, editKey = 'KeyE', pathNormalization = { enabled: true }, // Enable by default
|
|
20
22
|
mode = 'dom', // Default to legacy DOM mode for backward compatibility
|
|
@@ -524,7 +526,7 @@ navigateRef, // For path mode routing
|
|
|
524
526
|
detectLocaleRef.current = detectLocale;
|
|
525
527
|
}, [detectLocale]);
|
|
526
528
|
// Initialize
|
|
527
|
-
|
|
529
|
+
useIsomorphicLayoutEffect(() => {
|
|
528
530
|
const initialLocale = detectLocaleRef.current();
|
|
529
531
|
lastNormalizedPathRef.current = processPath(window.location.pathname, enhancedPathConfig);
|
|
530
532
|
// Track initial page (fallback discovery for pages not present in the routes feed).
|
|
@@ -168,6 +168,18 @@ export function useBundleLoading({ apiRef, resolvedApiKey, defaultLocale, routin
|
|
|
168
168
|
logDebug(`[Lovalingo] Fetching translations for ${targetLocale} on ${normalizedPath}`);
|
|
169
169
|
setIsLoading(true);
|
|
170
170
|
enablePrehide(getCachedLoadingBgColor());
|
|
171
|
+
let revealedEarly = false;
|
|
172
|
+
const revealNow = () => {
|
|
173
|
+
if (revealedEarly)
|
|
174
|
+
return;
|
|
175
|
+
revealedEarly = true;
|
|
176
|
+
disablePrehide();
|
|
177
|
+
setIsLoading(false);
|
|
178
|
+
if (loadingFailsafeTimeoutRef.current != null) {
|
|
179
|
+
window.clearTimeout(loadingFailsafeTimeoutRef.current);
|
|
180
|
+
loadingFailsafeTimeoutRef.current = null;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
171
183
|
// Why: never keep the app hidden/blocked for longer than the UX budget; show the original content if translations aren't ready fast.
|
|
172
184
|
loadingFailsafeTimeoutRef.current = window.setTimeout(() => {
|
|
173
185
|
disablePrehide();
|
|
@@ -192,7 +204,7 @@ export function useBundleLoading({ apiRef, resolvedApiKey, defaultLocale, routin
|
|
|
192
204
|
if (mode === "dom") {
|
|
193
205
|
applyActiveTranslations(document.body);
|
|
194
206
|
}
|
|
195
|
-
|
|
207
|
+
revealNow();
|
|
196
208
|
revealedViaCachedCritical = true;
|
|
197
209
|
}
|
|
198
210
|
const bootstrap = await apiRef.current.fetchBootstrap(targetLocale, currentPath);
|
|
@@ -238,6 +250,8 @@ export function useBundleLoading({ apiRef, resolvedApiKey, defaultLocale, routin
|
|
|
238
250
|
if (mode === "dom") {
|
|
239
251
|
applyActiveTranslations(document.body);
|
|
240
252
|
}
|
|
253
|
+
// Why: once we have a deterministic critical slice, unblock the page immediately and keep remaining work in the background.
|
|
254
|
+
revealNow();
|
|
241
255
|
}
|
|
242
256
|
if (autoApplyRules) {
|
|
243
257
|
const bootstrapDomRules = bootstrap?.dom_rules;
|
|
@@ -286,7 +300,7 @@ export function useBundleLoading({ apiRef, resolvedApiKey, defaultLocale, routin
|
|
|
286
300
|
}
|
|
287
301
|
})();
|
|
288
302
|
}
|
|
289
|
-
|
|
303
|
+
revealNow();
|
|
290
304
|
// Delayed retry scan to catch late-rendering content
|
|
291
305
|
retryTimeoutRef.current = setTimeout(() => {
|
|
292
306
|
// Don't scan if we're navigating (prevents React conflicts)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
// Why:
|
|
3
|
-
export const PREHIDE_FAILSAFE_MS =
|
|
2
|
+
// Why: keep the no-flash "prehide" budget tight to improve FCP while still preventing perma-hide.
|
|
3
|
+
export const PREHIDE_FAILSAFE_MS = 900;
|
|
4
4
|
export function usePrehide() {
|
|
5
5
|
const prehideStateRef = useRef({
|
|
6
6
|
active: false,
|
package/package.json
CHANGED