@levo-so/site-runtime 0.3.8 → 0.3.10
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/{DateTimeWidget-hFjcn3o6.js → DateTimeWidget-U3lD0q1q.js} +1 -1
- package/{KBChatWidget-ClHFDgJ5.js → KBChatWidget-ByOwQQNg.js} +404 -397
- package/PopupBody-BI-MJqmd-DW3qWKam.js +159 -0
- package/PopupRenderer-D66Gx63H-BnOmWsyj.js +388 -0
- package/RichTextWidget-DfakML79.js +749 -0
- package/RteThemeProvider-BQmfSXSl.js +20 -0
- package/{nodeAlignment-BnJljFlw.js → content.cssm-XdMsTGIB.js} +24 -32
- package/{dist-CRDgdAkQ.js → dist-BdNHqoWi.js} +498 -466
- package/{dist-DPmVG7VB.js → dist-Cl_sKk9y.js} +3 -3
- package/index.d.ts +539 -9
- package/index.js +998 -798
- package/inlineRteTheme-CRPtkNXy.js +10 -0
- package/{kbChat-C00LVlxP.js → kbChat-GBcfFoss.js} +15 -15
- package/package.json +41 -27
- package/pillChrome-_hpGdrBS.js +50 -0
- package/popups.js +1 -1
- package/{PopupBody-DZVwDLkg-Cfy3lWGs.js → react-Cj_U6VZQ.js} +816 -850
- package/rich-text.d.ts +25 -0
- package/rich-text.js +1644 -0
- package/routes-CyKmkoRM.js +316 -0
- package/start.d.ts +38 -0
- package/start.js +19 -0
- package/styles.css +1 -1
- package/{usePopupRedirect-BThP_YPW-COUseDik.js → usePopupRedirect-CAAdcpaW-Ddjyf4KK.js} +15 -13
- package/PopupRenderer-BjW1jg70-W2y-PEPH.js +0 -387
- package/RichTextWidget-DkKHEq4j.js +0 -759
- package/pillChrome-BAu5AfCT.js +0 -30
- /package/{PhoneWidget-BmBO-kvA.js → PhoneWidget-DQQa4bL6.js} +0 -0
- /package/{Popover-3N88sRp4.js → Popover-C29yJ7rz.js} +0 -0
- /package/{es-CztjLuNy.js → es-ByBbirnH.js} +0 -0
- /package/{popupState-JmpeKzgM-qhNTV79U.js → popupState-JmpeKzgM-D_77LiAa.js} +0 -0
- /package/{react-CV3jf2mc.js → react-B9yvlbH0.js} +0 -0
- /package/{rteTheme.cssm-xg0gJ28J.js → rteTheme.cssm-W4gTOijw.js} +0 -0
package/index.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ import { ILevoMembership } from '@levo-so/core';
|
|
|
5
5
|
import { JSX } from 'react/jsx-runtime';
|
|
6
6
|
import { LevoQuery } from '@levo-so/core';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Build the canonical + hreflang link set for one page.
|
|
10
|
+
*
|
|
11
|
+
* The canonical is ALWAYS self-referential — a page canonicals to its own URL, never to a
|
|
12
|
+
* different variant. That single rule is what stops the variants fragmenting into
|
|
13
|
+
* duplicate content, which is the problem the whole design exists to avoid.
|
|
14
|
+
*/
|
|
15
|
+
export declare const buildHreflangLinks: (options: IBuildHreflangOptions) => IHreflangLink[];
|
|
16
|
+
|
|
8
17
|
/** Color-mode preference. `system` follows the OS `prefers-color-scheme`. */
|
|
9
18
|
export declare type ColorMode = "dark" | "light" | "system";
|
|
10
19
|
|
|
@@ -69,6 +78,23 @@ export declare const createLevoLoader: (site: ISiteJson) => (sources: ILevoDataS
|
|
|
69
78
|
params?: Record<string, string>;
|
|
70
79
|
}) => Promise<Record<string, unknown>>;
|
|
71
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Build a route-existence matcher from a generated `routeTree`.
|
|
83
|
+
*
|
|
84
|
+
* LAZY on purpose — the walk runs on the first lookup, not here — and the reason is import
|
|
85
|
+
* order, not router construction. `__root.tsx` is imported BY `routeTree.gen.ts`, so anything
|
|
86
|
+
* it evaluates at module scope runs while the tree is still half-built: walk it there and the
|
|
87
|
+
* list comes back short or empty. Deferring to the first lookup (inside a request middleware
|
|
88
|
+
* or a `beforeLoad`, both of which run long after every module has evaluated) sidesteps that
|
|
89
|
+
* entirely.
|
|
90
|
+
*
|
|
91
|
+
* Two bugs of this exact shape have already shipped, and both presented identically: the server
|
|
92
|
+
* render was correct, the client fell back to the default language, and the visitor watched the
|
|
93
|
+
* page flip a second after load. Before making this eager, confirm where the starter calls it
|
|
94
|
+
* from — at module scope in `__root.tsx`, eager is wrong.
|
|
95
|
+
*/
|
|
96
|
+
export declare const createRouteTreeMatcher: (routeTree: unknown) => ((pathname: string) => boolean);
|
|
97
|
+
|
|
72
98
|
/**
|
|
73
99
|
* Build the site's core Levo client from config, or `null` when the site isn't wired to a
|
|
74
100
|
* backend yet (no `apiUrl`). SSR-safe — `createLevoClient` guards its window/document reads.
|
|
@@ -93,6 +119,15 @@ export declare const createSiteClient: (config?: ILevoSiteConfig) => ILevoClient
|
|
|
93
119
|
*/
|
|
94
120
|
export declare const decideAccess: ({ rules, invalid, pageId, hasClient, isReady, isLoggedIn, check, }: IAccessDecisionInput) => IAccessDecision;
|
|
95
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Main entry: extract visitor country + language preferences.
|
|
124
|
+
* Accepts any header carrier — normalization is internal.
|
|
125
|
+
*/
|
|
126
|
+
export declare const extractGeoAndLanguageData: (headers: Record<string, string | string[]> | Headers | null | undefined) => {
|
|
127
|
+
bestCountry: ICountryInfo | null;
|
|
128
|
+
languageData: IParsedLanguageData;
|
|
129
|
+
};
|
|
130
|
+
|
|
96
131
|
/**
|
|
97
132
|
* The gate's policy, as a pure function.
|
|
98
133
|
*
|
|
@@ -138,6 +173,26 @@ export declare interface IAccessDecisionInput {
|
|
|
138
173
|
};
|
|
139
174
|
}
|
|
140
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Everything needed to annotate ONE page. Describes that page across all languages, so the path
|
|
178
|
+
* is the language-neutral one and the locale list is the site's, not the page's.
|
|
179
|
+
*/
|
|
180
|
+
export declare interface IBuildHreflangOptions {
|
|
181
|
+
/** Neutral path, no locale prefix — `/pricing`. */
|
|
182
|
+
neutralPath: string;
|
|
183
|
+
/** Site origin, e.g. `https://acme.com`. hreflang requires absolute URLs. */
|
|
184
|
+
origin: string;
|
|
185
|
+
locales: string[];
|
|
186
|
+
/**
|
|
187
|
+
* The locale prefix currently in the URL, or null on a neutral URL. Decides which URL is
|
|
188
|
+
* canonical: a prefixed visitor self-canonicals to their prefixed URL, a neutral visitor
|
|
189
|
+
* self-canonicals to the neutral one.
|
|
190
|
+
*/
|
|
191
|
+
activePrefix?: string | null;
|
|
192
|
+
/** Route-existence check — a locale with no route file for this page gets no alternate. */
|
|
193
|
+
hasRoute?: (pathname: string) => boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
141
196
|
export declare interface IColorModeProviderProps {
|
|
142
197
|
children: React.ReactNode;
|
|
143
198
|
/** Fallback when nothing is stored yet. */
|
|
@@ -165,6 +220,33 @@ export declare interface IComposedHead {
|
|
|
165
220
|
scripts: IHeadEntry[];
|
|
166
221
|
}
|
|
167
222
|
|
|
223
|
+
/**
|
|
224
|
+
* GeoIP header extractor.
|
|
225
|
+
*
|
|
226
|
+
* Pulls visitor country + language from HTTP headers across CDN providers
|
|
227
|
+
* (Cloudflare, Vercel, AWS CloudFront, Azure, BunnyCDN), nginx-ingress, and
|
|
228
|
+
* Traefik GeoIP2. Only surfaces `bestCountry` + `languageData` — the helpers
|
|
229
|
+
* that were historically exported (client IPs, provider-specific getters,
|
|
230
|
+
* raw header maps) are no longer used by any consumer.
|
|
231
|
+
*
|
|
232
|
+
* DELIBERATE DUPLICATE of `external/next-middleware/src/geoIp.ts`. The two must not be
|
|
233
|
+
* merged: next-middleware is a Next.js package serving V2 slate apps, and having a V2 app
|
|
234
|
+
* depend on the V3 site runtime (or vice versa) would couple two release trains that ship
|
|
235
|
+
* independently. The file has no framework imports, so copying is cheap — but a header
|
|
236
|
+
* added to ONE copy is a silent gap in the other. Change both.
|
|
237
|
+
*
|
|
238
|
+
* On v3 the live providers are nginx-ingress (`x-country-code`) and, on paper, Traefik
|
|
239
|
+
* (`x-geoip2-country`); our ingress sets both. See docs/studio-v3/site-localization.md.
|
|
240
|
+
*/
|
|
241
|
+
export declare interface ICountryInfo {
|
|
242
|
+
countryCode: string;
|
|
243
|
+
city?: string;
|
|
244
|
+
region?: string;
|
|
245
|
+
latitude?: string;
|
|
246
|
+
longitude?: string;
|
|
247
|
+
source: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
168
250
|
/** Editor → preview (received by `onMessage`; dashboard's `IOutboundMessage`). */
|
|
169
251
|
export declare type IEditorToPreview = {
|
|
170
252
|
type: "set-inspect";
|
|
@@ -251,6 +333,48 @@ declare interface IHeadEntry {
|
|
|
251
333
|
|
|
252
334
|
export declare type IHistoryFn = History["pushState"];
|
|
253
335
|
|
|
336
|
+
/**
|
|
337
|
+
* hreflang + canonical link generation for a localized site.
|
|
338
|
+
*
|
|
339
|
+
* These are the `<head>` annotations that tell Google which URL serves which language.
|
|
340
|
+
* They need NO database: the site knows its locale list (`site.json`) and which variants
|
|
341
|
+
* exist (the route tree), so correct annotations ship from day one — ahead of the sibling-row
|
|
342
|
+
* DB sync that the SITEMAP hreflang depends on.
|
|
343
|
+
*
|
|
344
|
+
* Known gap until that sync lands: these tags declare the relationships, but nothing points
|
|
345
|
+
* a crawler AT the prefixed URLs, so variants won't be indexed yet. See
|
|
346
|
+
* docs/studio-v3/site-localization.md.
|
|
347
|
+
*/
|
|
348
|
+
/** One `<link>` to render into `<head>` — either this page's canonical or one language alternate. */
|
|
349
|
+
export declare interface IHreflangLink {
|
|
350
|
+
rel: "alternate" | "canonical";
|
|
351
|
+
href: string;
|
|
352
|
+
hrefLang?: string;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export declare interface ILanguageInfo {
|
|
356
|
+
locale: string;
|
|
357
|
+
language: string;
|
|
358
|
+
region?: string;
|
|
359
|
+
quality: number;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** One selectable language, with the props a switcher needs to render it correctly. */
|
|
363
|
+
export declare interface ILanguageOption {
|
|
364
|
+
/** The locale tag, e.g. `fr-FR`. Label it however you like. */
|
|
365
|
+
locale: string;
|
|
366
|
+
isActive: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Spread onto an `<a>`. Contains the canonical `href`, the click interception, and
|
|
369
|
+
* `aria-current` — see `useLanguageOptions` for why each matters.
|
|
370
|
+
*/
|
|
371
|
+
linkProps: {
|
|
372
|
+
href: string;
|
|
373
|
+
"aria-current": "true" | undefined;
|
|
374
|
+
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
254
378
|
export declare interface ILayersNode {
|
|
255
379
|
tag: string;
|
|
256
380
|
source?: string;
|
|
@@ -706,10 +830,10 @@ export declare interface ILevoProfileFormProps {
|
|
|
706
830
|
className?: string;
|
|
707
831
|
}
|
|
708
832
|
|
|
709
|
-
/** A single-record binding — one
|
|
833
|
+
/** A single-record binding — one record of `module` by `slug` (a detail page). */
|
|
710
834
|
export declare interface ILevoRecordConfig {
|
|
711
|
-
module:
|
|
712
|
-
/**
|
|
835
|
+
module: ILevoSourceModule;
|
|
836
|
+
/** What the module's `identifier` names — see {@link ILevoSourceModule}. Round-tripped; the event single endpoint is flat (slug only). */
|
|
713
837
|
identifier: string;
|
|
714
838
|
/**
|
|
715
839
|
* Reserved for field projection. The slug endpoints return the whole record today, so
|
|
@@ -744,11 +868,10 @@ export declare interface ILevoSiteConfig {
|
|
|
744
868
|
appMode?: string;
|
|
745
869
|
}
|
|
746
870
|
|
|
747
|
-
/** A list binding —
|
|
871
|
+
/** A list binding — the records of one module matching `query`. */
|
|
748
872
|
export declare interface ILevoSourceConfig {
|
|
749
|
-
|
|
750
|
-
module
|
|
751
|
-
/** Collection key / blog key / event series id. */
|
|
873
|
+
module: ILevoSourceModule;
|
|
874
|
+
/** What the module's `identifier` names — see {@link ILevoSourceModule}. */
|
|
752
875
|
identifier: string;
|
|
753
876
|
/** where / sort / limit / page / search. `limit: -1` = all. */
|
|
754
877
|
query?: LevoQuery.FindQuery;
|
|
@@ -770,6 +893,23 @@ export declare type ILevoSourceDef = (ILevoSourceConfig & {
|
|
|
770
893
|
mode: "form";
|
|
771
894
|
});
|
|
772
895
|
|
|
896
|
+
/**
|
|
897
|
+
* Every content module a page can bind to, and what `identifier` names for each:
|
|
898
|
+
*
|
|
899
|
+
* collection the collection key → records of that collection
|
|
900
|
+
* blog the blog key → posts of that blog
|
|
901
|
+
* event the event series id → events in that series
|
|
902
|
+
* community the forum key → posts in that forum
|
|
903
|
+
* blog_category the parent BLOG's key → categories of that blog
|
|
904
|
+
* blog_tag the parent BLOG's key → tags of that blog
|
|
905
|
+
* blog_author the parent BLOG's key → authors writing on that blog
|
|
906
|
+
*
|
|
907
|
+
* The three blog children name their PARENT, not themselves: a category page lists the
|
|
908
|
+
* categories of ONE blog, and the platform reads a v3 binding exactly that way when it
|
|
909
|
+
* enumerates a dynamic page's URLs (`services/builder/src/services/sitemap_sources.ts`).
|
|
910
|
+
*/
|
|
911
|
+
export declare type ILevoSourceModule = "blog" | "blog_author" | "blog_category" | "blog_tag" | "collection" | "community" | "event";
|
|
912
|
+
|
|
773
913
|
export declare interface ILevoSourceResult<T = Record<string, unknown>> {
|
|
774
914
|
/** null until loaded, on error, and on an unwired site. */
|
|
775
915
|
data: T[] | null;
|
|
@@ -785,6 +925,142 @@ export declare interface ILevoSourceResult<T = Record<string, unknown>> {
|
|
|
785
925
|
};
|
|
786
926
|
}
|
|
787
927
|
|
|
928
|
+
export declare interface ILocaleConfig {
|
|
929
|
+
/** Every locale the site publishes. Empty or single-entry on a non-localized site. */
|
|
930
|
+
locales: string[];
|
|
931
|
+
/** The locale served at unprefixed URLs. */
|
|
932
|
+
defaultLocale: string;
|
|
933
|
+
/**
|
|
934
|
+
* Whether this site does localization at all. Every site is single-locale until a workspace
|
|
935
|
+
* adds a second one, and the whole mechanism is inert while this is false.
|
|
936
|
+
*/
|
|
937
|
+
isLocalized: boolean;
|
|
938
|
+
/**
|
|
939
|
+
* Absolute origin for hreflang, or null when the site has no domain stamped yet. hreflang
|
|
940
|
+
* requires absolute URLs, so an unstamped `site.json` means no tags rather than broken ones.
|
|
941
|
+
*/
|
|
942
|
+
origin: string | null;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
export declare type ILocaleDecision = ILocaleRender | ILocaleRedirect;
|
|
946
|
+
|
|
947
|
+
/** Mount once in `<head>`; the current page comes from router state, not from props. */
|
|
948
|
+
export declare interface ILocaleHreflangProps {
|
|
949
|
+
config: ILocaleConfig;
|
|
950
|
+
/**
|
|
951
|
+
* The generated route tree. Supplying it drops alternates for variants that have no route
|
|
952
|
+
* file, so an advertised URL never 404s. Omit and every locale is advertised.
|
|
953
|
+
*/
|
|
954
|
+
routeTree?: unknown;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* What the provider needs to expose a language to page code. Everything except `locale` is
|
|
959
|
+
* site-wide config; prefer `<SiteLocaleProvider>`, which derives all of it from `ILocaleConfig`.
|
|
960
|
+
*/
|
|
961
|
+
export declare interface ILocaleProviderProps {
|
|
962
|
+
children: React.ReactNode;
|
|
963
|
+
/** The locale being rendered, from the server's resolution. */
|
|
964
|
+
locale: string;
|
|
965
|
+
/** Every locale the site publishes. */
|
|
966
|
+
locales?: string[];
|
|
967
|
+
/** Locale served at unprefixed URLs. */
|
|
968
|
+
defaultLocale?: string;
|
|
969
|
+
/** How `locale` was chosen — `"prefix"` means the visitor is on a canonical URL. */
|
|
970
|
+
source?: LocaleSource;
|
|
971
|
+
/**
|
|
972
|
+
* Does a route file exist at this path? Supplying it stops the switcher offering a language
|
|
973
|
+
* this page was never translated into — see `useLanguageOptions`.
|
|
974
|
+
*/
|
|
975
|
+
hasRoute?: (pathname: string) => boolean;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** Send the visitor to their language's URL. */
|
|
979
|
+
export declare interface ILocaleRedirect {
|
|
980
|
+
kind: "redirect";
|
|
981
|
+
locale: string;
|
|
982
|
+
source: LocaleSource;
|
|
983
|
+
/** Path only — `localeRedirectResponse` appends the request's own `search`/`hash`. */
|
|
984
|
+
path: string;
|
|
985
|
+
status: 302;
|
|
986
|
+
/**
|
|
987
|
+
* Every header the redirect must carry: `location`, the load-bearing `cache-control`, and
|
|
988
|
+
* `set-cookie` when the choice is worth remembering. Send them as-is; nothing is missing.
|
|
989
|
+
*/
|
|
990
|
+
headers: Record<string, string>;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/** Serve the URL the visitor asked for. */
|
|
994
|
+
export declare interface ILocaleRender {
|
|
995
|
+
kind: "render";
|
|
996
|
+
locale: string;
|
|
997
|
+
source: LocaleSource;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
export declare interface ILocaleRequestOptions {
|
|
1001
|
+
/** The pathname the visitor asked for, e.g. `/pricing` or `/fr-FR/pricing`. */
|
|
1002
|
+
pathname: string;
|
|
1003
|
+
/** Every locale the site publishes, from `site.json`. */
|
|
1004
|
+
locales: string[];
|
|
1005
|
+
/** The locale served at unprefixed URLs. */
|
|
1006
|
+
defaultLocale: string;
|
|
1007
|
+
/**
|
|
1008
|
+
* Request headers. Server path only — an in-app navigation never reaches the server, so the
|
|
1009
|
+
* client path has none and negotiates from the cookie alone.
|
|
1010
|
+
*/
|
|
1011
|
+
headers?: Record<string, string | string[]> | Headers | null;
|
|
1012
|
+
/**
|
|
1013
|
+
* A raw cookie string. Omit on the server, where it is read out of `headers`; pass
|
|
1014
|
+
* `document.cookie` on the client, which has no headers to read it from.
|
|
1015
|
+
*/
|
|
1016
|
+
cookie?: string | null;
|
|
1017
|
+
/**
|
|
1018
|
+
* Does a route file exist at this path? Without it a visitor can be sent to a translation
|
|
1019
|
+
* nobody wrote. Build it with `createRouteTreeMatcher(routeTree)`.
|
|
1020
|
+
*/
|
|
1021
|
+
hasRoute?: (pathname: string) => boolean;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* What `useLocale()` returns: the language being rendered, the site's full list, and the one
|
|
1026
|
+
* mutation page code may perform. Read-only apart from `setLocale` — the URL, not this object,
|
|
1027
|
+
* is what decides the language.
|
|
1028
|
+
*/
|
|
1029
|
+
export declare interface ILocaleState {
|
|
1030
|
+
locale: string;
|
|
1031
|
+
locales: string[];
|
|
1032
|
+
defaultLocale: string;
|
|
1033
|
+
source: LocaleSource;
|
|
1034
|
+
/** True once the site publishes more than one locale — gate switcher UI on this. */
|
|
1035
|
+
isMultiLocale: boolean;
|
|
1036
|
+
/**
|
|
1037
|
+
* Switch language: remember the choice and go to that language's URL for this page.
|
|
1038
|
+
*
|
|
1039
|
+
* It NAVIGATES rather than swapping content in place, because with the redirect model a
|
|
1040
|
+
* language IS a URL — `/hi-IN/pricing` is the Hindi page and the only place it exists. That
|
|
1041
|
+
* also means the result is shareable and cacheable, which staying on a neutral URL could
|
|
1042
|
+
* not give you.
|
|
1043
|
+
*/
|
|
1044
|
+
setLocale: (locale: string) => void;
|
|
1045
|
+
/**
|
|
1046
|
+
* Route-existence check for this site, when the provider was given one. Consumed by
|
|
1047
|
+
* `useLanguageOptions`; page code should not need it.
|
|
1048
|
+
*/
|
|
1049
|
+
hasRoute?: (pathname: string) => boolean;
|
|
1050
|
+
/**
|
|
1051
|
+
* Prefix a path with a locale. For the LANGUAGE SWITCHER only — the one place a visitor is
|
|
1052
|
+
* explicitly choosing a variant.
|
|
1053
|
+
*
|
|
1054
|
+
* Do NOT use it in shared chrome (navbar, footer, logo). Those render on every variant, and
|
|
1055
|
+
* building their links from the current page's language traps a visitor in the wrong one:
|
|
1056
|
+
* someone who landed on `/en` because no Hindi home exists then gets `/en/...` links
|
|
1057
|
+
* everywhere, and never finds their way back to `/hi/about` even though it exists. Shared
|
|
1058
|
+
* components link to the NEUTRAL path and let the doorway route them — it knows the cookie
|
|
1059
|
+
* and which translations exist, and a component knows neither.
|
|
1060
|
+
*/
|
|
1061
|
+
pathFor: (path: string, locale: string) => string;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
788
1064
|
export declare function Inspector(): null;
|
|
789
1065
|
|
|
790
1066
|
/** One page's head metadata. Every field optional — site defaults fill gaps per-field. */
|
|
@@ -848,6 +1124,12 @@ export declare interface IParsedAccess {
|
|
|
848
1124
|
invalid: boolean;
|
|
849
1125
|
}
|
|
850
1126
|
|
|
1127
|
+
export declare interface IParsedLanguageData {
|
|
1128
|
+
languages: ILanguageInfo[];
|
|
1129
|
+
primaryLanguage: string;
|
|
1130
|
+
inferredRegions: string[];
|
|
1131
|
+
}
|
|
1132
|
+
|
|
851
1133
|
export declare interface IPatchedHistoryFn {
|
|
852
1134
|
(this: History, ...args: Parameters<IHistoryFn>): ReturnType<IHistoryFn>;
|
|
853
1135
|
__inspectorPatched?: boolean;
|
|
@@ -1001,6 +1283,63 @@ export declare interface IRegionElement {
|
|
|
1001
1283
|
text?: string;
|
|
1002
1284
|
}
|
|
1003
1285
|
|
|
1286
|
+
export declare interface IResolvedLocale {
|
|
1287
|
+
/** The locale to render, always a member of `locales` (or the default). */
|
|
1288
|
+
locale: string;
|
|
1289
|
+
source: LocaleSource;
|
|
1290
|
+
/**
|
|
1291
|
+
* The locale prefix stripped off the incoming pathname, if any — `/fr-FR/pricing` →
|
|
1292
|
+
* `fr-FR`. Null on a neutral URL. This is what tells the caller the visitor is on a
|
|
1293
|
+
* canonical URL rather than a negotiated one.
|
|
1294
|
+
*/
|
|
1295
|
+
prefix: string | null;
|
|
1296
|
+
/** The pathname with any locale prefix removed — `/fr-FR/pricing` → `/pricing`. */
|
|
1297
|
+
neutralPath: string;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export declare interface IResolveLocaleOptions {
|
|
1301
|
+
/** Full locale list from `site.json` (`workspace.locales`). */
|
|
1302
|
+
locales?: string[];
|
|
1303
|
+
/** Locale served at unprefixed URLs (`workspace.default_locale`). */
|
|
1304
|
+
defaultLocale?: string;
|
|
1305
|
+
/** The request pathname, e.g. `/fr-FR/pricing`. */
|
|
1306
|
+
pathname: string;
|
|
1307
|
+
/** Request headers. Server-side only; omit on the client. */
|
|
1308
|
+
headers?: Record<string, string | string[]> | Headers | null;
|
|
1309
|
+
/** Value of the `levo_locale` cookie, if the visitor has one. */
|
|
1310
|
+
cookieLocale?: string | null;
|
|
1311
|
+
/**
|
|
1312
|
+
* Skip cookie/header/geo negotiation and pin to the default. Set for crawlers, so a
|
|
1313
|
+
* neutral URL genuinely IS its canonical language from Google's point of view.
|
|
1314
|
+
*/
|
|
1315
|
+
forceDefault?: boolean;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
export declare interface IResolveLocaleRedirectOptions {
|
|
1319
|
+
/** The locale the visitor should see, from `resolveLocale`. */
|
|
1320
|
+
locale: string;
|
|
1321
|
+
/** Locale served at unprefixed URLs. */
|
|
1322
|
+
defaultLocale: string;
|
|
1323
|
+
/** The locale prefix already present in the URL, or null on a neutral URL. */
|
|
1324
|
+
prefix: string | null;
|
|
1325
|
+
/** The request path with any locale prefix removed. */
|
|
1326
|
+
neutralPath: string;
|
|
1327
|
+
/** Every locale the site publishes. */
|
|
1328
|
+
locales: string[];
|
|
1329
|
+
/**
|
|
1330
|
+
* Does a route file exist at this path? Prevents redirecting to a translation that was
|
|
1331
|
+
* never written: `/hi-IN/careers` absent means the visitor stays on `/careers` and reads
|
|
1332
|
+
* the default language, rather than being bounced to a 404.
|
|
1333
|
+
*/
|
|
1334
|
+
hasRoute?: (pathname: string) => boolean;
|
|
1335
|
+
/**
|
|
1336
|
+
* The pathname exactly as the visitor asked for it. Only used to detect a prefix whose
|
|
1337
|
+
* spelling differs from the site's own, e.g. `/EN/pricing` for a site publishing `en`.
|
|
1338
|
+
* Omit and that correction is skipped.
|
|
1339
|
+
*/
|
|
1340
|
+
currentPath?: string;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1004
1343
|
/** The element-selected payload — see `selectedPayload`. */
|
|
1005
1344
|
export declare interface ISelectedElement {
|
|
1006
1345
|
tag: string;
|
|
@@ -1080,6 +1419,22 @@ export declare interface ISiteJson {
|
|
|
1080
1419
|
insights_url?: string | null;
|
|
1081
1420
|
app_mode?: string;
|
|
1082
1421
|
};
|
|
1422
|
+
/**
|
|
1423
|
+
* Every locale the site publishes, as dash-form full locales (`en-GB`, `fr-FR`, `ar-AE`)
|
|
1424
|
+
* to match how `page.language` stores them. Projected from `workspace.locales` — locales
|
|
1425
|
+
* are a WORKSPACE-level setting, so every site in a workspace shares this list.
|
|
1426
|
+
*
|
|
1427
|
+
* Absent or length <= 1 → localization is entirely inert: no rewrite, no hreflang, no
|
|
1428
|
+
* cookie. That is every site today, which is what lets the mechanism ship before the
|
|
1429
|
+
* builder learns to generate `/fr-FR/*` routes.
|
|
1430
|
+
*/
|
|
1431
|
+
locales?: string[];
|
|
1432
|
+
/**
|
|
1433
|
+
* The locale served at UNPREFIXED URLs. `/pricing` is this locale's canonical home, and
|
|
1434
|
+
* it deliberately has no `/en-GB/pricing` twin — a prefixed default would be a duplicate
|
|
1435
|
+
* needing its own canonical. Projected from `workspace.default_locale`.
|
|
1436
|
+
*/
|
|
1437
|
+
default_locale?: string;
|
|
1083
1438
|
}
|
|
1084
1439
|
|
|
1085
1440
|
/** Site-wide defaults + site-wide raw head, from `src/pages/_site.meta.ts`. */
|
|
@@ -1307,6 +1662,62 @@ export declare const LevoSiteProvider: React.FC<{
|
|
|
1307
1662
|
children: React.ReactNode;
|
|
1308
1663
|
}>;
|
|
1309
1664
|
|
|
1665
|
+
/** Cookie the resolved locale is persisted under. Readable by JS — the switcher writes it. */
|
|
1666
|
+
export declare const LOCALE_COOKIE = "levo_locale";
|
|
1667
|
+
|
|
1668
|
+
/** Cache-Control for the doorway redirect. Never let a shared cache store this — see above. */
|
|
1669
|
+
export declare const LOCALE_REDIRECT_CACHE_CONTROL = "private, no-store";
|
|
1670
|
+
|
|
1671
|
+
/**
|
|
1672
|
+
* Build the `beforeLoad` guard for a site's root route.
|
|
1673
|
+
*
|
|
1674
|
+
* `routeTree` is passed here rather than carried on the config because `__root.tsx` is imported
|
|
1675
|
+
* by the generated tree; the matcher it produces reads the tree lazily, on the first navigation,
|
|
1676
|
+
* which is what keeps that circular import safe.
|
|
1677
|
+
*/
|
|
1678
|
+
export declare const localeBeforeLoad: (config: ILocaleConfig, routeTree: unknown) => ({ location }: {
|
|
1679
|
+
location: {
|
|
1680
|
+
pathname: string;
|
|
1681
|
+
searchStr?: string;
|
|
1682
|
+
};
|
|
1683
|
+
}) => void;
|
|
1684
|
+
|
|
1685
|
+
export declare const LocaleHreflang: React.FC<ILocaleHreflangProps>;
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* The URL a page has in a given variant.
|
|
1689
|
+
*
|
|
1690
|
+
* On a multi-variant site EVERY variant is prefixed — including the site's default. That is
|
|
1691
|
+
* what makes each page cacheable: `/en/pricing` is always English and `/fr-FR/pricing` is
|
|
1692
|
+
* always French, so a plain URL-keyed cache is correct with no configuration. The unprefixed
|
|
1693
|
+
* `/pricing` is not a page at all under this model; it is the doorway that redirects.
|
|
1694
|
+
*
|
|
1695
|
+
* A single-variant site gets no prefixes — `/en/` on every URL of a site that only speaks
|
|
1696
|
+
* English is noise, and there is nothing to disambiguate.
|
|
1697
|
+
*
|
|
1698
|
+
* The default locale takes a prefix like every other, so this needs no knowledge of which one
|
|
1699
|
+
* it is. (It did while the default was unprefixed; that inverted on 2026-08-18.)
|
|
1700
|
+
*/
|
|
1701
|
+
export declare const localePath: (path: string, locale: string, locales: string[]) => string;
|
|
1702
|
+
|
|
1703
|
+
export declare const LocaleProvider: React.FC<ILocaleProviderProps>;
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Build the HTTP response for a redirect decision.
|
|
1707
|
+
*
|
|
1708
|
+
* Exists so the server path cannot assemble it wrongly: status and headers come straight off the
|
|
1709
|
+
* decision, and `302` (never `301`) is fixed here — the target depends on who is asking, so a
|
|
1710
|
+
* browser must never remember it as permanent.
|
|
1711
|
+
*/
|
|
1712
|
+
export declare const localeRedirectResponse: (decision: ILocaleRedirect, search?: string, hash?: string) => Response;
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* How a locale was chosen. Surfaced so callers can act on provenance rather than re-deriving
|
|
1716
|
+
* it: `prefix` means the visitor is on a canonical locale URL (don't redirect, don't restamp
|
|
1717
|
+
* the cookie), and `default` on a multi-locale site means nothing matched.
|
|
1718
|
+
*/
|
|
1719
|
+
export declare type LocaleSource = "prefix" | "cookie" | "header" | "geo" | "default";
|
|
1720
|
+
|
|
1310
1721
|
/**
|
|
1311
1722
|
* To E.164: strip every separator, keep one leading `+`. `"+91 98765 43210"` and
|
|
1312
1723
|
* `"0091-98765-43210"` both normalize to `"+919876543210"` — the format the OTP endpoint
|
|
@@ -1387,6 +1798,9 @@ export declare const prefetchLevoSources: ({ queryClient, config, sources, param
|
|
|
1387
1798
|
params?: Record<string, string>;
|
|
1388
1799
|
}) => Promise<Record<string, unknown>>;
|
|
1389
1800
|
|
|
1801
|
+
/** Read the locale config out of a site's `site.json`. */
|
|
1802
|
+
export declare const readLocaleConfig: (site: ISiteJson) => ILocaleConfig;
|
|
1803
|
+
|
|
1390
1804
|
/**
|
|
1391
1805
|
* The id the currently matched route stamped into the head, or null when the site's
|
|
1392
1806
|
* platform hasn't stamped ids into its meta files yet (the field ships empty). Client-only —
|
|
@@ -1407,6 +1821,41 @@ export declare const resolveAuthMethods: (rules: ILevoAccessConfig) => ILevoAuth
|
|
|
1407
1821
|
/** What `system` resolves to — the class actually on `<html>`. */
|
|
1408
1822
|
export declare type ResolvedColorMode = "dark" | "light";
|
|
1409
1823
|
|
|
1824
|
+
/**
|
|
1825
|
+
* Resolve which locale to serve.
|
|
1826
|
+
*
|
|
1827
|
+
* Precedence: explicit URL prefix → cookie → `Accept-Language` → GeoIP country → default.
|
|
1828
|
+
*
|
|
1829
|
+
* The prefix wins over the cookie deliberately: a `/fr-FR/pricing` link shared with someone
|
|
1830
|
+
* whose cookie says `de-DE` must render French, or shared links stop meaning anything.
|
|
1831
|
+
*
|
|
1832
|
+
* GeoIP sits BELOW `Accept-Language` because country is a weaker signal for language than
|
|
1833
|
+
* the visitor's own stated browser preference — someone in Belgium may want either Dutch or
|
|
1834
|
+
* French, and their browser already answered that.
|
|
1835
|
+
*/
|
|
1836
|
+
export declare const resolveLocale: (options: IResolveLocaleOptions) => IResolvedLocale;
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* The path to redirect a visitor to, or `null` to serve the current URL as-is.
|
|
1840
|
+
*
|
|
1841
|
+
* Returns null — meaning "no redirect" — in every one of these cases:
|
|
1842
|
+
* - the visitor is already on a prefixed URL (they asked for that language explicitly)
|
|
1843
|
+
* - the site publishes one locale or none
|
|
1844
|
+
* - no route file exists for this page in the visitor's language
|
|
1845
|
+
*/
|
|
1846
|
+
export declare const resolveLocaleRedirect: (options: IResolveLocaleRedirectOptions) => string | null;
|
|
1847
|
+
|
|
1848
|
+
/**
|
|
1849
|
+
* Decide what to do with one request to a possibly-unprefixed URL.
|
|
1850
|
+
*
|
|
1851
|
+
* Bot detection is derived here rather than asked of the caller. Crawlers must always see the
|
|
1852
|
+
* default language at a neutral URL — it is what makes that URL genuinely *be* its canonical
|
|
1853
|
+
* language — and that is a property of the design, not a decision each doorway should be making
|
|
1854
|
+
* for itself. `resolveLocale`'s `forceDefault` stays available for anything that needs to pin it
|
|
1855
|
+
* directly.
|
|
1856
|
+
*/
|
|
1857
|
+
export declare const resolveLocaleRequest: (options: ILocaleRequestOptions) => ILocaleDecision;
|
|
1858
|
+
|
|
1410
1859
|
/**
|
|
1411
1860
|
* The site's Levo client config from its `site.json` — the only place that mapping lives, so
|
|
1412
1861
|
* the provider and the SSR route loader can't drift apart.
|
|
@@ -1416,9 +1865,65 @@ export declare type ResolvedColorMode = "dark" | "light";
|
|
|
1416
1865
|
*/
|
|
1417
1866
|
export declare const siteConfigFromJson: (site: ISiteJson) => ILevoSiteConfig;
|
|
1418
1867
|
|
|
1868
|
+
/**
|
|
1869
|
+
* Mount the locale context from a site's config.
|
|
1870
|
+
*
|
|
1871
|
+
* Prefer this over `<LocaleProvider>` in a generated site: it derives the active language from
|
|
1872
|
+
* the URL itself, so there is no second copy of that derivation to fall out of step with the
|
|
1873
|
+
* `<html lang>` or the hreflang tags.
|
|
1874
|
+
*/
|
|
1875
|
+
export declare const SiteLocaleProvider: React.FC<{
|
|
1876
|
+
config: ILocaleConfig;
|
|
1877
|
+
children: React.ReactNode;
|
|
1878
|
+
/**
|
|
1879
|
+
* The generated route tree. Supplying it lets the switcher hide languages this page has no
|
|
1880
|
+
* translation into — the difference between offering a link and offering a 404.
|
|
1881
|
+
*/
|
|
1882
|
+
routeTree?: unknown;
|
|
1883
|
+
}>;
|
|
1884
|
+
|
|
1885
|
+
/**
|
|
1886
|
+
* Split a locale prefix off a pathname. Only strips a segment that is a KNOWN locale, so a
|
|
1887
|
+
* site with a page genuinely named `/fr-FR` (or any other collision) is unaffected — the
|
|
1888
|
+
* segment has to be in the site's own locale list to count.
|
|
1889
|
+
*/
|
|
1890
|
+
export declare const splitLocalePrefix: (pathname: string, locales: string[]) => {
|
|
1891
|
+
prefix: string | null;
|
|
1892
|
+
neutralPath: string;
|
|
1893
|
+
};
|
|
1894
|
+
|
|
1419
1895
|
/** Read/set the current color mode. Throws outside a `<ColorModeProvider>`. */
|
|
1420
1896
|
export declare function useColorMode(): IColorModeState;
|
|
1421
1897
|
|
|
1898
|
+
/**
|
|
1899
|
+
* The behaviour half of a language switcher, so the starter only owns the look.
|
|
1900
|
+
*
|
|
1901
|
+
* Three things here are load-bearing and were previously comments in a starter component asking
|
|
1902
|
+
* not to be "simplified":
|
|
1903
|
+
*
|
|
1904
|
+
* 1. `href` is a real canonical URL, so a crawler can follow it. A switcher built from buttons
|
|
1905
|
+
* and a click handler is invisible to search, and the site's own hreflang tags then point at
|
|
1906
|
+
* pages nothing links to.
|
|
1907
|
+
* 2. A plain left-click is intercepted into a cookie write plus a navigation, so the choice is
|
|
1908
|
+
* remembered. Modifier and middle clicks are deliberately NOT intercepted, so "open in new
|
|
1909
|
+
* tab" lands on the real URL like any other link.
|
|
1910
|
+
* 3. The path is stripped back to its neutral form before each variant's URL is built, or the
|
|
1911
|
+
* hrefs come out doubled (`/de-DE/fr-FR/pricing`).
|
|
1912
|
+
*
|
|
1913
|
+
* Render `options` however you want; drop `linkProps` onto an `<a>` and none of the above can
|
|
1914
|
+
* be lost.
|
|
1915
|
+
*
|
|
1916
|
+
* Languages this page has no translation into are OMITTED, when the provider was given a route
|
|
1917
|
+
* tree. Their URL would be a real 404: a prefixed URL is an explicit request, so the doorway
|
|
1918
|
+
* correctly declines to redirect it, and there is no page behind it. This is the same rule the
|
|
1919
|
+
* hreflang tags follow — a switcher offering what hreflang refuses to advertise is the two
|
|
1920
|
+
* halves disagreeing about which pages exist.
|
|
1921
|
+
*
|
|
1922
|
+
* Returns an empty list when fewer than two languages remain, so a switcher self-hides on a
|
|
1923
|
+
* single-locale site and on a page that only exists in one language.
|
|
1924
|
+
*/
|
|
1925
|
+
export declare function useLanguageOptions(): ILanguageOption[];
|
|
1926
|
+
|
|
1422
1927
|
export declare const useLevoAccess: (source: ILevoAccessSource) => ILevoAccessResult;
|
|
1423
1928
|
|
|
1424
1929
|
export declare const useLevoAuth: () => ILevoAuthState;
|
|
@@ -1461,11 +1966,36 @@ export declare const useLevoRecord: <T = Record<string, unknown>>(config: ILevoR
|
|
|
1461
1966
|
export declare const useLevoSite: () => ILevoClient | null;
|
|
1462
1967
|
|
|
1463
1968
|
/**
|
|
1464
|
-
* Render a LIST of platform content. The config is a static
|
|
1465
|
-
*
|
|
1969
|
+
* Render a LIST of platform content. The config is normally a static `dataSources` entry
|
|
1970
|
+
* (the editor rewrites it); a page whose filter depends on another binding's loaded record
|
|
1971
|
+
* spreads that entry and overrides `query` at the call — see the builder prompt.
|
|
1972
|
+
*
|
|
1973
|
+
* Reads the router QueryClient: if the route loader prefetched this binding, the first
|
|
1466
1974
|
* render has data (SSR ships it); otherwise it fetches client-side. Always render a loading
|
|
1467
1975
|
* and an empty state — `data` is null before the first load and on unwired sites.
|
|
1468
1976
|
*/
|
|
1469
1977
|
export declare const useLevoSource: <T = Record<string, unknown>>(config: ILevoSourceConfig) => ILevoSourceResult<T>;
|
|
1470
1978
|
|
|
1979
|
+
/**
|
|
1980
|
+
* Read the active locale and switch languages.
|
|
1981
|
+
*
|
|
1982
|
+
* Returns an inert single-locale state outside a `<LocaleProvider>` rather than throwing —
|
|
1983
|
+
* unlike `useColorMode`, which throws. A switcher is page content the AI may drop onto a
|
|
1984
|
+
* site whose provider was never wired, and crashing the whole page over a language picker is
|
|
1985
|
+
* a worse failure than rendering nothing.
|
|
1986
|
+
*/
|
|
1987
|
+
export declare function useLocale(): ILocaleState;
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* The language the current URL serves.
|
|
1991
|
+
*
|
|
1992
|
+
* Read off the URL, not negotiated: every variant is a prefixed URL under the redirect model,
|
|
1993
|
+
* so `/hi-IN/pricing` is Hindi and nothing else, and server and browser cannot disagree about
|
|
1994
|
+
* it. (When locales were applied as a rewrite they could, and did — three separate bugs where
|
|
1995
|
+
* the page rendered in one language and hydrated in another.)
|
|
1996
|
+
*
|
|
1997
|
+
* For `<html lang>`, which sits above the provider and so cannot use `useLocale()`.
|
|
1998
|
+
*/
|
|
1999
|
+
export declare function useUrlLocale(config: ILocaleConfig): string;
|
|
2000
|
+
|
|
1471
2001
|
export { }
|