@lovalingo/lovalingo 0.3.0 → 0.3.3

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.
@@ -9,6 +9,7 @@ import { processPath } from '../utils/pathNormalizer';
9
9
  import { LanguageSwitcher } from './LanguageSwitcher';
10
10
  const LOCALE_STORAGE_KEY = 'Lovalingo_locale';
11
11
  const LOADING_BG_STORAGE_PREFIX = "Lovalingo_loading_bg_color";
12
+ const BRANDING_STORAGE_PREFIX = "Lovalingo_branding_enabled";
12
13
  const CRITICAL_CACHE_PREFIX = "Lovalingo_critical_v0_3";
13
14
  export const LovalingoProvider = ({ children, apiKey: apiKeyProp, publicAnonKey, defaultLocale, locales, apiBase = 'https://leuskvkajliuzalrlwhw.supabase.co', routing = 'query', // Default to query mode (backward compatible)
14
15
  autoPrefixLinks = true, autoApplyRules = true, switcherPosition = 'bottom-right', switcherOffsetY = 20, switcherTheme = 'dark', editMode: initialEditMode = false, editKey = 'KeyE', pathNormalization = { enabled: true }, // Enable by default
@@ -80,6 +81,21 @@ navigateRef, // For path mode routing
80
81
  const exclusionsCacheRef = useRef(null);
81
82
  const domRulesCacheRef = useRef(new Map());
82
83
  const loadingBgStorageKey = `${LOADING_BG_STORAGE_PREFIX}:${resolvedApiKey || "anonymous"}`;
84
+ const brandingStorageKey = `${BRANDING_STORAGE_PREFIX}:${resolvedApiKey || "anonymous"}`;
85
+ const readBrandingCache = () => {
86
+ try {
87
+ const cached = (localStorage.getItem(brandingStorageKey) || "").trim();
88
+ if (cached === "0")
89
+ return false;
90
+ if (cached === "1")
91
+ return true;
92
+ }
93
+ catch {
94
+ // ignore
95
+ }
96
+ return true;
97
+ };
98
+ const [brandingEnabled, setBrandingEnabled] = useState(readBrandingCache);
83
99
  const prehideStateRef = useRef({
84
100
  active: false,
85
101
  timeoutId: null,
@@ -110,6 +126,18 @@ navigateRef, // For path mode routing
110
126
  // ignore
111
127
  }
112
128
  }, [loadingBgStorageKey]);
129
+ const setCachedBrandingEnabled = useCallback((enabled) => {
130
+ try {
131
+ localStorage.setItem(brandingStorageKey, enabled === false ? "0" : "1");
132
+ }
133
+ catch {
134
+ // ignore
135
+ }
136
+ }, [brandingStorageKey]);
137
+ useEffect(() => {
138
+ setBrandingEnabled(readBrandingCache());
139
+ // eslint-disable-next-line react-hooks/exhaustive-deps
140
+ }, [brandingStorageKey]);
113
141
  const enablePrehide = useCallback((bgColor) => {
114
142
  if (typeof document === "undefined")
115
143
  return;
@@ -299,14 +327,26 @@ navigateRef, // For path mode routing
299
327
  return;
300
328
  let cancelled = false;
301
329
  (async () => {
302
- const next = await apiRef.current.fetchEntitlements(locale);
303
- if (!cancelled && next)
304
- setEntitlements(next);
330
+ const bootstrap = await apiRef.current.fetchBootstrap(locale, window.location.pathname + window.location.search);
331
+ if (cancelled)
332
+ return;
333
+ if (bootstrap?.entitlements)
334
+ setEntitlements(bootstrap.entitlements);
335
+ if (bootstrap?.loading_bg_color)
336
+ setCachedLoadingBgColor(bootstrap.loading_bg_color);
337
+ if (bootstrap?.entitlements?.brandingRequired) {
338
+ setBrandingEnabled(true);
339
+ setCachedBrandingEnabled(true);
340
+ }
341
+ else if (typeof bootstrap?.branding_enabled === "boolean") {
342
+ setBrandingEnabled(bootstrap.branding_enabled);
343
+ setCachedBrandingEnabled(bootstrap.branding_enabled);
344
+ }
305
345
  })();
306
346
  return () => {
307
347
  cancelled = true;
308
348
  };
309
- }, [defaultLocale, entitlements, locale]);
349
+ }, [defaultLocale, entitlements, locale, setCachedBrandingEnabled, setCachedLoadingBgColor]);
310
350
  const applySeoBundle = useCallback((bundle, hreflangEnabled) => {
311
351
  try {
312
352
  const head = document.head;
@@ -531,6 +571,14 @@ navigateRef, // For path mode routing
531
571
  setCachedLoadingBgColor(bootstrap.loading_bg_color);
532
572
  enablePrehide(bootstrap.loading_bg_color);
533
573
  }
574
+ if ((bootstrap?.entitlements || nextEntitlements)?.brandingRequired) {
575
+ setBrandingEnabled(true);
576
+ setCachedBrandingEnabled(true);
577
+ }
578
+ else if (typeof bootstrap?.branding_enabled === "boolean") {
579
+ setBrandingEnabled(bootstrap.branding_enabled);
580
+ setCachedBrandingEnabled(bootstrap.branding_enabled);
581
+ }
534
582
  const exclusions = Array.isArray(bootstrap?.exclusions)
535
583
  ? bootstrap.exclusions
536
584
  .map((row) => {
@@ -1028,7 +1076,9 @@ navigateRef, // For path mode routing
1028
1076
  };
1029
1077
  return (React.createElement(LovalingoContext.Provider, { value: contextValue },
1030
1078
  children,
1031
- React.createElement(LanguageSwitcher, { locales: allLocales, currentLocale: locale, onLocaleChange: setLocale, position: switcherPosition, offsetY: switcherOffsetY, theme: switcherTheme, branding: entitlements?.brandingRequired
1032
- ? { required: true, href: "https://lovalingo.com" }
1033
- : undefined })));
1079
+ React.createElement(LanguageSwitcher, { locales: allLocales, currentLocale: locale, onLocaleChange: setLocale, position: switcherPosition, offsetY: switcherOffsetY, theme: switcherTheme, branding: {
1080
+ required: Boolean(entitlements?.brandingRequired),
1081
+ enabled: brandingEnabled,
1082
+ href: "https://lovalingo.com",
1083
+ } })));
1034
1084
  };
