@lovalingo/lovalingo 0.5.6 → 0.5.8

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.
@@ -252,7 +252,6 @@ navigateRef, // For path mode routing
252
252
  const head = document.head;
253
253
  if (!head)
254
254
  return;
255
- head.querySelectorAll('link[data-Lovalingo="hreflang"], link[data-Lovalingo="canonical"]').forEach((el) => el.remove());
256
255
  if (!bundle)
257
256
  return;
258
257
  const seo = (bundle?.seo && typeof bundle.seo === "object" ? bundle.seo : {});
@@ -321,6 +320,14 @@ navigateRef, // For path mode routing
321
320
  : typeof alternates.canonical === "string" && alternates.canonical.trim()
322
321
  ? alternates.canonical.trim()
323
322
  : "";
323
+ const languages = alternates.languages && typeof alternates.languages === "object" ? alternates.languages : {};
324
+ const hasAnyAlternate = Boolean(alternates.xDefault) || Object.values(languages).some(Boolean);
325
+ if (!canonicalHref && !(hreflangEnabled && hasAnyAlternate))
326
+ return;
327
+ // Why: search engines may ignore hreflang/canonical when multiple conflicting tags exist (we want sitemap + head parity).
328
+ head
329
+ .querySelectorAll('link[rel="canonical"], link[rel="alternate"][hreflang], link[data-Lovalingo="hreflang"], link[data-Lovalingo="canonical"]')
330
+ .forEach((el) => el.remove());
324
331
  if (canonicalHref) {
325
332
  const canonical = document.createElement("link");
326
333
  canonical.rel = "canonical";
@@ -330,7 +337,6 @@ navigateRef, // For path mode routing
330
337
  }
331
338
  if (!hreflangEnabled)
332
339
  return;
333
- const languages = alternates.languages && typeof alternates.languages === "object" ? alternates.languages : {};
334
340
  for (const [lang, href] of Object.entries(languages)) {
335
341
  if (!href)
336
342
  continue;
@@ -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
- disablePrehide();
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
- disablePrehide();
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,4 +1,4 @@
1
- export declare const PREHIDE_FAILSAFE_MS = 1700;
1
+ export declare const PREHIDE_FAILSAFE_MS = 900;
2
2
  export declare function usePrehide(): {
3
3
  enablePrehide: (bgColor: string) => void;
4
4
  disablePrehide: () => void;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
- // Why: avoid long blank screens on blocked/untranslated routes by always revealing the original page quickly.
3
- export const PREHIDE_FAILSAFE_MS = 1700;
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/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.5";
1
+ export declare const VERSION = "0.5.6";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.5.5";
1
+ export const VERSION = "0.5.6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
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",