@flyo/nitro-next 2.0.0 → 2.1.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.
- package/dist/client.d.mts +64 -4
- package/dist/client.d.ts +64 -4
- package/dist/client.js +28 -0
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +27 -0
- package/dist/client.mjs.map +1 -1
- package/dist/proxy.d.mts +2 -2
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.js +8 -2
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +8 -2
- package/dist/proxy.mjs.map +1 -1
- package/dist/server.d.mts +74 -5
- package/dist/server.d.ts +74 -5
- package/dist/server.js +82 -8
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +81 -8
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -1,14 +1,68 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import { MetadataRoute, Metadata } from 'next';
|
|
4
|
-
import { Entity, ConfigResponse, PagesApi, EntitiesApi, SitemapApi, SearchApi, Page, Block } from '@flyo/nitro-typescript';
|
|
4
|
+
import { Translation, Entity, ConfigResponse, PagesApi, EntitiesApi, SitemapApi, SearchApi, Page, Block } from '@flyo/nitro-typescript';
|
|
5
|
+
export { Translation } from '@flyo/nitro-typescript';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A single language option for a language switcher, derived from a page's or
|
|
9
|
+
* entity's `translation[]`. Framework-agnostic plain data — render it however
|
|
10
|
+
* you like.
|
|
11
|
+
*/
|
|
12
|
+
interface FlyoLanguageLink {
|
|
13
|
+
/** Locale shortcode, e.g. `"de"`. */
|
|
14
|
+
shortcode: string;
|
|
15
|
+
/** Full language name from the CMS translation, e.g. `"Deutsch"` (when available). */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Fully-resolved localized href, or `null` when this locale has no linked translation. */
|
|
18
|
+
href: string | null;
|
|
19
|
+
/** Localized page/entity title, when available. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** `true` when this entry is the currently active locale. */
|
|
22
|
+
isCurrent: boolean;
|
|
23
|
+
/** `true` when a linked translation actually exists for this locale. */
|
|
24
|
+
exists: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Map a page's or entity's `translation[]` into a switcher-ready array of typed
|
|
28
|
+
* links — the building block for a language switcher.
|
|
29
|
+
*
|
|
30
|
+
* Flyo only returns `translation` entries for languages that actually have a
|
|
31
|
+
* translation. Pass `options.locales` (e.g. `flyo.state.locales`) to also get an
|
|
32
|
+
* entry for every configured locale that is *missing* a translation — those come
|
|
33
|
+
* back as `{ href: null, exists: false }` so you can render a fallback (a disabled
|
|
34
|
+
* item, a link to the home page, …).
|
|
35
|
+
*
|
|
36
|
+
* Pure — no React or server-only APIs, so it is safe to call from server *or*
|
|
37
|
+
* client components.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const links = getLanguageLinks(page.translation, {
|
|
42
|
+
* currentLang: lang,
|
|
43
|
+
* locales: flyo.state.locales,
|
|
44
|
+
* });
|
|
45
|
+
* // links.map(l => l.exists
|
|
46
|
+
* // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>
|
|
47
|
+
* // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function getLanguageLinks(translations: Translation[] | undefined, options?: {
|
|
51
|
+
currentLang?: string;
|
|
52
|
+
locales?: string[];
|
|
53
|
+
}): FlyoLanguageLink[];
|
|
5
54
|
|
|
6
55
|
/**
|
|
7
56
|
* Read-only configuration state
|
|
8
57
|
*/
|
|
9
58
|
interface NitroState {
|
|
10
59
|
readonly accessToken: string;
|
|
60
|
+
/** The single default language (back-compat). Prefer `defaultLocale` for i18n. */
|
|
11
61
|
readonly lang: string | null;
|
|
62
|
+
/** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */
|
|
63
|
+
readonly locales: string[];
|
|
64
|
+
/** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */
|
|
65
|
+
readonly defaultLocale: string | null;
|
|
12
66
|
readonly baseUrl: string | null;
|
|
13
67
|
readonly components: Record<string, any>;
|
|
14
68
|
readonly showMissingComponentAlert: boolean;
|
|
@@ -42,8 +96,12 @@ type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;
|
|
|
42
96
|
interface FlyoInstance {
|
|
43
97
|
/** Read-only configuration state */
|
|
44
98
|
readonly state: NitroState;
|
|
45
|
-
/**
|
|
46
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Fetch and cache the Nitro CMS configuration (React-cached per requested locale).
|
|
101
|
+
* When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`
|
|
102
|
+
* header set by the proxy), falling back to `defaultLocale`.
|
|
103
|
+
*/
|
|
104
|
+
getNitroConfig(lang?: string): Promise<ConfigResponse>;
|
|
47
105
|
/** Create a PagesApi client */
|
|
48
106
|
getNitroPages(): PagesApi;
|
|
49
107
|
/** Create an EntitiesApi client */
|
|
@@ -52,10 +110,17 @@ interface FlyoInstance {
|
|
|
52
110
|
getNitroSitemap(): SitemapApi;
|
|
53
111
|
/** Create a SearchApi client */
|
|
54
112
|
getNitroSearch(): SearchApi;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve the active request locale from the `x-flyo-locale` header (set by the
|
|
115
|
+
* proxy middleware), falling back to `defaultLocale`. Use it inside entity
|
|
116
|
+
* resolvers or to set `<html lang>`.
|
|
117
|
+
*/
|
|
118
|
+
getRequestLocale(): Promise<string | undefined>;
|
|
55
119
|
/** Resolve a page from catch-all route params (React-cached per request) */
|
|
56
120
|
pageResolveRoute(props: RouteParams): Promise<{
|
|
57
121
|
page: Page;
|
|
58
122
|
path: string;
|
|
123
|
+
lang: string | undefined;
|
|
59
124
|
cfg: ConfigResponse;
|
|
60
125
|
}>;
|
|
61
126
|
/** Generate a Next.js sitemap from Flyo Nitro content */
|
|
@@ -82,9 +147,13 @@ interface FlyoInstance {
|
|
|
82
147
|
* });
|
|
83
148
|
* ```
|
|
84
149
|
*/
|
|
85
|
-
declare function initNitro({ accessToken, lang, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
|
|
150
|
+
declare function initNitro({ accessToken, lang, locales, defaultLocale, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
|
|
86
151
|
accessToken: string;
|
|
87
152
|
lang?: string;
|
|
153
|
+
/** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */
|
|
154
|
+
locales?: string[];
|
|
155
|
+
/** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */
|
|
156
|
+
defaultLocale?: string;
|
|
88
157
|
baseUrl?: string;
|
|
89
158
|
components?: Record<string, any>;
|
|
90
159
|
showMissingComponentAlert?: boolean;
|
|
@@ -232,4 +301,4 @@ declare function nitroEntityGenerateMetadata<T = any>(flyo: FlyoInstance, option
|
|
|
232
301
|
resolver: EntityResolver<T>;
|
|
233
302
|
}): (props: EntityRouteParams<T>) => Promise<Metadata>;
|
|
234
303
|
|
|
235
|
-
export { type EntityResolver, type FlyoInstance, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute };
|
|
304
|
+
export { type EntityResolver, type FlyoInstance, type FlyoLanguageLink, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, getLanguageLinks, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute };
|
package/dist/server.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(server_exports, {
|
|
|
25
25
|
NitroEntityJsonLd: () => NitroEntityJsonLd,
|
|
26
26
|
NitroPage: () => NitroPage,
|
|
27
27
|
NitroSlot: () => NitroSlot,
|
|
28
|
+
getLanguageLinks: () => getLanguageLinks,
|
|
28
29
|
initNitro: () => initNitro,
|
|
29
30
|
nitroEntityGenerateMetadata: () => nitroEntityGenerateMetadata,
|
|
30
31
|
nitroEntityRoute: () => nitroEntityRoute,
|
|
@@ -35,11 +36,40 @@ __export(server_exports, {
|
|
|
35
36
|
module.exports = __toCommonJS(server_exports);
|
|
36
37
|
var import_react = require("react");
|
|
37
38
|
var import_navigation = require("next/navigation");
|
|
39
|
+
var import_headers = require("next/headers");
|
|
38
40
|
var import_nitro_typescript = require("@flyo/nitro-typescript");
|
|
41
|
+
|
|
42
|
+
// src/i18n.ts
|
|
43
|
+
function getLanguageLinks(translations, options) {
|
|
44
|
+
const currentLang = options?.currentLang;
|
|
45
|
+
const byShortcode = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const t of translations ?? []) {
|
|
47
|
+
const shortcode = t.language?.shortcode;
|
|
48
|
+
if (shortcode) {
|
|
49
|
+
byShortcode.set(shortcode, t);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const toLink = (shortcode, t) => ({
|
|
53
|
+
shortcode,
|
|
54
|
+
name: t?.language?.name,
|
|
55
|
+
href: t?.href ?? null,
|
|
56
|
+
title: t?.title,
|
|
57
|
+
isCurrent: shortcode === currentLang,
|
|
58
|
+
exists: t?.href != null
|
|
59
|
+
});
|
|
60
|
+
if (options?.locales && options.locales.length > 0) {
|
|
61
|
+
return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));
|
|
62
|
+
}
|
|
63
|
+
return (translations ?? []).filter((t) => t.language?.shortcode).map((t) => toLink(t.language.shortcode, t));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/server.tsx
|
|
39
67
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
68
|
function initNitro({
|
|
41
69
|
accessToken,
|
|
42
70
|
lang,
|
|
71
|
+
locales,
|
|
72
|
+
defaultLocale,
|
|
43
73
|
baseUrl,
|
|
44
74
|
components,
|
|
45
75
|
showMissingComponentAlert,
|
|
@@ -48,9 +78,13 @@ function initNitro({
|
|
|
48
78
|
clientCacheTtl
|
|
49
79
|
}) {
|
|
50
80
|
const configuration = new import_nitro_typescript.Configuration({ apiKey: accessToken });
|
|
81
|
+
const resolvedDefaultLocale = defaultLocale ?? lang ?? null;
|
|
82
|
+
const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);
|
|
51
83
|
const state = {
|
|
52
84
|
accessToken,
|
|
53
85
|
lang: lang ?? null,
|
|
86
|
+
locales: resolvedLocales,
|
|
87
|
+
defaultLocale: resolvedDefaultLocale,
|
|
54
88
|
baseUrl: baseUrl ?? null,
|
|
55
89
|
components: components ?? {},
|
|
56
90
|
showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,
|
|
@@ -58,26 +92,43 @@ function initNitro({
|
|
|
58
92
|
serverCacheTtl: serverCacheTtl ?? 1200,
|
|
59
93
|
clientCacheTtl: clientCacheTtl ?? 900
|
|
60
94
|
};
|
|
61
|
-
const
|
|
95
|
+
const getRequestLocale = async () => {
|
|
96
|
+
try {
|
|
97
|
+
const requestHeaders = await (0, import_headers.headers)();
|
|
98
|
+
const headerLocale = requestHeaders.get("x-flyo-locale");
|
|
99
|
+
if (headerLocale) {
|
|
100
|
+
return headerLocale;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
return state.defaultLocale ?? state.lang ?? void 0;
|
|
105
|
+
};
|
|
106
|
+
const fetchConfig = (0, import_react.cache)(async (lang2) => {
|
|
62
107
|
const configApi = new import_nitro_typescript.ConfigApi(configuration);
|
|
63
|
-
|
|
64
|
-
return configApi.config({ lang: useLang });
|
|
108
|
+
return configApi.config({ lang: lang2 ?? void 0 });
|
|
65
109
|
});
|
|
110
|
+
const getNitroConfig = async (lang2) => {
|
|
111
|
+
const useLang = lang2 ?? await getRequestLocale();
|
|
112
|
+
return fetchConfig(useLang);
|
|
113
|
+
};
|
|
66
114
|
const pageResolveRoute = (0, import_react.cache)(async ({ params }) => {
|
|
67
115
|
const { slug } = await params;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
116
|
+
const segments = slug ?? [];
|
|
117
|
+
const path = segments.join("/");
|
|
118
|
+
const firstSegment = segments[0];
|
|
119
|
+
const lang2 = firstSegment && state.locales.includes(firstSegment) ? firstSegment : state.defaultLocale ?? void 0;
|
|
120
|
+
const cfg = await getNitroConfig(lang2);
|
|
70
121
|
if (!cfg.pages?.includes(path)) {
|
|
71
122
|
(0, import_navigation.notFound)();
|
|
72
123
|
}
|
|
73
|
-
const page = await new import_nitro_typescript.PagesApi(configuration).page({ slug: path }).catch((error) => {
|
|
124
|
+
const page = await new import_nitro_typescript.PagesApi(configuration).page({ slug: path, lang: lang2 }).catch((error) => {
|
|
74
125
|
console.error("Error fetching page:", path, error);
|
|
75
126
|
(0, import_navigation.notFound)();
|
|
76
127
|
});
|
|
77
128
|
if (!page) {
|
|
78
129
|
(0, import_navigation.notFound)();
|
|
79
130
|
}
|
|
80
|
-
return { page, path, cfg };
|
|
131
|
+
return { page, path, lang: lang2, cfg };
|
|
81
132
|
});
|
|
82
133
|
const sitemap = async () => {
|
|
83
134
|
const sitemapApi = new import_nitro_typescript.SitemapApi(configuration);
|
|
@@ -108,6 +159,7 @@ function initNitro({
|
|
|
108
159
|
return {
|
|
109
160
|
state,
|
|
110
161
|
getNitroConfig,
|
|
162
|
+
getRequestLocale,
|
|
111
163
|
getNitroPages: () => new import_nitro_typescript.PagesApi(configuration),
|
|
112
164
|
getNitroEntities: () => new import_nitro_typescript.EntitiesApi(configuration),
|
|
113
165
|
getNitroSitemap: () => new import_nitro_typescript.SitemapApi(configuration),
|
|
@@ -123,6 +175,23 @@ var readEnv = (key, fallback = "") => {
|
|
|
123
175
|
}
|
|
124
176
|
return fallback;
|
|
125
177
|
};
|
|
178
|
+
function buildLanguageAlternates(translations, currentLang) {
|
|
179
|
+
const languages = {};
|
|
180
|
+
let canonical;
|
|
181
|
+
for (const t of translations ?? []) {
|
|
182
|
+
const shortcode = t.language?.shortcode;
|
|
183
|
+
if (shortcode && t.href) {
|
|
184
|
+
languages[shortcode] = t.href;
|
|
185
|
+
if (currentLang && shortcode === currentLang) {
|
|
186
|
+
canonical = t.href;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (Object.keys(languages).length === 0) {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
return canonical ? { canonical, languages } : { languages };
|
|
194
|
+
}
|
|
126
195
|
function createCachedEntityResolver(resolver) {
|
|
127
196
|
return (0, import_react.cache)(async ({ params }) => {
|
|
128
197
|
const entity = await resolver(params);
|
|
@@ -245,16 +314,18 @@ function nitroPageRoute(flyo) {
|
|
|
245
314
|
}
|
|
246
315
|
function nitroPageGenerateMetadata(flyo) {
|
|
247
316
|
return async (props) => {
|
|
248
|
-
const { page } = await flyo.pageResolveRoute(props);
|
|
317
|
+
const { page, lang } = await flyo.pageResolveRoute(props);
|
|
249
318
|
const meta = page.meta_json;
|
|
250
319
|
const title = meta?.title || page.title || "";
|
|
251
320
|
const description = meta?.description ?? "";
|
|
252
321
|
const image = meta?.image ?? "";
|
|
253
322
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
254
323
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
324
|
+
const alternates = buildLanguageAlternates(page.translation, lang);
|
|
255
325
|
return {
|
|
256
326
|
title,
|
|
257
327
|
description,
|
|
328
|
+
...alternates ? { alternates } : {},
|
|
258
329
|
openGraph: {
|
|
259
330
|
title,
|
|
260
331
|
description,
|
|
@@ -299,9 +370,11 @@ function nitroEntityGenerateMetadata(flyo, options) {
|
|
|
299
370
|
const image = entity.entity?.entity_image ?? "";
|
|
300
371
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
301
372
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
373
|
+
const alternates = buildLanguageAlternates(entity.translation, entity.language);
|
|
302
374
|
return {
|
|
303
375
|
title,
|
|
304
376
|
description,
|
|
377
|
+
...alternates ? { alternates } : {},
|
|
305
378
|
openGraph: {
|
|
306
379
|
title,
|
|
307
380
|
description,
|
|
@@ -324,6 +397,7 @@ function nitroEntityGenerateMetadata(flyo, options) {
|
|
|
324
397
|
NitroEntityJsonLd,
|
|
325
398
|
NitroPage,
|
|
326
399
|
NitroSlot,
|
|
400
|
+
getLanguageLinks,
|
|
327
401
|
initNitro,
|
|
328
402
|
nitroEntityGenerateMetadata,
|
|
329
403
|
nitroEntityRoute,
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.tsx"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi\n} from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n readonly lang: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /** Fetch and cache the Nitro CMS configuration (React-cached per request) */\n getNitroConfig(): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n const useLang = state.lang ?? undefined;\n return configApi.config({ lang: useLang });\n });\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const path = slug?.join('/') ?? '';\n\n const cfg = await getNitroConfig();\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n return { page, path, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Internal helper to wrap and cache entity resolvers\n */\nfunction createCachedEntityResolver<T>(\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.tsx","../src/i18n.ts"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport { headers } from 'next/headers';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi,\n type Translation\n} from '@flyo/nitro-typescript';\n\n// Re-export the framework-agnostic language-links helper + types so they are\n// available from `@flyo/nitro-next/server` alongside the rest of the server API.\nexport { getLanguageLinks } from './i18n';\nexport type { FlyoLanguageLink } from './i18n';\nexport type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n /** The single default language (back-compat). Prefer `defaultLocale` for i18n. */\n readonly lang: string | null;\n /** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */\n readonly locales: string[];\n /** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */\n readonly defaultLocale: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /**\n * Fetch and cache the Nitro CMS configuration (React-cached per requested locale).\n * When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`\n * header set by the proxy), falling back to `defaultLocale`.\n */\n getNitroConfig(lang?: string): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /**\n * Resolve the active request locale from the `x-flyo-locale` header (set by the\n * proxy middleware), falling back to `defaultLocale`. Use it inside entity\n * resolvers or to set `<html lang>`.\n */\n getRequestLocale(): Promise<string | undefined>;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; lang: string | undefined; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n locales,\n defaultLocale,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n /** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */\n locales?: string[];\n /** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */\n defaultLocale?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const resolvedDefaultLocale = defaultLocale ?? lang ?? null;\n const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n locales: resolvedLocales,\n defaultLocale: resolvedDefaultLocale,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getRequestLocale = async (): Promise<string | undefined> => {\n try {\n const requestHeaders = await headers();\n const headerLocale = requestHeaders.get('x-flyo-locale');\n if (headerLocale) {\n return headerLocale;\n }\n } catch {\n // `headers()` throws outside a request scope (build time, tests, …) — fall back below.\n }\n return state.defaultLocale ?? state.lang ?? undefined;\n };\n\n // React-cached per requested locale, so different languages don't collide on\n // one memoized value and the same locale is only fetched once per request.\n const fetchConfig = cache(async (lang?: string): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n return configApi.config({ lang: lang ?? undefined });\n });\n\n const getNitroConfig = async (lang?: string): Promise<ConfigResponse> => {\n const useLang = lang ?? (await getRequestLocale());\n return fetchConfig(useLang);\n };\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const segments = slug ?? [];\n // Pages are addressed by their full, globally-unique slug (locale prefix\n // included), which is exactly what the catch-all captures — pass it through.\n const path = segments.join('/');\n\n // Derive the active locale from the leading path segment when it is a\n // configured locale (matches the Laravel adapter's `request()->segment(1)`).\n const firstSegment = segments[0];\n const lang = firstSegment && state.locales.includes(firstSegment)\n ? firstSegment\n : (state.defaultLocale ?? undefined);\n\n const cfg = await getNitroConfig(lang);\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path, lang })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n return { page, path, lang, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getRequestLocale,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Build Next.js `alternates` (hreflang + canonical) from a page's or entity's\n * `translation[]`. Returns `undefined` when there are no linked translations.\n */\nfunction buildLanguageAlternates(\n translations: Translation[] | undefined,\n currentLang?: string,\n): Metadata['alternates'] | undefined {\n const languages: Record<string, string> = {};\n let canonical: string | undefined;\n\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode && t.href) {\n languages[shortcode] = t.href;\n if (currentLang && shortcode === currentLang) {\n canonical = t.href;\n }\n }\n }\n\n if (Object.keys(languages).length === 0) {\n return undefined;\n }\n\n return canonical ? { canonical, languages } : { languages };\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers\n */\nfunction createCachedEntityResolver<T>(\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page, lang } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(page.translation, lang);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(entity.translation, entity.language);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}","import type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * A single language option for a language switcher, derived from a page's or\n * entity's `translation[]`. Framework-agnostic plain data — render it however\n * you like.\n */\nexport interface FlyoLanguageLink {\n /** Locale shortcode, e.g. `\"de\"`. */\n shortcode: string;\n /** Full language name from the CMS translation, e.g. `\"Deutsch\"` (when available). */\n name?: string;\n /** Fully-resolved localized href, or `null` when this locale has no linked translation. */\n href: string | null;\n /** Localized page/entity title, when available. */\n title?: string;\n /** `true` when this entry is the currently active locale. */\n isCurrent: boolean;\n /** `true` when a linked translation actually exists for this locale. */\n exists: boolean;\n}\n\n/**\n * Map a page's or entity's `translation[]` into a switcher-ready array of typed\n * links — the building block for a language switcher.\n *\n * Flyo only returns `translation` entries for languages that actually have a\n * translation. Pass `options.locales` (e.g. `flyo.state.locales`) to also get an\n * entry for every configured locale that is *missing* a translation — those come\n * back as `{ href: null, exists: false }` so you can render a fallback (a disabled\n * item, a link to the home page, …).\n *\n * Pure — no React or server-only APIs, so it is safe to call from server *or*\n * client components.\n *\n * @example\n * ```tsx\n * const links = getLanguageLinks(page.translation, {\n * currentLang: lang,\n * locales: flyo.state.locales,\n * });\n * // links.map(l => l.exists\n * // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>\n * // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)\n * ```\n */\nexport function getLanguageLinks(\n translations: Translation[] | undefined,\n options?: { currentLang?: string; locales?: string[] },\n): FlyoLanguageLink[] {\n const currentLang = options?.currentLang;\n\n const byShortcode = new Map<string, Translation>();\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode) {\n byShortcode.set(shortcode, t);\n }\n }\n\n const toLink = (shortcode: string, t: Translation | undefined): FlyoLanguageLink => ({\n shortcode,\n name: t?.language?.name,\n href: t?.href ?? null,\n title: t?.title,\n isCurrent: shortcode === currentLang,\n exists: t?.href != null,\n });\n\n // When the full set of locales is known, emit an entry for every one so\n // callers can render fallbacks for languages that have no translation.\n if (options?.locales && options.locales.length > 0) {\n return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));\n }\n\n // Otherwise, just surface the translations that exist.\n return (translations ?? [])\n .filter((t) => t.language?.shortcode)\n .map((t) => toLink(t.language!.shortcode!, t));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAsB;AAEtB,wBAAyB;AACzB,qBAAwB;AACxB,8BAYO;;;AC8BA,SAAS,iBACd,cACA,SACoB;AACpB,QAAM,cAAc,SAAS;AAE7B,QAAM,cAAc,oBAAI,IAAyB;AACjD,aAAW,KAAK,gBAAgB,CAAC,GAAG;AAClC,UAAM,YAAY,EAAE,UAAU;AAC9B,QAAI,WAAW;AACb,kBAAY,IAAI,WAAW,CAAC;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,WAAmB,OAAkD;AAAA,IACnF;AAAA,IACA,MAAM,GAAG,UAAU;AAAA,IACnB,MAAM,GAAG,QAAQ;AAAA,IACjB,OAAO,GAAG;AAAA,IACV,WAAW,cAAc;AAAA,IACzB,QAAQ,GAAG,QAAQ;AAAA,EACrB;AAIA,MAAI,SAAS,WAAW,QAAQ,QAAQ,SAAS,GAAG;AAClD,WAAO,QAAQ,QAAQ,IAAI,CAAC,cAAc,OAAO,WAAW,YAAY,IAAI,SAAS,CAAC,CAAC;AAAA,EACzF;AAGA,UAAQ,gBAAgB,CAAC,GACtB,OAAO,CAAC,MAAM,EAAE,UAAU,SAAS,EACnC,IAAI,CAAC,MAAM,OAAO,EAAE,SAAU,WAAY,CAAC,CAAC;AACjD;;;ADsTM;AA7QC,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAciB;AACf,QAAM,gBAAgB,IAAI,sCAAc,EAAE,QAAQ,YAAY,CAAC;AAE/D,QAAM,wBAAwB,iBAAiB,QAAQ;AACvD,QAAM,kBAAkB,YAAY,wBAAwB,CAAC,qBAAqB,IAAI,CAAC;AAEvF,QAAM,QAAoB;AAAA,IACxB;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,SAAS;AAAA,IACT,eAAe;AAAA,IACf,SAAS,WAAW;AAAA,IACpB,YAAY,cAAc,CAAC;AAAA,IAC3B,2BAA2B,6BAA6B,YAAY;AAAA,IACpE,UAAU,YAAY;AAAA,IACtB,gBAAgB,kBAAkB;AAAA,IAClC,gBAAgB,kBAAkB;AAAA,EACpC;AAEA,QAAM,mBAAmB,YAAyC;AAChE,QAAI;AACF,YAAM,iBAAiB,UAAM,wBAAQ;AACrC,YAAM,eAAe,eAAe,IAAI,eAAe;AACvD,UAAI,cAAc;AAChB,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,MAAM,iBAAiB,MAAM,QAAQ;AAAA,EAC9C;AAIA,QAAM,kBAAc,oBAAM,OAAOA,UAA2C;AAC1E,UAAM,YAAY,IAAI,kCAAU,aAAa;AAC7C,WAAO,UAAU,OAAO,EAAE,MAAMA,SAAQ,OAAU,CAAC;AAAA,EACrD,CAAC;AAED,QAAM,iBAAiB,OAAOA,UAA2C;AACvE,UAAM,UAAUA,SAAS,MAAM,iBAAiB;AAChD,WAAO,YAAY,OAAO;AAAA,EAC5B;AAEA,QAAM,uBAAmB,oBAAM,OAAO,EAAE,OAAO,MAAmB;AAChE,UAAM,EAAE,KAAK,IAAI,MAAM;AACvB,UAAM,WAAW,QAAQ,CAAC;AAG1B,UAAM,OAAO,SAAS,KAAK,GAAG;AAI9B,UAAM,eAAe,SAAS,CAAC;AAC/B,UAAMA,QAAO,gBAAgB,MAAM,QAAQ,SAAS,YAAY,IAC5D,eACC,MAAM,iBAAiB;AAE5B,UAAM,MAAM,MAAM,eAAeA,KAAI;AAErC,QAAI,CAAC,IAAI,OAAO,SAAS,IAAI,GAAG;AAC9B,sCAAS;AAAA,IACX;AAEA,UAAM,OAAO,MAAM,IAAI,iCAAS,aAAa,EAC1C,KAAK,EAAE,MAAM,MAAM,MAAAA,MAAK,CAAC,EACzB,MAAM,CAAC,UAAmB;AACzB,cAAQ,MAAM,wBAAwB,MAAM,KAAK;AACjD,sCAAS;AAAA,IACX,CAAC;AAEH,QAAI,CAAC,MAAM;AACT,sCAAS;AAAA,IACX;AAEA,WAAO,EAAE,MAAM,MAAM,MAAAA,OAAM,IAAI;AAAA,EACjC,CAAC;AAED,QAAM,UAAU,YAA4C;AAC1D,UAAM,aAAa,IAAI,mCAAW,aAAa;AAC/C,UAAM,cAAc,MAAM,QAAQ;AAElC,QAAI,CAAC,MAAM,SAAS;AAClB,YAAM,IAAI,MAAM,0DAA0D;AAAA,IAC5E;AAEA,UAAM,QAAQ,MAAM,WAAW,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC5D,UAAM,eAAe,MAAM,QAAQ,SAAS,GAAG,IAAI,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAEtF,WAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAI,OAAO;AAEX,UAAI,KAAK,UAAU,OAAO,KAAK,WAAW,UAAU;AAClD,cAAM,cAAc,OAAO,OAAO,KAAK,MAAM;AAC7C,YAAI,YAAY,SAAS,GAAG;AAC1B,iBAAO,YAAY,CAAC;AAAA,QACtB;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,KAAK,aAAa;AAC7B,eAAO,KAAK;AAAA,MACd;AAEA,YAAM,YAAY,QAAQ,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,IAAI,KAAK;AAE/D,aAAO;AAAA,QACL,KAAK,GAAG,YAAY,GAAG,SAAS;AAAA,QAChC,cAAc,oBAAI,KAAK;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,MAAM,IAAI,iCAAS,aAAa;AAAA,IAC/C,kBAAkB,MAAM,IAAI,oCAAY,aAAa;AAAA,IACrD,iBAAiB,MAAM,IAAI,mCAAW,aAAa;AAAA,IACnD,gBAAgB,MAAM,IAAI,kCAAU,aAAa;AAAA,IACjD;AAAA,IACA;AAAA,EACF;AACF;AAOA,IAAM,UAAU,CAAC,KAAa,WAAW,OAAe;AACtD,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,UAAa,UAAU,IAAI;AACvC,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AAMA,SAAS,wBACP,cACA,aACoC;AACpC,QAAM,YAAoC,CAAC;AAC3C,MAAI;AAEJ,aAAW,KAAK,gBAAgB,CAAC,GAAG;AAClC,UAAM,YAAY,EAAE,UAAU;AAC9B,QAAI,aAAa,EAAE,MAAM;AACvB,gBAAU,SAAS,IAAI,EAAE;AACzB,UAAI,eAAe,cAAc,aAAa;AAC5C,oBAAY,EAAE;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACvC,WAAO;AAAA,EACT;AAEA,SAAO,YAAY,EAAE,WAAW,UAAU,IAAI,EAAE,UAAU;AAC5D;AAKA,SAAS,2BACP,UACkD;AAClD,aAAO,oBAAM,OAAO,EAAE,OAAO,MAA4B;AACvD,UAAM,SAAS,MAAM,SAAS,MAAM;AAEpC,QAAI,CAAC,QAAQ;AACX,sCAAS;AAAA,IACX;AAEA,WAAO;AAAA,EACT,CAAC;AACH;AAeA,eAAsB,eAAe,EAAE,KAAK,GAA2B;AACrE,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,eAAe;AACzC,UAAM,EAAE,MAAM,IAAI;AAElB,UAAM,OAAO,QAAQ,YAAY,GAAG;AACpC,UAAM,qBAAqB,QAAQ,wBAAwB,GAAG;AAC9D,UAAM,qBAAqB,QAAQ,yBAAyB,GAAG;AAC/D,UAAM,UAAU,QAAQ,WAAW,EAAE;AAErC,UAAM,aAAa,MAAM,eAAe;AACxC,UAAM,QAAQ,OAAO,eAAe,WAAW,aAAa;AAC5D,UAAM,YAAY,MAAM,WAAW,IAAI,IACnC,eACA,MAAM,WAAW,IAAI,IACrB,YACA;AAEJ,UAAM,QAAQ,MAAM;AAEpB,UAAM,aAAa,OAAO,OAAO,SAAS,SAAS,KAAK;AACxD,UAAM,gBAAgB,OAAO,OAAO,aAChC,IAAI,KAAK,OAAO,MAAM,aAAa,GAAI,EAAE,eAAe,SAAS;AAAA,MAC/D,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC,IACD;AAEJ,UAAM,iBAAiB;AAAA,MACrB,YAAY,KAAK;AAAA,MACjB,OAAO,IAAI;AAAA,MACX,WAAW,UAAU;AAAA,MACrB,eAAe,aAAa;AAAA,MAC5B,aAAa,SAAS;AAAA,MACtB,OAAO,kBAAkB;AAAA,MACzB,QAAQ,kBAAkB;AAAA,IAC5B;AAEA,QAAI,SAAS;AACX,qBAAe,KAAK,WAAW,OAAO,EAAE;AAAA,IAC1C;AAEA,UAAM,YAAY,eAAe,KAAK,KAAK;AAE3C,WACE,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,QAAQ,SAAS,OAAO,GAAG,0BAAwB,MAAC;AAAA,EAErG,QAAQ;AACN,WAAO,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,wCAAwC,GAAG,0BAAwB,MAAC;AAAA,EAC1H;AACF;AAMO,SAAS,kBAAkB,EAAE,OAAO,GAAuB;AAChE,MAAI,CAAC,QAAQ,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,yBAAyB;AAAA,QACvB,QAAQ,KAAK,UAAU,OAAO,MAAM,EAAE,QAAQ,MAAM,SAAS;AAAA,MAC/D;AAAA;AAAA,EACF;AAEJ;AAKO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAGG;AACD,MAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,SACE,2EACG,eAAK,KAAK,IAAI,CAAC,OAAc,UAC5B;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA;AAAA,IAFK,MAAM,OAAO;AAAA,EAGpB,CACD,GACH;AAEJ;AAKO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,YAAY,KAAK,MAAM,WAAW,MAAM,SAAS,IAAI;AAE7E,MAAI,WAAW;AACb,WAAO,4CAAC,aAAU,OAAc;AAAA,EAClC;AAEA,MAAI,KAAK,MAAM,2BAA2B;AACxC,WACE,6CAAC,SAAI,OAAO,EAAE,QAAQ,kBAAkB,SAAS,QAAQ,cAAc,QAAQ,iBAAiB,MAAM,GAAG;AAAA;AAAA,MAC7F,4CAAC,OAAG,gBAAM,WAAU;AAAA,MAAI;AAAA,OACpC;AAAA,EAEJ;AAEA,SAAO;AACT;AAoBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAKG;AACD,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,GAAG;AAClD,WAAO;AAAA,EACT;AAEA,SACE,2EACG,eAAK,QAAQ,IAAI,CAAC,OAAc,UAC/B;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA;AAAA,IAFK,MAAM,OAAO;AAAA,EAGpB,CACD,GACH;AAEJ;AAgBO,SAAS,eAAe,MAAoB;AACjD,iBAAe,UAAU,OAAoB;AAC3C,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,iBAAiB,KAAK;AAClD,WAAO,4CAAC,aAAU,MAAY,MAAY;AAAA,EAC5C;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MAAoB;AAC5D,SAAO,OAAO,UAA0C;AACtD,UAAM,EAAE,MAAM,KAAK,IAAI,MAAM,KAAK,iBAAiB,KAAK;AAExD,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,MAAM,SAAS,KAAK,SAAS;AAC3C,UAAM,cAAc,MAAM,eAAe;AACzC,UAAM,QAAQ,MAAM,SAAS;AAE7B,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,UAAM,aAAa,wBAAwB,KAAK,aAAa,IAAI;AAEjE,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,MACnC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,QAC/B,MAAM;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;AAcO,SAAS,8BAA8B,MAAoB;AAChE,SAAO,YAAY;AACjB,UAAM,MAAM,MAAM,KAAK,eAAe;AACtC,UAAM,QAAQ,IAAI,SAAS,CAAC;AAE5B,WAAO,MAAM,IAAI,CAAC,UAAkB;AAAA,MAClC,MAAM,SAAS,KAAK,SAAY,KAAK,MAAM,GAAG;AAAA,IAChD,EAAE;AAAA,EACJ;AACF;AAuBO,SAAS,iBACd,MACA,SAIA;AACA,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAElE,iBAAe,YAAY,OAA6B;AACtD,UAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,QAAI,QAAQ,QAAQ;AAClB,aAAO,QAAQ,OAAO,MAAM;AAAA,IAC9B;AAEA,WAAO,4CAAC,SAAK,iBAAO,QAAQ,cAAa;AAAA,EAC3C;AACA,SAAO;AACT;AAeO,SAAS,4BACd,MACA,SAGA;AACA,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAElE,SAAO,OAAO,UAAmD;AAC/D,UAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,UAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAC7C,UAAM,cAAc,OAAO,QAAQ,iBAAiB;AACpD,UAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAE7C,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,UAAM,aAAa,wBAAwB,OAAO,aAAa,OAAO,QAAQ;AAE9E,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,MACnC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,QAC/B,MAAM;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;","names":["lang"]}
|
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/server.tsx
|
|
2
2
|
import { cache } from "react";
|
|
3
3
|
import { notFound } from "next/navigation";
|
|
4
|
+
import { headers } from "next/headers";
|
|
4
5
|
import {
|
|
5
6
|
ConfigApi,
|
|
6
7
|
Configuration,
|
|
@@ -9,10 +10,38 @@ import {
|
|
|
9
10
|
SitemapApi,
|
|
10
11
|
SearchApi
|
|
11
12
|
} from "@flyo/nitro-typescript";
|
|
13
|
+
|
|
14
|
+
// src/i18n.ts
|
|
15
|
+
function getLanguageLinks(translations, options) {
|
|
16
|
+
const currentLang = options?.currentLang;
|
|
17
|
+
const byShortcode = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const t of translations ?? []) {
|
|
19
|
+
const shortcode = t.language?.shortcode;
|
|
20
|
+
if (shortcode) {
|
|
21
|
+
byShortcode.set(shortcode, t);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const toLink = (shortcode, t) => ({
|
|
25
|
+
shortcode,
|
|
26
|
+
name: t?.language?.name,
|
|
27
|
+
href: t?.href ?? null,
|
|
28
|
+
title: t?.title,
|
|
29
|
+
isCurrent: shortcode === currentLang,
|
|
30
|
+
exists: t?.href != null
|
|
31
|
+
});
|
|
32
|
+
if (options?.locales && options.locales.length > 0) {
|
|
33
|
+
return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));
|
|
34
|
+
}
|
|
35
|
+
return (translations ?? []).filter((t) => t.language?.shortcode).map((t) => toLink(t.language.shortcode, t));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/server.tsx
|
|
12
39
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
40
|
function initNitro({
|
|
14
41
|
accessToken,
|
|
15
42
|
lang,
|
|
43
|
+
locales,
|
|
44
|
+
defaultLocale,
|
|
16
45
|
baseUrl,
|
|
17
46
|
components,
|
|
18
47
|
showMissingComponentAlert,
|
|
@@ -21,9 +50,13 @@ function initNitro({
|
|
|
21
50
|
clientCacheTtl
|
|
22
51
|
}) {
|
|
23
52
|
const configuration = new Configuration({ apiKey: accessToken });
|
|
53
|
+
const resolvedDefaultLocale = defaultLocale ?? lang ?? null;
|
|
54
|
+
const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);
|
|
24
55
|
const state = {
|
|
25
56
|
accessToken,
|
|
26
57
|
lang: lang ?? null,
|
|
58
|
+
locales: resolvedLocales,
|
|
59
|
+
defaultLocale: resolvedDefaultLocale,
|
|
27
60
|
baseUrl: baseUrl ?? null,
|
|
28
61
|
components: components ?? {},
|
|
29
62
|
showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,
|
|
@@ -31,26 +64,43 @@ function initNitro({
|
|
|
31
64
|
serverCacheTtl: serverCacheTtl ?? 1200,
|
|
32
65
|
clientCacheTtl: clientCacheTtl ?? 900
|
|
33
66
|
};
|
|
34
|
-
const
|
|
67
|
+
const getRequestLocale = async () => {
|
|
68
|
+
try {
|
|
69
|
+
const requestHeaders = await headers();
|
|
70
|
+
const headerLocale = requestHeaders.get("x-flyo-locale");
|
|
71
|
+
if (headerLocale) {
|
|
72
|
+
return headerLocale;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return state.defaultLocale ?? state.lang ?? void 0;
|
|
77
|
+
};
|
|
78
|
+
const fetchConfig = cache(async (lang2) => {
|
|
35
79
|
const configApi = new ConfigApi(configuration);
|
|
36
|
-
|
|
37
|
-
return configApi.config({ lang: useLang });
|
|
80
|
+
return configApi.config({ lang: lang2 ?? void 0 });
|
|
38
81
|
});
|
|
82
|
+
const getNitroConfig = async (lang2) => {
|
|
83
|
+
const useLang = lang2 ?? await getRequestLocale();
|
|
84
|
+
return fetchConfig(useLang);
|
|
85
|
+
};
|
|
39
86
|
const pageResolveRoute = cache(async ({ params }) => {
|
|
40
87
|
const { slug } = await params;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
88
|
+
const segments = slug ?? [];
|
|
89
|
+
const path = segments.join("/");
|
|
90
|
+
const firstSegment = segments[0];
|
|
91
|
+
const lang2 = firstSegment && state.locales.includes(firstSegment) ? firstSegment : state.defaultLocale ?? void 0;
|
|
92
|
+
const cfg = await getNitroConfig(lang2);
|
|
43
93
|
if (!cfg.pages?.includes(path)) {
|
|
44
94
|
notFound();
|
|
45
95
|
}
|
|
46
|
-
const page = await new PagesApi(configuration).page({ slug: path }).catch((error) => {
|
|
96
|
+
const page = await new PagesApi(configuration).page({ slug: path, lang: lang2 }).catch((error) => {
|
|
47
97
|
console.error("Error fetching page:", path, error);
|
|
48
98
|
notFound();
|
|
49
99
|
});
|
|
50
100
|
if (!page) {
|
|
51
101
|
notFound();
|
|
52
102
|
}
|
|
53
|
-
return { page, path, cfg };
|
|
103
|
+
return { page, path, lang: lang2, cfg };
|
|
54
104
|
});
|
|
55
105
|
const sitemap = async () => {
|
|
56
106
|
const sitemapApi = new SitemapApi(configuration);
|
|
@@ -81,6 +131,7 @@ function initNitro({
|
|
|
81
131
|
return {
|
|
82
132
|
state,
|
|
83
133
|
getNitroConfig,
|
|
134
|
+
getRequestLocale,
|
|
84
135
|
getNitroPages: () => new PagesApi(configuration),
|
|
85
136
|
getNitroEntities: () => new EntitiesApi(configuration),
|
|
86
137
|
getNitroSitemap: () => new SitemapApi(configuration),
|
|
@@ -96,6 +147,23 @@ var readEnv = (key, fallback = "") => {
|
|
|
96
147
|
}
|
|
97
148
|
return fallback;
|
|
98
149
|
};
|
|
150
|
+
function buildLanguageAlternates(translations, currentLang) {
|
|
151
|
+
const languages = {};
|
|
152
|
+
let canonical;
|
|
153
|
+
for (const t of translations ?? []) {
|
|
154
|
+
const shortcode = t.language?.shortcode;
|
|
155
|
+
if (shortcode && t.href) {
|
|
156
|
+
languages[shortcode] = t.href;
|
|
157
|
+
if (currentLang && shortcode === currentLang) {
|
|
158
|
+
canonical = t.href;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (Object.keys(languages).length === 0) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return canonical ? { canonical, languages } : { languages };
|
|
166
|
+
}
|
|
99
167
|
function createCachedEntityResolver(resolver) {
|
|
100
168
|
return cache(async ({ params }) => {
|
|
101
169
|
const entity = await resolver(params);
|
|
@@ -218,16 +286,18 @@ function nitroPageRoute(flyo) {
|
|
|
218
286
|
}
|
|
219
287
|
function nitroPageGenerateMetadata(flyo) {
|
|
220
288
|
return async (props) => {
|
|
221
|
-
const { page } = await flyo.pageResolveRoute(props);
|
|
289
|
+
const { page, lang } = await flyo.pageResolveRoute(props);
|
|
222
290
|
const meta = page.meta_json;
|
|
223
291
|
const title = meta?.title || page.title || "";
|
|
224
292
|
const description = meta?.description ?? "";
|
|
225
293
|
const image = meta?.image ?? "";
|
|
226
294
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
227
295
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
296
|
+
const alternates = buildLanguageAlternates(page.translation, lang);
|
|
228
297
|
return {
|
|
229
298
|
title,
|
|
230
299
|
description,
|
|
300
|
+
...alternates ? { alternates } : {},
|
|
231
301
|
openGraph: {
|
|
232
302
|
title,
|
|
233
303
|
description,
|
|
@@ -272,9 +342,11 @@ function nitroEntityGenerateMetadata(flyo, options) {
|
|
|
272
342
|
const image = entity.entity?.entity_image ?? "";
|
|
273
343
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
274
344
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
345
|
+
const alternates = buildLanguageAlternates(entity.translation, entity.language);
|
|
275
346
|
return {
|
|
276
347
|
title,
|
|
277
348
|
description,
|
|
349
|
+
...alternates ? { alternates } : {},
|
|
278
350
|
openGraph: {
|
|
279
351
|
title,
|
|
280
352
|
description,
|
|
@@ -296,6 +368,7 @@ export {
|
|
|
296
368
|
NitroEntityJsonLd,
|
|
297
369
|
NitroPage,
|
|
298
370
|
NitroSlot,
|
|
371
|
+
getLanguageLinks,
|
|
299
372
|
initNitro,
|
|
300
373
|
nitroEntityGenerateMetadata,
|
|
301
374
|
nitroEntityRoute,
|