@@ -8,6 +8,7 @@ interface LanguageSwitcherProps {
8
8
  theme?: 'dark' | 'light';
9
9
  branding?: {
10
10
  required?: boolean;
11
+ enabled?: boolean;
11
12
  label?: string;
12
13
  href?: string;
13
14
  };
@@ -205,7 +205,7 @@ export const LanguageSwitcher = ({ locales, currentLocale, onLocaleChange, posit
205
205
  e.currentTarget.style.filter = 'brightness(1)';
206
206
  e.currentTarget.style.transform = 'scale(1)';
207
207
  }, "aria-label": `Switch to ${locale.toUpperCase()}`, title: locale.toUpperCase(), tabIndex: isOpen ? 0 : -1 }, LANGUAGE_FLAGS[locale] || '🏳️')))),
208
- branding?.required && (React.createElement("div", { style: badgeRowStyles, "aria-label": "Lovalingo branding" },
208
+ (branding?.required || branding?.enabled) && (React.createElement("div", { style: badgeRowStyles, "aria-label": "Lovalingo branding" },
209
209
  React.createElement("a", { href: branding.href || 'https://lovalingo.com', target: "_blank", rel: "noreferrer", style: badgeLinkStyles, tabIndex: isOpen ? 0 : -1, "aria-label": "Localized by Lovalingo", title: "Localized by Lovalingo" },
210
210
  React.createElement("span", { style: {
211
211
  width: '16px',
@@ -26,6 +26,7 @@ export type BootstrapResponse = {
26
26
  normalized_path?: string;
27
27
  routing_strategy?: string;
28
28
  loading_bg_color?: string | null;
29
+ branding_enabled?: boolean;
29
30
  seoEnabled?: boolean;
30
31
  entitlements?: ProjectEntitlements;
31
32
  alternates?: {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.3.0";
1
+ export declare const VERSION = "0.3.2";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.3.0";
1
+ export const VERSION = "0.3.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
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",