@lovalingo/lovalingo 0.5.3 → 0.5.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.
package/LICENSE CHANGED
@@ -1,9 +1,9 @@
1
1
  LOVALINGO COMMERCIAL LICENSE
2
2
 
3
- Copyright (c) 2025 Mertens Advies. All rights reserved.
3
+ Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
4
4
 
5
5
  NOTICE: This software and associated documentation files (the "Software") are the
6
- proprietary and confidential information of Mertens Advies.
6
+ proprietary and confidential information of Lovalingo Swiss.
7
7
 
8
8
  The Software is licensed, not sold. This license grants you the following rights:
9
9
 
@@ -74,17 +74,17 @@ FOR END CLIENTS:
74
74
  4. API KEY REQUIREMENT
75
75
  ====================================================================================
76
76
 
77
- Production use of the Software requires a valid API key obtained from Mertens Advies.
77
+ Production use of the Software requires a valid API key obtained from Lovalingo Swiss.
78
78
  Usage is subject to the terms of service associated with your API key subscription.
79
79
 
80
80
  ====================================================================================
81
81
  5. INTELLECTUAL PROPERTY
82
82
  ====================================================================================
83
83
 
84
- This Software is the exclusive intellectual property of Mertens Advies.
84
+ This Software is the exclusive intellectual property of Lovalingo Swiss.
85
85
  All rights, title, and interest in and to the Software, including all
86
86
  intellectual property rights, patents, trademarks, copyrights, and trade secrets,
87
- remain the sole property of Mertens Advies.
87
+ remain the sole property of Lovalingo Swiss.
88
88
 
89
89
  ====================================================================================
90
90
  6. NOT OPEN SOURCE
@@ -133,16 +133,16 @@ SUCH DAMAGE.
133
133
  ====================================================================================
134
134
 
135
135
  This license shall be governed by and construed in accordance with the laws of
136
- the jurisdiction in which Mertens Advies operates, without regard to its conflict
136
+ the jurisdiction in which Lovalingo Swiss operates, without regard to its conflict
137
137
  of law provisions.
138
138
 
139
139
  ====================================================================================
140
140
 
141
141
  For licensing inquiries, enterprise licensing, or questions about permitted use,
142
- please contact Mertens Advies.
142
+ please contact Lovalingo Swiss.
143
143
 
144
144
  MERTENS ADVIES
145
145
  Lovalingo Translation Platform
146
146
  © 2025 All Rights Reserved
147
147
 
