@lovalingo/lovalingo 0.5.29 → 0.6.0

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.
Files changed (148) hide show
  1. package/README.md +34 -0
  2. package/dist/chunk-2FZR2AKF.mjs +88 -0
  3. package/dist/chunk-7D5LBV45.mjs +46 -0
  4. package/dist/chunk-CJOSN7RA.mjs +90 -0
  5. package/dist/chunk-VAHA2TOX.mjs +3440 -0
  6. package/dist/chunk-ZMRCSUM7.mjs +26 -0
  7. package/dist/chunk-ZVYKEEUF.mjs +220 -0
  8. package/dist/core.d.mts +131 -0
  9. package/dist/core.d.ts +131 -0
  10. package/dist/core.js +3561 -0
  11. package/dist/core.mjs +19 -0
  12. package/dist/index.d.mts +5 -0
  13. package/dist/index.d.ts +5 -25
  14. package/dist/index.js +3885 -28
  15. package/dist/index.mjs +33 -0
  16. package/dist/react-router.d.mts +101 -0
  17. package/dist/react-router.d.ts +101 -0
  18. package/dist/react-router.js +353 -0
  19. package/dist/react-router.mjs +14 -0
  20. package/dist/tanstack-router.d.mts +22 -0
  21. package/dist/tanstack-router.d.ts +22 -0
  22. package/dist/tanstack-router.js +162 -0
  23. package/dist/tanstack-router.mjs +8 -0
  24. package/package.json +34 -3
  25. package/dist/__tests__/languageFlags.test.d.ts +0 -1
  26. package/dist/__tests__/languageFlags.test.js +0 -42
  27. package/dist/__tests__/mergeEntitlements.test.d.ts +0 -1
  28. package/dist/__tests__/mergeEntitlements.test.js +0 -27
  29. package/dist/components/AixsterProvider.d.ts +0 -1
  30. package/dist/components/AixsterProvider.js +0 -1
  31. package/dist/components/LangLink.d.ts +0 -20
  32. package/dist/components/LangLink.js +0 -38
  33. package/dist/components/LangRouter.d.ts +0 -37
  34. package/dist/components/LangRouter.js +0 -191
  35. package/dist/components/LanguageSwitcher.d.ts +0 -17
  36. package/dist/components/LanguageSwitcher.js +0 -257
  37. package/dist/components/LovalingoProvider.d.ts +0 -10
  38. package/dist/components/LovalingoProvider.js +0 -413
  39. package/dist/components/NavigationOverlay.d.ts +0 -6
  40. package/dist/components/NavigationOverlay.js +0 -22
  41. package/dist/components/provider/__tests__/seoUtils.test.d.ts +0 -1
  42. package/dist/components/provider/__tests__/seoUtils.test.js +0 -13
  43. package/dist/components/provider/editModeUtils.d.ts +0 -6
  44. package/dist/components/provider/editModeUtils.js +0 -59
  45. package/dist/components/provider/localeUtils.d.ts +0 -8
  46. package/dist/components/provider/localeUtils.js +0 -46
  47. package/dist/components/provider/providerConstants.d.ts +0 -12
  48. package/dist/components/provider/providerConstants.js +0 -11
  49. package/dist/components/provider/seoUtils.d.ts +0 -8
  50. package/dist/components/provider/seoUtils.js +0 -118
  51. package/dist/components/provider/useEditModeOverlay.d.ts +0 -7
  52. package/dist/components/provider/useEditModeOverlay.js +0 -134
  53. package/dist/components/provider/useHistoryNavigationPatch.d.ts +0 -3
  54. package/dist/components/provider/useHistoryNavigationPatch.js +0 -47
  55. package/dist/components/provider/useProviderCache.d.ts +0 -12
  56. package/dist/components/provider/useProviderCache.js +0 -82
  57. package/dist/context/AixsterContext.d.ts +0 -3
  58. package/dist/context/AixsterContext.js +0 -2
  59. package/dist/context/LangContext.d.ts +0 -1
  60. package/dist/context/LangContext.js +0 -2
  61. package/dist/context/LangRoutingContext.d.ts +0 -8
  62. package/dist/context/LangRoutingContext.js +0 -7
  63. package/dist/context/LovalingoContext.d.ts +0 -1
  64. package/dist/context/LovalingoContext.js +0 -1
  65. package/dist/hooks/provider/useBundleLoading.d.ts +0 -33
  66. package/dist/hooks/provider/useBundleLoading.js +0 -380
  67. package/dist/hooks/provider/useDomRules.d.ts +0 -15
  68. package/dist/hooks/provider/useDomRules.js +0 -38
  69. package/dist/hooks/provider/useLinkAutoPrefix.d.ts +0 -12
  70. package/dist/hooks/provider/useLinkAutoPrefix.js +0 -146
  71. package/dist/hooks/provider/useNavigationPrefetch.d.ts +0 -12
  72. package/dist/hooks/provider/useNavigationPrefetch.js +0 -82
  73. package/dist/hooks/provider/usePageviewTracking.d.ts +0 -10
  74. package/dist/hooks/provider/usePageviewTracking.js +0 -44
  75. package/dist/hooks/provider/usePrehide.d.ts +0 -5
  76. package/dist/hooks/provider/usePrehide.js +0 -72
  77. package/dist/hooks/provider/useSitemapLinkTag.d.ts +0 -7
  78. package/dist/hooks/provider/useSitemapLinkTag.js +0 -28
  79. package/dist/hooks/provider/useStringMissReporting.d.ts +0 -14
  80. package/dist/hooks/provider/useStringMissReporting.js +0 -155
  81. package/dist/hooks/useAixster.d.ts +0 -6
  82. package/dist/hooks/useAixster.js +0 -14
  83. package/dist/hooks/useAixsterEdit.d.ts +0 -5
  84. package/dist/hooks/useAixsterEdit.js +0 -13
  85. package/dist/hooks/useAixsterTranslate.d.ts +0 -4
  86. package/dist/hooks/useAixsterTranslate.js +0 -12
  87. package/dist/hooks/useLang.d.ts +0 -16
  88. package/dist/hooks/useLang.js +0 -23
  89. package/dist/hooks/useLangNavigate.d.ts +0 -24
  90. package/dist/hooks/useLangNavigate.js +0 -40
  91. package/dist/hooks/useLovalingo.d.ts +0 -1
  92. package/dist/hooks/useLovalingo.js +0 -1
  93. package/dist/hooks/useLovalingoEdit.d.ts +0 -1
  94. package/dist/hooks/useLovalingoEdit.js +0 -1
  95. package/dist/hooks/useLovalingoTranslate.d.ts +0 -1
  96. package/dist/hooks/useLovalingoTranslate.js +0 -1
  97. package/dist/types.d.ts +0 -76
  98. package/dist/types.js +0 -1
  99. package/dist/utils/api.d.ts +0 -42
  100. package/dist/utils/api.js +0 -395
  101. package/dist/utils/apiTypes.d.ts +0 -78
  102. package/dist/utils/apiTypes.js +0 -1
  103. package/dist/utils/apiUtils.d.ts +0 -4
  104. package/dist/utils/apiUtils.js +0 -54
  105. package/dist/utils/domRules.d.ts +0 -2
  106. package/dist/utils/domRules.js +0 -150
  107. package/dist/utils/hash.d.ts +0 -9
  108. package/dist/utils/hash.js +0 -27
  109. package/dist/utils/languageFlags.d.ts +0 -7
  110. package/dist/utils/languageFlags.js +0 -90
  111. package/dist/utils/logger.d.ts +0 -3
  112. package/dist/utils/logger.js +0 -40
  113. package/dist/utils/markerEngine.d.ts +0 -12
  114. package/dist/utils/markerEngine.js +0 -109
  115. package/dist/utils/markerEngineApply.d.ts +0 -3
  116. package/dist/utils/markerEngineApply.js +0 -136
  117. package/dist/utils/markerEngineConstants.d.ts +0 -10
  118. package/dist/utils/markerEngineConstants.js +0 -12
  119. package/dist/utils/markerEngineCritical.d.ts +0 -2
  120. package/dist/utils/markerEngineCritical.js +0 -98
  121. package/dist/utils/markerEngineDomUtils.d.ts +0 -8
  122. package/dist/utils/markerEngineDomUtils.js +0 -74
  123. package/dist/utils/markerEngineFilters.d.ts +0 -2
  124. package/dist/utils/markerEngineFilters.js +0 -26
  125. package/dist/utils/markerEngineMisses.d.ts +0 -5
  126. package/dist/utils/markerEngineMisses.js +0 -81
  127. package/dist/utils/markerEngineOriginals.d.ts +0 -5
  128. package/dist/utils/markerEngineOriginals.js +0 -29
  129. package/dist/utils/markerEngineScan.d.ts +0 -5
  130. package/dist/utils/markerEngineScan.js +0 -162
  131. package/dist/utils/markerEngineState.d.ts +0 -4
  132. package/dist/utils/markerEngineState.js +0 -14
  133. package/dist/utils/markerEngineStats.d.ts +0 -3
  134. package/dist/utils/markerEngineStats.js +0 -28
  135. package/dist/utils/markerEngineTranslations.d.ts +0 -3
  136. package/dist/utils/markerEngineTranslations.js +0 -49
  137. package/dist/utils/markerEngineTypes.d.ts +0 -62
  138. package/dist/utils/markerEngineTypes.js +0 -1
  139. package/dist/utils/markerEngineViewport.d.ts +0 -2
  140. package/dist/utils/markerEngineViewport.js +0 -27
  141. package/dist/utils/mergeEntitlements.d.ts +0 -2
  142. package/dist/utils/mergeEntitlements.js +0 -7
  143. package/dist/utils/nonLocalizedPaths.d.ts +0 -12
  144. package/dist/utils/nonLocalizedPaths.js +0 -136
  145. package/dist/utils/pathNormalizer.d.ts +0 -49
  146. package/dist/utils/pathNormalizer.js +0 -115
  147. package/dist/version.d.ts +0 -1
  148. package/dist/version.js +0 -1
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/tanstack-router.ts
21
+ var tanstack_router_exports = {};
22
+ __export(tanstack_router_exports, {
23
+ createLovalingoTanStackRewrite: () => createLovalingoTanStackRewrite
24
+ });
25
+ module.exports = __toCommonJS(tanstack_router_exports);
26
+
27
+ // src/components/provider/providerConstants.ts
28
+ var LOCALE_STORAGE_KEY = "Lovalingo_locale";
29
+
30
+ // src/utils/nonLocalizedPaths.ts
31
+ var GLOBAL_NON_LOCALIZED_APP_PATHS = /* @__PURE__ */ new Set(["/robots.txt", "/sitemap.xml"]);
32
+ function isGlobalNonLocalizedPath(pathname) {
33
+ const input = (pathname || "").toString();
34
+ if (!input.startsWith("/")) return false;
35
+ if (GLOBAL_NON_LOCALIZED_APP_PATHS.has(input)) return true;
36
+ if (input.startsWith("/.well-known/")) return true;
37
+ return /\.(?:png|jpg|jpeg|gif|svg|webp|avif|ico|css|js|map|json|xml|txt|pdf|zip|gz|br|woff2?|ttf|eot)$/i.test(input);
38
+ }
39
+ function matchesNonLocalizedRules(pathname, rules) {
40
+ const input = (pathname || "").toString();
41
+ if (!input.startsWith("/")) return false;
42
+ if (!Array.isArray(rules) || rules.length === 0) return false;
43
+ for (const rule of rules) {
44
+ const pattern = typeof rule?.pattern === "string" ? rule.pattern : "";
45
+ const matchType = rule?.match_type;
46
+ if (!pattern) continue;
47
+ if (matchType === "exact") {
48
+ if (input === pattern) return true;
49
+ continue;
50
+ }
51
+ if (matchType === "prefix") {
52
+ if (input === pattern) return true;
53
+ const normalizedPrefix = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
54
+ if (!normalizedPrefix || normalizedPrefix === "/") return true;
55
+ if (input.startsWith(`${normalizedPrefix}/`)) return true;
56
+ continue;
57
+ }
58
+ if (matchType === "regex") {
59
+ try {
60
+ if (new RegExp(pattern).test(input)) return true;
61
+ } catch {
62
+ }
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ function isNonLocalizedPath(pathname, rules) {
68
+ return isGlobalNonLocalizedPath(pathname) || matchesNonLocalizedRules(pathname, rules);
69
+ }
70
+ function stripLocalePrefix(pathname, locales) {
71
+ const input = (pathname || "").toString();
72
+ if (!input.startsWith("/")) return input;
73
+ const parts = input.split("/").filter(Boolean);
74
+ if (parts.length === 0) return "/";
75
+ const first = parts[0] || "";
76
+ if (!first || !Array.isArray(locales) || !locales.includes(first)) return input;
77
+ const rest = `/${parts.slice(1).join("/")}`;
78
+ return rest === "" ? "/" : rest;
79
+ }
80
+
81
+ // src/tanstack-router.ts
82
+ function normalizeLocale(value) {
83
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
84
+ }
85
+ function uniq(items) {
86
+ return Array.from(new Set(items));
87
+ }
88
+ function buildPathWithLocale(locale, pathname) {
89
+ const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
90
+ if (normalizedPath === "/") return `/${locale}`;
91
+ return `/${locale}${normalizedPath}`;
92
+ }
93
+ function cloneUrlWithPathname(url, pathname) {
94
+ const next = new URL(url.href);
95
+ next.pathname = pathname || "/";
96
+ return next;
97
+ }
98
+ function readStoredLocale(supportedLocales) {
99
+ if (typeof window === "undefined") return "";
100
+ try {
101
+ const stored = normalizeLocale(window.localStorage?.getItem(LOCALE_STORAGE_KEY));
102
+ return supportedLocales.includes(stored) ? stored : "";
103
+ } catch {
104
+ return "";
105
+ }
106
+ }
107
+ function detectLocaleFromPath(pathname, supportedLocales) {
108
+ const first = pathname.split("/").filter(Boolean)[0] || "";
109
+ const normalized = normalizeLocale(first);
110
+ return supportedLocales.includes(normalized) ? normalized : "";
111
+ }
112
+ function createLovalingoTanStackRewrite({
113
+ defaultLocale,
114
+ locales,
115
+ prefixDefaultLocale = true,
116
+ nonLocalizedPaths = [],
117
+ getLocale
118
+ }) {
119
+ const normalizedDefaultLocale = normalizeLocale(defaultLocale);
120
+ const supportedLocales = uniq([normalizedDefaultLocale, ...(locales || []).map(normalizeLocale)].filter(Boolean));
121
+ const fallbackLocale = normalizedDefaultLocale || supportedLocales[0] || "en";
122
+ let activeLocale = fallbackLocale;
123
+ const resolveOutputLocale = (pathname) => {
124
+ const explicitLocale = normalizeLocale(getLocale?.());
125
+ if (supportedLocales.includes(explicitLocale)) {
126
+ activeLocale = explicitLocale;
127
+ return explicitLocale;
128
+ }
129
+ const pathLocale = detectLocaleFromPath(pathname, supportedLocales);
130
+ if (pathLocale) {
131
+ activeLocale = pathLocale;
132
+ return pathLocale;
133
+ }
134
+ const storedLocale = readStoredLocale(supportedLocales);
135
+ if (storedLocale) {
136
+ activeLocale = storedLocale;
137
+ return storedLocale;
138
+ }
139
+ return supportedLocales.includes(activeLocale) ? activeLocale : fallbackLocale;
140
+ };
141
+ return {
142
+ input: ({ url }) => {
143
+ const pathLocale = detectLocaleFromPath(url.pathname, supportedLocales);
144
+ if (!pathLocale) return void 0;
145
+ activeLocale = pathLocale;
146
+ const stripped = stripLocalePrefix(url.pathname, supportedLocales);
147
+ return cloneUrlWithPathname(url, stripped || "/");
148
+ },
149
+ output: ({ url }) => {
150
+ const internalPath = stripLocalePrefix(url.pathname, supportedLocales);
151
+ if (isNonLocalizedPath(internalPath, nonLocalizedPaths)) return void 0;
152
+ if (detectLocaleFromPath(url.pathname, supportedLocales)) return void 0;
153
+ const locale = resolveOutputLocale(url.pathname);
154
+ if (!prefixDefaultLocale && locale === fallbackLocale) return void 0;
155
+ return cloneUrlWithPathname(url, buildPathWithLocale(locale, internalPath));
156
+ }
157
+ };
158
+ }
159
+ // Annotate the CommonJS export names for ESM import in node:
160
+ 0 && (module.exports = {
161
+ createLovalingoTanStackRewrite
162
+ });
@@ -0,0 +1,8 @@
1
+ import {
2
+ createLovalingoTanStackRewrite
3
+ } from "./chunk-CJOSN7RA.mjs";
4
+ import "./chunk-ZMRCSUM7.mjs";
5
+ import "./chunk-2FZR2AKF.mjs";
6
+ export {
7
+ createLovalingoTanStackRewrite
8
+ };
package/package.json CHANGED
@@ -1,9 +1,33 @@
1
1
  {
2
2
  "name": "@lovalingo/lovalingo",
3
- "version": "0.5.29",
3
+ "version": "0.6.0",
4
4
  "description": "React translation runtime with i18n routing, deterministic bundles + DOM rules, and zero-flash rendering.",
5
- "main": "dist/index.js",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./core": {
15
+ "types": "./dist/core.d.ts",
16
+ "import": "./dist/core.mjs",
17
+ "require": "./dist/core.js"
18
+ },
19
+ "./react-router": {
20
+ "types": "./dist/react-router.d.ts",
21
+ "import": "./dist/react-router.mjs",
22
+ "require": "./dist/react-router.js"
23
+ },
24
+ "./tanstack-router": {
25
+ "types": "./dist/tanstack-router.d.ts",
26
+ "import": "./dist/tanstack-router.mjs",
27
+ "require": "./dist/tanstack-router.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
7
31
  "publishConfig": {
8
32
  "access": "public"
9
33
  },
@@ -11,7 +35,7 @@
11
35
  "dist"
12
36
  ],
13
37
  "scripts": {
14
- "build": "rm -rf dist && tsc",
38
+ "build": "tsup src/index.tsx src/core.tsx src/react-router.tsx src/tanstack-router.ts --format esm,cjs --dts --external react,react-dom,react-router-dom --clean",
15
39
  "prepublishOnly": "npm run build"
16
40
  },
17
41
  "keywords": [
@@ -36,10 +60,17 @@
36
60
  "react-dom": "^18.0.0",
37
61
  "react-router-dom": "^6.30.3"
38
62
  },
63
+ "peerDependenciesMeta": {
64
+ "react-router-dom": {
65
+ "optional": true
66
+ }
67
+ },
39
68
  "devDependencies": {
40
69
  "@types/node": "^24.9.1",
41
70
  "@types/react": "^18.3.1",
42
71
  "@types/react-dom": "^18.3.0",
72
+ "react-router-dom": "^6.30.4",
73
+ "tsup": "^8.5.1",
43
74
  "typescript": "^5.0.0"
44
75
  },
45
76
  "repository": {
@@ -1 +0,0 @@
1
- export {};
@@ -1,42 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { countryCodeToFlagEmoji, normalizeLocaleCode, parseLocale, resolveLocaleFlag } from "../utils/languageFlags";
3
- describe("languageFlags", () => {
4
- it("normalizes locale codes with case and underscore variants", () => {
5
- expect(normalizeLocaleCode("TR")).toBe("tr");
6
- expect(normalizeLocaleCode("tr_TR")).toBe("tr-tr");
7
- expect(normalizeLocaleCode("tr-TR")).toBe("tr-tr");
8
- });
9
- it("parses language and region", () => {
10
- expect(parseLocale("en-CA")).toEqual({ language: "en", region: "CA" });
11
- expect(parseLocale("fr")).toEqual({ language: "fr", region: null });
12
- });
13
- it("returns consistent flag for TR variants", () => {
14
- expect(resolveLocaleFlag("TR")).toBe("🇹🇷");
15
- expect(resolveLocaleFlag("tr")).toBe("🇹🇷");
16
- expect(resolveLocaleFlag("tr-TR")).toBe("🇹🇷");
17
- expect(resolveLocaleFlag("tr_TR")).toBe("🇹🇷");
18
- });
19
- it("uses region flag when locale includes region", () => {
20
- expect(resolveLocaleFlag("en-CA")).toBe("🇨🇦");
21
- expect(resolveLocaleFlag("pt-BR")).toBe("🇧🇷");
22
- });
23
- it("uses language default region when locale has no region", () => {
24
- expect(resolveLocaleFlag("en")).toBe("🇬🇧");
25
- expect(resolveLocaleFlag("zh")).toBe("🇨🇳");
26
- expect(resolveLocaleFlag("pt")).toBe("🇵🇹");
27
- });
28
- it("falls back to globe for unknown or invalid locale", () => {
29
- expect(resolveLocaleFlag("zzz")).toBe("🌐");
30
- expect(resolveLocaleFlag("")).toBe("🌐");
31
- expect(resolveLocaleFlag(null)).toBe("🌐");
32
- });
33
- it("never falls back to white flag", () => {
34
- expect(resolveLocaleFlag("zzz")).not.toBe("🏳️");
35
- expect(resolveLocaleFlag("foo-bar-baz")).not.toBe("🏳️");
36
- });
37
- it("converts country code to emoji", () => {
38
- expect(countryCodeToFlagEmoji("ca")).toBe("🇨🇦");
39
- expect(countryCodeToFlagEmoji("PT")).toBe("🇵🇹");
40
- expect(countryCodeToFlagEmoji("ZZZ")).toBeNull();
41
- });
42
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,27 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { mergeEntitlementsSeoEnabled } from "../utils/mergeEntitlements";
3
- describe("mergeEntitlementsSeoEnabled", () => {
4
- it("merges seoEnabled when provided", () => {
5
- const entitlements = {
6
- tier: "starter",
7
- maxTargetLocales: 1,
8
- allowedTargetLocales: [],
9
- brandingRequired: true,
10
- hreflangEnabled: false,
11
- };
12
- const merged = mergeEntitlementsSeoEnabled(entitlements, false);
13
- expect(merged?.seoEnabled).toBe(false);
14
- });
15
- it("returns the same object when seoEnabled is not a boolean", () => {
16
- const entitlements = {
17
- tier: "startup",
18
- maxTargetLocales: 3,
19
- allowedTargetLocales: ["zh"],
20
- brandingRequired: false,
21
- hreflangEnabled: true,
22
- seoEnabled: true,
23
- };
24
- const merged = mergeEntitlementsSeoEnabled(entitlements, null);
25
- expect(merged).toBe(entitlements);
26
- });
27
- });
@@ -1 +0,0 @@
1
- export { LovalingoProvider } from "./LovalingoProvider";
@@ -1 +0,0 @@
1
- export { LovalingoProvider } from "./LovalingoProvider";
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import { LinkProps } from 'react-router-dom';
3
- /**
4
- * LangLink - Language-aware Link component
5
- *
6
- * Automatically prepends the current language to all navigation paths
7
- *
8
- * Usage:
9
- * ```tsx
10
- * // Instead of:
11
- * <Link to={`/${locale}/pricing`}>Pricing</Link>
12
- *
13
- * // Just write:
14
- * <LangLink to="pricing">Pricing</LangLink>
15
- * // Automatically becomes /en/pricing or /fr/pricing
16
- * ```
17
- *
18
- * All props from React Router's <Link> are supported
19
- */
20
- export declare function LangLink({ to, ...props }: LinkProps): React.JSX.Element;
@@ -1,38 +0,0 @@
1
- import React from 'react';
2
- import { Link } from 'react-router-dom';
3
- import { useLang } from '../hooks/useLang';
4
- import { useContext } from 'react';
5
- import { LangRoutingContext } from '../context/LangRoutingContext';
6
- import { isNonLocalizedPath } from '../utils/nonLocalizedPaths';
7
- /**
8
- * LangLink - Language-aware Link component
9
- *
10
- * Automatically prepends the current language to all navigation paths
11
- *
12
- * Usage:
13
- * ```tsx
14
- * // Instead of:
15
- * <Link to={`/${locale}/pricing`}>Pricing</Link>
16
- *
17
- * // Just write:
18
- * <LangLink to="pricing">Pricing</LangLink>
19
- * // Automatically becomes /en/pricing or /fr/pricing
20
- * ```
21
- *
22
- * All props from React Router's <Link> are supported
23
- */
24
- export function LangLink({ to, ...props }) {
25
- const lang = useLang();
26
- const routing = useContext(LangRoutingContext);
27
- // If 'to' is a string, prepend language
28
- const langTo = typeof to === 'string'
29
- ? (() => {
30
- const trimmed = (to || '').toString().trim();
31
- const normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
32
- return isNonLocalizedPath(normalized, routing.nonLocalizedPaths)
33
- ? normalized
34
- : `/${lang}${normalized}`;
35
- })()
36
- : to;
37
- return React.createElement(Link, { ...props, to: langTo });
38
- }
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
- interface LangRouterProps {
3
- children: React.ReactNode;
4
- defaultLang: string;
5
- langs: string[];
6
- navigateRef?: React.MutableRefObject<((path: string) => void) | undefined>;
7
- apiBase?: string;
8
- apiKey?: string;
9
- publicAnonKey?: string;
10
- }
11
- /**
12
- * LangRouter - Drop-in replacement for BrowserRouter that automatically handles language routing
13
- *
14
- * Usage:
15
- * ```tsx
16
- * const navigateRef = useRef();
17
- *
18
- * <LangRouter defaultLang="en" langs={['en', 'fr', 'de', 'ko']} navigateRef={navigateRef}>
19
- * <Routes>
20
- * <Route path="/" element={<Home />} />
21
- * <Route path="home" element={<Home />} />
22
- * <Route path="pricing" element={<Pricing />} />
23
- * </Routes>
24
- * </LangRouter>
25
- * ```
26
- *
27
- * This automatically creates routes:
28
- * - /en (root)
29
- * - /en/home
30
- * - /en/pricing
31
- * - /fr (root)
32
- * - /fr/home
33
- * - /fr/pricing
34
- * - etc.
35
- */
36
- export declare function LangRouter({ children, defaultLang, langs, navigateRef, apiBase, apiKey, publicAnonKey }: LangRouterProps): React.JSX.Element;
37
- export {};
@@ -1,191 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { BrowserRouter, Routes, Route, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
3
- import { LangContext } from '../context/LangContext';
4
- import { LangRoutingContext } from '../context/LangRoutingContext';
5
- import { isNonLocalizedPath, parseBootstrapInactivePages, parseBootstrapNonLocalizedPaths } from '../utils/nonLocalizedPaths';
6
- import { logDebug } from '../utils/logger';
7
- /**
8
- * NavigateExporter - Internal component that exports navigate function via ref
9
- */
10
- function NavigateExporter({ navigateRef }) {
11
- const navigate = useNavigate();
12
- useEffect(() => {
13
- if (navigateRef) {
14
- navigateRef.current = navigate;
15
- }
16
- }, [navigate, navigateRef]);
17
- return null;
18
- }
19
- /**
20
- * LangGuard - Internal component that validates language and provides it to children
21
- */
22
- function LangGuard({ lang, nonLocalizedPaths, defaultLang, }) {
23
- const location = useLocation();
24
- // If the URL is language-prefixed but the underlying route is non-localized (e.g. robots/sitemap),
25
- // redirect to the canonical non-localized path.
26
- const prefix = `/${lang}`;
27
- const restPath = location.pathname.startsWith(prefix) ? location.pathname.slice(prefix.length) || '/' : location.pathname;
28
- // Why: only explicit non-localized rules may strip the locale prefix; inactive pages must never affect client routing.
29
- if (isNonLocalizedPath(restPath, nonLocalizedPaths)) {
30
- const nextPath = `${restPath}${location.search}${location.hash}`;
31
- return React.createElement(Navigate, { to: nextPath, replace: true });
32
- }
33
- // Valid language - render children (user's routes)
34
- return (React.createElement(LangContext.Provider, { value: lang },
35
- React.createElement(Outlet, { context: { lang } })));
36
- }
37
- function RedirectToDefaultLang({ defaultLang, children, nonLocalizedPaths, routingStatus, }) {
38
- const location = useLocation();
39
- const navigate = useNavigate();
40
- const shouldSkip = isNonLocalizedPath(location.pathname, nonLocalizedPaths);
41
- useEffect(() => {
42
- if (shouldSkip)
43
- return;
44
- if (routingStatus === "loading")
45
- return;
46
- const nextPath = location.pathname === "/" || location.pathname === ""
47
- ? `/${defaultLang}${location.search}${location.hash}`
48
- : `/${defaultLang}${location.pathname}${location.search}${location.hash}`;
49
- const current = `${location.pathname}${location.search}${location.hash}`;
50
- if (nextPath === current)
51
- return;
52
- navigate(nextPath, { replace: true });
53
- }, [defaultLang, location.hash, location.pathname, location.search, navigate, routingStatus, shouldSkip]);
54
- return React.createElement(React.Fragment, null, children);
55
- }
56
- /**
57
- * LangRouter - Drop-in replacement for BrowserRouter that automatically handles language routing
58
- *
59
- * Usage:
60
- * ```tsx
61
- * const navigateRef = useRef();
62
- *
63
- * <LangRouter defaultLang="en" langs={['en', 'fr', 'de', 'ko']} navigateRef={navigateRef}>
64
- * <Routes>
65
- * <Route path="/" element={<Home />} />
66
- * <Route path="home" element={<Home />} />
67
- * <Route path="pricing" element={<Pricing />} />
68
- * </Routes>
69
- * </LangRouter>
70
- * ```
71
- *
72
- * This automatically creates routes:
73
- * - /en (root)
74
- * - /en/home
75
- * - /en/pricing
76
- * - /fr (root)
77
- * - /fr/home
78
- * - /fr/pricing
79
- * - etc.
80
- */
81
- export function LangRouter({ children, defaultLang, langs, navigateRef, apiBase, apiKey, publicAnonKey }) {
82
- const metaKey = typeof document !== "undefined"
83
- ? document.querySelector('meta[name="lovalingo-public-anon-key"]')?.content?.trim() || ""
84
- : "";
85
- const globals = globalThis;
86
- const resolvedApiKey = (typeof apiKey === "string" && apiKey.trim().length > 0
87
- ? apiKey
88
- : typeof publicAnonKey === "string" && publicAnonKey.trim().length > 0
89
- ? publicAnonKey
90
- : globals.__LOVALINGO_PUBLIC_ANON_KEY__ || globals.__LOVALINGO_API_KEY__ || metaKey || "").trim();
91
- const resolvedApiBase = (typeof apiBase === "string" && apiBase.trim().length > 0
92
- ? apiBase.trim()
93
- : "https://cdn.lovalingo.com");
94
- const nonLocalizedStorageKey = useMemo(() => `Lovalingo_non_localized_paths:${resolvedApiKey || "anonymous"}`, [resolvedApiKey]);
95
- const inactivePagesStorageKey = useMemo(() => `Lovalingo_inactive_pages:${resolvedApiKey || "anonymous"}`, [resolvedApiKey]);
96
- const [nonLocalizedPaths, setNonLocalizedPaths] = useState(() => {
97
- if (typeof window === "undefined")
98
- return [];
99
- if (!resolvedApiKey)
100
- return [];
101
- try {
102
- const raw = localStorage.getItem(nonLocalizedStorageKey);
103
- if (!raw)
104
- return [];
105
- const parsed = JSON.parse(raw);
106
- return parseBootstrapNonLocalizedPaths(parsed);
107
- }
108
- catch {
109
- return [];
110
- }
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
- });
128
- const [routingStatus, setRoutingStatus] = useState(() => {
129
- if (!resolvedApiKey)
130
- return "unknown";
131
- return nonLocalizedPaths.length > 0 || inactivePages.length > 0 ? "ready" : "loading";
132
- });
133
- const fetchRoutingConfig = useCallback(async () => {
134
- if (typeof window === "undefined")
135
- return;
136
- if (!resolvedApiKey)
137
- return;
138
- const pathParam = window.location.pathname + window.location.search;
139
- const requestUrl = `${resolvedApiBase}/functions/v1/bootstrap?key=${encodeURIComponent(resolvedApiKey)}&locale=${encodeURIComponent(defaultLang)}&path=${encodeURIComponent(pathParam)}`;
140
- const response = await fetch(requestUrl);
141
- const resolvedResponse = response.status === 304 ? await fetch(requestUrl, { cache: "force-cache" }) : response;
142
- if (!resolvedResponse.ok)
143
- throw new Error(`bootstrap HTTP ${resolvedResponse.status}`);
144
- const data = (await resolvedResponse.json());
145
- const record = (data || {});
146
- return {
147
- nonLocalizedPaths: parseBootstrapNonLocalizedPaths(record["non_localized_paths"]),
148
- inactivePages: parseBootstrapInactivePages(record["inactive_pages"]),
149
- };
150
- }, [defaultLang, resolvedApiBase, resolvedApiKey]);
151
- useEffect(() => {
152
- let cancelled = false;
153
- void (async () => {
154
- if (!resolvedApiKey)
155
- return;
156
- setRoutingStatus((prev) => (prev === "ready" ? prev : "loading"));
157
- try {
158
- const next = await fetchRoutingConfig();
159
- if (cancelled || !next)
160
- return;
161
- setNonLocalizedPaths(next.nonLocalizedPaths);
162
- setInactivePages(next.inactivePages);
163
- setRoutingStatus("ready");
164
- try {
165
- localStorage.setItem(nonLocalizedStorageKey, JSON.stringify(next.nonLocalizedPaths));
166
- localStorage.setItem(inactivePagesStorageKey, JSON.stringify(next.inactivePages));
167
- }
168
- catch {
169
- // ignore
170
- }
171
- }
172
- catch (err) {
173
- if (cancelled)
174
- return;
175
- setRoutingStatus("error");
176
- logDebug("[Lovalingo] Failed to fetch routing config:", err);
177
- }
178
- })();
179
- return () => {
180
- cancelled = true;
181
- };
182
- }, [fetchRoutingConfig, inactivePagesStorageKey, nonLocalizedStorageKey, resolvedApiKey]);
183
- return (React.createElement(BrowserRouter, null,
184
- React.createElement(NavigateExporter, { navigateRef: navigateRef }),
185
- React.createElement(LangRoutingContext.Provider, { value: { defaultLang, nonLocalizedPaths, inactivePages, status: routingStatus } },
186
- React.createElement(Routes, null,
187
- langs.map((lang) => (React.createElement(Route, { key: lang, path: `${lang}/*`, element: React.createElement(LangGuard, { lang: lang, nonLocalizedPaths: nonLocalizedPaths, defaultLang: defaultLang }) },
188
- React.createElement(Route, { index: true, element: React.createElement(React.Fragment, null, children) }),
189
- React.createElement(Route, { path: "*", element: React.createElement(React.Fragment, null, children) })))),
190
- React.createElement(Route, { path: "*", element: React.createElement(RedirectToDefaultLang, { defaultLang: defaultLang, nonLocalizedPaths: nonLocalizedPaths, routingStatus: routingStatus }, children) })))));
191
- }
@@ -1,17 +0,0 @@
1
- import React from 'react';
2
- interface LanguageSwitcherProps {
3
- locales: string[];
4
- currentLocale: string;
5
- onLocaleChange: (locale: string) => void;
6
- position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
7
- offsetY?: number;
8
- theme?: 'dark' | 'light';
9
- branding?: {
10
- required?: boolean;
11
- enabled?: boolean;
12
- label?: string;
13
- href?: string;
14
- };
15
- }
16
- export declare const LanguageSwitcher: React.FC<LanguageSwitcherProps>;
17
- export {};