148
- Website: [Contact Mertens Advies for licensing information]
148
+ Website: [Contact Lovalingo Swiss for licensing information]
package/README.md CHANGED
@@ -349,7 +349,7 @@ export async function GET() {
349
349
 
350
350
  **COMMERCIAL LICENSE - NOT OPEN SOURCE**
351
351
 
352
- Copyright (c) 2025 Mertens Advies. All rights reserved.
352
+ Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
353
353
 
354
354
  ### For Agencies & Developers
355
355
 
@@ -374,6 +374,6 @@ applications containing Lovalingo, but may not modify, redistribute, or extract
374
374
 
375
375
  This software is licensed under the **Lovalingo Commercial License**.
376
376
  This is NOT open source software. All intellectual property rights remain the
377
- exclusive property of Mertens Advies.
377
+ exclusive property of Lovalingo Swiss.
378
378
 
379
379
  See LICENSE file for complete terms and conditions.
@@ -114,6 +114,7 @@ navigateRef, // For path mode routing
114
114
  const prehideStateRef = useRef({
115
115
  active: false,
116
116
  timeoutId: null,
117
+ startedAtMs: null,
117
118
  prevHtmlVisibility: "",
118
119
  prevBodyVisibility: "",
119
120
  prevHtmlBg: "",
@@ -198,6 +199,27 @@ navigateRef, // For path mode routing
198
199
  pageviewFingerprintTimeoutRef.current = window.setTimeout(trySendFingerprint, 800);
199
200
  pageviewFingerprintRetryTimeoutRef.current = window.setTimeout(trySendFingerprint, 2000);
200
201
  }, []);
202
+ const forceDisablePrehide = useCallback(() => {
203
+ if (typeof document === "undefined")
204
+ return;
205
+ const html = document.documentElement;
206
+ const body = document.body;
207
+ if (!html || !body)
208
+ return;
209
+ const state = prehideStateRef.current;
210
+ if (state.timeoutId != null) {
211
+ window.clearTimeout(state.timeoutId);
212
+ state.timeoutId = null;
213
+ }
214
+ if (!state.active)
215
+ return;
216
+ state.active = false;
217
+ state.startedAtMs = null;
218
+ html.style.visibility = state.prevHtmlVisibility;
219
+ body.style.visibility = state.prevBodyVisibility;
220
+ html.style.backgroundColor = state.prevHtmlBg;
221
+ body.style.backgroundColor = state.prevBodyBg;
222
+ }, []);
201
223
  const enablePrehide = useCallback((bgColor) => {
202
224
  if (typeof document === "undefined")
203
225
  return;
@@ -206,8 +228,13 @@ navigateRef, // For path mode routing
206
228
  if (!html || !body)
207
229
  return;
208
230
  const state = prehideStateRef.current;
231
+ // Why: avoid "perma-hidden" pages when repeated navigation/errors keep prehide active; always hard-stop after a few seconds.
232
+ if (state.active && state.startedAtMs != null && Date.now() - state.startedAtMs > PREHIDE_FAILSAFE_MS * 3) {
233
+ forceDisablePrehide();
234
+ }
209
235
  if (!state.active) {
210
236
  state.active = true;
237
+ state.startedAtMs = Date.now();
211
238
  state.prevHtmlVisibility = html.style.visibility || "";
212
239
  state.prevBodyVisibility = body.style.visibility || "";
213
240
  state.prevHtmlBg = html.style.backgroundColor || "";
@@ -223,28 +250,9 @@ navigateRef, // For path mode routing
223
250
  return;
224
251
  }
225
252
  // Why: avoid a "perma-hide" when navigation events repeatedly re-trigger prehide and keep extending the timeout.
226
- state.timeoutId = window.setTimeout(() => disablePrehide(), PREHIDE_FAILSAFE_MS);
227
- }, []);
228
- const disablePrehide = useCallback(() => {
229
- if (typeof document === "undefined")
230
- return;
231
- const html = document.documentElement;
232
- const body = document.body;
233
- if (!html || !body)
234
- return;
235
- const state = prehideStateRef.current;
236
- if (state.timeoutId != null) {
237
- window.clearTimeout(state.timeoutId);
238
- state.timeoutId = null;
239
- }
240
- if (!state.active)
241
- return;
242
- state.active = false;
243
- html.style.visibility = state.prevHtmlVisibility;
244
- body.style.visibility = state.prevBodyVisibility;
245
- html.style.backgroundColor = state.prevHtmlBg;
246
- body.style.backgroundColor = state.prevBodyBg;
247
- }, []);
253
+ state.timeoutId = window.setTimeout(() => forceDisablePrehide(), PREHIDE_FAILSAFE_MS);
254
+ }, [forceDisablePrehide]);
255
+ const disablePrehide = forceDisablePrehide;
248
256
  const buildCriticalCacheKey = useCallback((targetLocale, normalizedPath) => {
249
257
  const key = `${resolvedApiKey || "anonymous"}:${targetLocale}:${normalizedPath || "/"}`;
250
258
  return `${CRITICAL_CACHE_PREFIX}:${hashContent(key)}`;
@@ -754,6 +762,7 @@ navigateRef, // For path mode routing
754
762
  }
755
763
  }, [
756
764
  applySeoBundle,
765
+ allLocales,
757
766
  autoApplyRules,
758
767
  defaultLocale,
759
768
  disablePrehide,
@@ -763,6 +772,8 @@ navigateRef, // For path mode routing
763
772
  isSeoActive,
764
773
  mode,
765
774
  readCriticalCache,
775
+ routing,
776
+ routingConfig.nonLocalizedPaths,
766
777
  setCachedLoadingBgColor,
767
778
  toTranslations,
768
779
  writeCriticalCache,
@@ -890,7 +901,7 @@ navigateRef, // For path mode routing
890
901
  })().finally(() => {
891
902
  isInternalNavigationRef.current = false;
892
903
  });
893
- }, [allLocales, locale, routing, loadData, navigateRef, routingConfig.nonLocalizedPaths]);
904
+ }, [allLocales, defaultLocale, locale, routing, loadData, navigateRef, routingConfig.nonLocalizedPaths]);
894
905
  // No string-level "misses" from the client: the pipeline (Browser Rendering) is source-of-truth for string discovery.
895
906
  // Initialize
896
907
  useEffect(() => {
@@ -29,7 +29,9 @@ export function LangLink({ to, ...props }) {
29
29
  ? (() => {
30
30
  const trimmed = (to || '').toString().trim();
31
31
  const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
32
- return isNonLocalizedPath(normalized, routing.nonLocalizedPaths) ? normalized : `/${lang}${normalized}`;
32
+ return isNonLocalizedPath(normalized, routing.nonLocalizedPaths)
33
+ ? normalized
34
+ : `/${lang}${normalized}`;
33
35
  })()
34
36
  : to;
35
37
  return React.createElement(Link, { ...props, to: langTo });
@@ -1,8 +1,8 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { BrowserRouter, Routes, Route, Outlet, useLocation, useNavigate } from 'react-router-dom';
2
+ import { BrowserRouter, Routes, Route, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
3
3
  import { LangContext } from '../context/LangContext';
4
4
  import { LangRoutingContext } from '../context/LangRoutingContext';
5
- import { isNonLocalizedPath, parseBootstrapNonLocalizedPaths } from '../utils/nonLocalizedPaths';
5
+ import { isNonLocalizedPath, parseBootstrapInactivePages, parseBootstrapNonLocalizedPaths } from '../utils/nonLocalizedPaths';
6
6
  import { logDebug } from '../utils/logger';
7
7
  /**
8
8
  * NavigateExporter - Internal component that exports navigate function via ref
@@ -19,20 +19,17 @@ function NavigateExporter({ navigateRef }) {
19
19
  /**
20
20
  * LangGuard - Internal component that validates language and provides it to children
21
21
  */
22
- function LangGuard({ lang, nonLocalizedPaths, }) {
22
+ function LangGuard({ lang, nonLocalizedPaths, defaultLang, }) {
23
23
  const location = useLocation();
24
- const navigate = useNavigate();
25
24
  // If the URL is language-prefixed but the underlying route is non-localized (e.g. robots/sitemap),
26
25
  // redirect to the canonical non-localized path.
27
26
  const prefix = `/${lang}`;
28
27
  const restPath = location.pathname.startsWith(prefix) ? location.pathname.slice(prefix.length) || '/' : location.pathname;
29
- const shouldDePrefix = isNonLocalizedPath(restPath, nonLocalizedPaths);
30
- useEffect(() => {
31
- if (!shouldDePrefix)
32
- return;
28
+ // Why: only explicit non-localized rules may strip the locale prefix; inactive pages must never affect client routing.
29
+ if (isNonLocalizedPath(restPath, nonLocalizedPaths)) {
33
30
  const nextPath = `${restPath}${location.search}${location.hash}`;
34
- navigate(nextPath, { replace: true });
35
- }, [location.hash, location.search, navigate, restPath, shouldDePrefix]);
31
+ return React.createElement(Navigate, { to: nextPath, replace: true });
32
+ }
36
33
  // Valid language - render children (user's routes)
37
34
  return (React.createElement(LangContext.Provider, { value: lang },
38
35
  React.createElement(Outlet, { context: { lang } })));
@@ -95,6 +92,7 @@ export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase,
95
92
  ? apiBase.trim()
96
93
  : "https://cdn.lovalingo.com");
97
94
  const nonLocalizedStorageKey = useMemo(() => `Lovalingo_non_localized_paths:${resolvedApiKey || "anonymous"}`, [resolvedApiKey]);
95
+ const inactivePagesStorageKey = useMemo(() => `Lovalingo_inactive_pages:${resolvedApiKey || "anonymous"}`, [resolvedApiKey]);
98
96
  const [nonLocalizedPaths, setNonLocalizedPaths] = useState(() => {
99
97
  if (typeof window === "undefined")
100
98
  return [];
@@ -111,12 +109,28 @@ export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase,
111
109
  return [];
112
110
  }
113
111
  });
112
+ const [inactivePages, setInactivePages] = useState(() => {
113
+ if (typeof window === "undefined")
114
+ return [];
115
+ if (!resolvedApiKey)
116
+ return [];
117
+ try {
118
+ const raw = localStorage.getItem(inactivePagesStorageKey);
119
+ if (!raw)
120
+ return [];
121
+ const parsed = JSON.parse(raw);
122
+ return parseBootstrapInactivePages(parsed);
123
+ }
124
+ catch {
125
+ return [];
126
+ }
127
+ });
114
128
  const [routingStatus, setRoutingStatus] = useState(() => {
115
129
  if (!resolvedApiKey)
116
130
  return "unknown";
117
- return nonLocalizedPaths.length > 0 ? "ready" : "loading";
131
+ return nonLocalizedPaths.length > 0 || inactivePages.length > 0 ? "ready" : "loading";
118
132
  });
119
- const fetchNonLocalizedPaths = useCallback(async () => {
133
+ const fetchRoutingConfig = useCallback(async () => {
120
134
  if (typeof window === "undefined")
121
135
  return;
122
136
  if (!resolvedApiKey)
@@ -129,7 +143,10 @@ export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase,
129
143
  throw new Error(`bootstrap HTTP ${resolvedResponse.status}`);
130
144
  const data = (await resolvedResponse.json());
131
145
  const record = (data || {});
132
- return parseBootstrapNonLocalizedPaths(record["non_localized_paths"]);
146
+ return {
147
+ nonLocalizedPaths: parseBootstrapNonLocalizedPaths(record["non_localized_paths"]),
148
+ inactivePages: parseBootstrapInactivePages(record["inactive_pages"]),
149
+ };
133
150
  }, [defaultLang, resolvedApiBase, resolvedApiKey]);
134
151
  useEffect(() => {
135
152
  let cancelled = false;
@@ -138,13 +155,15 @@ export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase,
138
155
  return;
139
156
  setRoutingStatus((prev) => (prev === "ready" ? prev : "loading"));
140
157
  try {
141
- const next = await fetchNonLocalizedPaths();
158
+ const next = await fetchRoutingConfig();
142
159
  if (cancelled || !next)
143
160
  return;
144
- setNonLocalizedPaths(next);
161
+ setNonLocalizedPaths(next.nonLocalizedPaths);
162
+ setInactivePages(next.inactivePages);
145
163
  setRoutingStatus("ready");
146
164
  try {
147
- localStorage.setItem(nonLocalizedStorageKey, JSON.stringify(next));
165
+ localStorage.setItem(nonLocalizedStorageKey, JSON.stringify(next.nonLocalizedPaths));
166
+ localStorage.setItem(inactivePagesStorageKey, JSON.stringify(next.inactivePages));
148
167
  }
149
168
  catch {
150
169
  // ignore
@@ -154,18 +173,18 @@ export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase,
154
173
  if (cancelled)
155
174
  return;
156
175
  setRoutingStatus("error");
157
- logDebug("[Lovalingo] Failed to fetch non-localized paths:", err);
176
+ logDebug("[Lovalingo] Failed to fetch routing config:", err);
158
177
  }
159
178
  })();
160
179
  return () => {
161
180
  cancelled = true;
162
181
  };
163
- }, [fetchNonLocalizedPaths, nonLocalizedStorageKey, resolvedApiKey]);
182
+ }, [fetchRoutingConfig, inactivePagesStorageKey, nonLocalizedStorageKey, resolvedApiKey]);
164
183
  return (React.createElement(BrowserRouter, null,
165
184
  React.createElement(NavigateExporter, { navigateRef: navigateRef }),
166
- React.createElement(LangRoutingContext.Provider, { value: { nonLocalizedPaths, status: routingStatus } },
185
+ React.createElement(LangRoutingContext.Provider, { value: { defaultLang, nonLocalizedPaths, inactivePages, status: routingStatus } },
167
186
  React.createElement(Routes, null,
168
- langs.map((lang) => (React.createElement(Route, { key: lang, path: `${lang}/*`, element: React.createElement(LangGuard, { lang: lang, nonLocalizedPaths: nonLocalizedPaths }) },
187
+ langs.map((lang) => (React.createElement(Route, { key: lang, path: `${lang}/*`, element: React.createElement(LangGuard, { lang: lang, nonLocalizedPaths: nonLocalizedPaths, defaultLang: defaultLang }) },
169
188
  React.createElement(Route, { index: true, element: React.createElement(React.Fragment, null, children) }),
170
189
  React.createElement(Route, { path: "*", element: React.createElement(React.Fragment, null, children) })))),
171
190
  React.createElement(Route, { path: "*", element: React.createElement(RedirectToDefaultLang, { defaultLang: defaultLang, nonLocalizedPaths: nonLocalizedPaths, routingStatus: routingStatus }, children) })))));
@@ -210,16 +210,18 @@ export const LanguageSwitcher = ({ locales, currentLocale, onLocaleChange, posit
210
210
  React.createElement("span", { style: {
211
211
  width: '16px',
212
212
  height: '16px',
213
- borderRadius: '5px',
214
- background: '#6BD63D',
213
+ borderRadius: '999px',
214
+ overflow: 'hidden',
215
+ background: '#DA2576',
215
216
  display: 'inline-flex',
216
217
  alignItems: 'center',
217
218
  justifyContent: 'center',
218
219
  boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.25)',
219
220
  flexShrink: 0,
220
221
  } },
221
- React.createElement("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true" },
222
- React.createElement("path", { d: "M9 5a2 2 0 0 1 2 2v10h8a2 2 0 1 1 0 4H9a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2Z", fill: "#0D0D0D" }))),
222
+ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 512 512", fill: "none", "aria-hidden": "true" },
223
+ React.createElement("path", { d: "M215.657 429.489C270.707 422.73 289.644 339.333 278 244.5C266.356 149.667 228.54 79.3089 173.49 86.0682C118.44 92.8275 83.253 175.184 94.8971 270.017C106.541 364.85 160.607 436.248 215.657 429.489Z", stroke: "#FFFFFF", strokeWidth: "44", strokeLinecap: "round", strokeLinejoin: "round" }),
224
+ React.createElement("path", { d: "M168.218 408.885C188.661 447.333 263.959 447.277 336.399 408.759C408.84 370.242 450.992 307.849 430.549 269.401C410.106 230.953 334.808 231.009 262.368 269.526C189.927 308.044 147.775 370.437 168.218 408.885Z", stroke: "#FFFFFF", strokeWidth: "44", strokeLinecap: "round", strokeLinejoin: "round" }))),
223
225
  React.createElement("span", null,
224
226
  branding.label || 'Localized by',
225
227
  " ",
@@ -1,6 +1,8 @@
1
1
  import type { NonLocalizedPathRule } from "../utils/nonLocalizedPaths";
2
2
  export type LangRoutingContextValue = {
3
+ defaultLang: string;
3
4
  nonLocalizedPaths: NonLocalizedPathRule[];
5
+ inactivePages: string[];
4
6
  status: "unknown" | "loading" | "ready" | "error";
5
7
  };
6
8
  export declare const LangRoutingContext: import("react").Context<LangRoutingContextValue>;
@@ -1,5 +1,7 @@
1
1
  import { createContext } from "react";
2
2
  export const LangRoutingContext = createContext({
3
+ defaultLang: "",
3
4
  nonLocalizedPaths: [],
5
+ inactivePages: [],
4
6
  status: "unknown",
5
7
  });
@@ -32,7 +32,9 @@ export function useLangNavigate() {
32
32
  if (!trimmed)
33
33
  return;
34
34
  const normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
35
- const fullPath = isNonLocalizedPath(normalized, routing.nonLocalizedPaths) ? normalized : `/${lang}${normalized}`;
35
+ const fullPath = isNonLocalizedPath(normalized, routing.nonLocalizedPaths)
36
+ ? normalized
37
+ : `/${lang}${normalized}`;
36
38
  navigate(fullPath, options);
37
39
  }, [lang, navigate, routing.nonLocalizedPaths]);
38
40
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * @Lovalingo/Lovalingo - Proprietary Translation Library
3
3
  *
4
- * Copyright (c) 2025 Mertens Advies. All rights reserved.
4
+ * Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
5
5
  *
6
- * This software is the intellectual property of Mertens Advies.
6
+ * This software is the intellectual property of Lovalingo Swiss.
7
7
  * NOT OPEN SOURCE - All rights reserved.
8
8
  *
9
9
  * Unauthorized copying, modification, distribution, or use of this software
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * @Lovalingo/Lovalingo - Proprietary Translation Library
3
3
  *
4
- * Copyright (c) 2025 Mertens Advies. All rights reserved.
4
+ * Copyright (c) 2025 Lovalingo Swiss. All rights reserved.
5
5
  *
6
- * This software is the intellectual property of Mertens Advies.
6
+ * This software is the intellectual property of Lovalingo Swiss.
7
7
  * NOT OPEN SOURCE - All rights reserved.
8
8
  *
9
9
  * Unauthorized copying, modification, distribution, or use of this software
@@ -30,6 +30,10 @@ export type BootstrapResponse = {
30
30
  match_type?: string;
31
31
  updated_at?: string | null;
32
32
  }>;
33
+ inactive_pages?: Array<{
34
+ page_path?: string;
35
+ updated_at?: string | null;
36
+ }>;
33
37
  loading_bg_color?: string | null;
34
38
  branding_enabled?: boolean;
35
39
  seoEnabled?: boolean;
@@ -7,3 +7,6 @@ export declare function matchesNonLocalizedRules(pathname: string, rules: NonLoc
7
7
  export declare function isNonLocalizedPath(pathname: string, rules: NonLocalizedPathRule[]): boolean;
8
8
  export declare function stripLocalePrefix(pathname: string, locales: string[]): string;
9
9
  export declare function parseBootstrapNonLocalizedPaths(value: unknown): NonLocalizedPathRule[];
10
+ export declare function parseBootstrapInactivePages(value: unknown): string[];
11
+ export declare function matchesRouteTemplate(pathname: string, template: string): boolean;
12
+ export declare function isInactivePagePath(pathname: string, inactivePages: string[]): boolean;
@@ -26,7 +26,12 @@ export function matchesNonLocalizedRules(pathname, rules) {
26
26
  continue;
27
27
  }
28
28
  if (matchType === "prefix") {
29
- if (input.startsWith(pattern))
29
+ if (input === pattern)
30
+ return true;
31
+ const normalizedPrefix = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
32
+ if (!normalizedPrefix || normalizedPrefix === "/")
33
+ return true;
34
+ if (input.startsWith(`${normalizedPrefix}/`))
30
35
  return true;
31
36
  continue;
32
37
  }
@@ -76,3 +81,56 @@ export function parseBootstrapNonLocalizedPaths(value) {
76
81
  }
77
82
  return out;
78
83
  }
84
+ export function parseBootstrapInactivePages(value) {
85
+ if (!Array.isArray(value))
86
+ return [];
87
+ const out = [];
88
+ for (const row of value) {
89
+ if (typeof row === "string") {
90
+ const pagePath = row.trim();
91
+ if (pagePath && pagePath.startsWith("/"))
92
+ out.push(pagePath);
93
+ continue;
94
+ }
95
+ if (!row || typeof row !== "object")
96
+ continue;
97
+ const record = row;
98
+ const pagePath = typeof record.page_path === "string" ? record.page_path.trim() : "";
99
+ if (!pagePath || !pagePath.startsWith("/"))
100
+ continue;
101
+ out.push(pagePath);
102
+ }
103
+ return out;
104
+ }
105
+ function escapeRegexLiteral(value) {
106
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
107
+ }
108
+ export function matchesRouteTemplate(pathname, template) {
109
+ const input = (pathname || "").toString();
110
+ const pattern = (template || "").toString();
111
+ if (!input.startsWith("/") || !pattern.startsWith("/"))
112
+ return false;
113
+ if (input === pattern)
114
+ return true;
115
+ // Convert "/dashboard/projects/:id/pages" or "/use-cases/[slug]" into a safe regex.
116
+ const parts = pattern.split("/").filter(Boolean);
117
+ const regexParts = parts.map((part) => {
118
+ if (part === "*")
119
+ return ".*";
120
+ if (part.startsWith(":"))
121
+ return "[^/]+";
122
+ if (part.startsWith("[") && part.endsWith("]"))
123
+ return "[^/]+";
124
+ return escapeRegexLiteral(part);
125
+ });
126
+ const regex = new RegExp(`^/${regexParts.join("/")}$`);
127
+ return regex.test(input);
128
+ }
129
+ export function isInactivePagePath(pathname, inactivePages) {
130
+ const input = (pathname || "").toString();
131
+ if (!input.startsWith("/"))
132
+ return false;
133
+ if (!Array.isArray(inactivePages) || inactivePages.length === 0)
134
+ return false;
135
+ return inactivePages.some((pattern) => matchesRouteTemplate(input, pattern));
136
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.3";
1
+ export declare const VERSION = "0.5.4";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.5.3";
1
+ export const VERSION = "0.5.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.3",
3
+ "version": "0.5.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",
@@ -28,7 +28,7 @@
28
28
  "lovable",
29
29
  "vibe-coded"
30
30
  ],
31
- "author": "Mertens Advies",
31
+ "author": "Lovalingo Swiss",
32
32
  "license": "UNLICENSED",
33
33
  "private": false,
34
34
  "peerDependencies": {