@flyo/nitro-next 2.0.1 → 2.2.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/server.d.mts CHANGED
@@ -1,14 +1,91 @@
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
+ * This is a pure *data* helper — it maps a `translation[]` to switcher links and
40
+ * renders nothing. **The Flyo route helpers already call it for you and publish
41
+ * the result**, so page and entity routes need no switcher code at all; the
42
+ * footer just reads the store (see `publishLanguageLinks` / `readLanguageLinks`
43
+ * in `@flyo/nitro-next/server`). Call this directly only when you publish links
44
+ * by hand from a raw `translation[]` — e.g. on a custom route Flyo doesn't
45
+ * resolve:
46
+ *
47
+ * ```tsx
48
+ * getLanguageLinks(translations, { currentLang, locales });
49
+ * ```
50
+ *
51
+ * **Render each link as a native `<a>`, not `next/link`'s `<Link>`.** A language
52
+ * switch must refresh the shared chrome — the localized nav, footer and
53
+ * `<html lang>` — that lives in the root layout. In the Next.js App Router, soft
54
+ * (client-side) navigation re-renders only the page segment, *not* shared
55
+ * layouts, so a `<Link>` would leave that chrome in the previous language while
56
+ * only the page body updates. A plain `<a>` triggers a full-document navigation,
57
+ * forcing a fresh server render in the new locale so every part updates. (Regular
58
+ * nav links can stay `<Link>` — the nav is identical within a language.)
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * const links = getLanguageLinks(page.translation, {
63
+ * currentLang: lang,
64
+ * locales: flyo.state.locales,
65
+ * });
66
+ * // Native <a> (full-document nav) — NOT next/link, so shared layout chrome
67
+ * // re-renders in the new locale.
68
+ * // links.map(l => l.exists
69
+ * // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>
70
+ * // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)
71
+ * ```
72
+ */
73
+ declare function getLanguageLinks(translations: Translation[] | undefined, options?: {
74
+ currentLang?: string;
75
+ locales?: string[];
76
+ }): FlyoLanguageLink[];
5
77
 
6
78
  /**
7
79
  * Read-only configuration state
8
80
  */
9
81
  interface NitroState {
10
82
  readonly accessToken: string;
83
+ /** The single default language (back-compat). Prefer `defaultLocale` for i18n. */
11
84
  readonly lang: string | null;
85
+ /** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */
86
+ readonly locales: string[];
87
+ /** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */
88
+ readonly defaultLocale: string | null;
12
89
  readonly baseUrl: string | null;
13
90
  readonly components: Record<string, any>;
14
91
  readonly showMissingComponentAlert: boolean;
@@ -42,8 +119,12 @@ type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;
42
119
  interface FlyoInstance {
43
120
  /** Read-only configuration state */
44
121
  readonly state: NitroState;
45
- /** Fetch and cache the Nitro CMS configuration (React-cached per request) */
46
- getNitroConfig(): Promise<ConfigResponse>;
122
+ /**
123
+ * Fetch and cache the Nitro CMS configuration (React-cached per requested locale).
124
+ * When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`
125
+ * header set by the proxy), falling back to `defaultLocale`.
126
+ */
127
+ getNitroConfig(lang?: string): Promise<ConfigResponse>;
47
128
  /** Create a PagesApi client */
48
129
  getNitroPages(): PagesApi;
49
130
  /** Create an EntitiesApi client */
@@ -52,10 +133,17 @@ interface FlyoInstance {
52
133
  getNitroSitemap(): SitemapApi;
53
134
  /** Create a SearchApi client */
54
135
  getNitroSearch(): SearchApi;
136
+ /**
137
+ * Resolve the active request locale from the `x-flyo-locale` header (set by the
138
+ * proxy middleware), falling back to `defaultLocale`. Use it inside entity
139
+ * resolvers or to set `<html lang>`.
140
+ */
141
+ getRequestLocale(): Promise<string | undefined>;
55
142
  /** Resolve a page from catch-all route params (React-cached per request) */
56
143
  pageResolveRoute(props: RouteParams): Promise<{
57
144
  page: Page;
58
145
  path: string;
146
+ lang: string | undefined;
59
147
  cfg: ConfigResponse;
60
148
  }>;
61
149
  /** Generate a Next.js sitemap from Flyo Nitro content */
@@ -82,9 +170,13 @@ interface FlyoInstance {
82
170
  * });
83
171
  * ```
84
172
  */
85
- declare function initNitro({ accessToken, lang, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
173
+ declare function initNitro({ accessToken, lang, locales, defaultLocale, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
86
174
  accessToken: string;
87
175
  lang?: string;
176
+ /** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */
177
+ locales?: string[];
178
+ /** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */
179
+ defaultLocale?: string;
88
180
  baseUrl?: string;
89
181
  components?: Record<string, any>;
90
182
  showMissingComponentAlert?: boolean;
@@ -92,6 +184,63 @@ declare function initNitro({ accessToken, lang, baseUrl, components, showMissing
92
184
  serverCacheTtl?: number;
93
185
  clientCacheTtl?: number;
94
186
  }): FlyoInstance;
187
+ interface LanguageLinksStore {
188
+ /** Publish the links once. Later calls are ignored (first publish wins). */
189
+ publish(links: FlyoLanguageLink[]): void;
190
+ /** A promise that resolves when the links are published. */
191
+ read(): Promise<FlyoLanguageLink[]>;
192
+ }
193
+ /**
194
+ * Create a single deferred language-links channel: one writer, many readers,
195
+ * order-independent. Low-level — most code uses {@link publishLanguageLinks} /
196
+ * {@link readLanguageLinks}, which wrap this in a per-request `cache()`.
197
+ */
198
+ declare function createLanguageLinksStore(): LanguageLinksStore;
199
+ /**
200
+ * Publish the current route's language-switcher links into the request-scoped
201
+ * store, so a switcher rendered in shared chrome (e.g. a footer in the root
202
+ * layout) can read them via {@link readLanguageLinks}.
203
+ *
204
+ * The Flyo route helpers call this for you: `pageResolveRoute` (and therefore
205
+ * `nitroPageRoute`) publishes the page's links, and `nitroEntityRoute` /
206
+ * `nitroEntityGenerateMetadata` publish the entity's. You only need to call it
207
+ * yourself on routes that render the shared switcher **without** going through
208
+ * those helpers — a custom page, or `not-found.tsx` — where you would otherwise
209
+ * leave the footer waiting forever. Pass a fallback there, e.g.
210
+ * `publishLanguageLinks(getLanguageLinks(undefined, { currentLang, locales }))`.
211
+ *
212
+ * Only the first call per request takes effect.
213
+ */
214
+ declare function publishLanguageLinks(links: FlyoLanguageLink[]): void;
215
+ /**
216
+ * Await the current route's language-switcher links from shared chrome — a
217
+ * footer, header, or any component in the root layout. Resolves with whatever
218
+ * the active page/entity route passed to {@link publishLanguageLinks}.
219
+ *
220
+ * Because it awaits, the reader suspends until the links are published, so it
221
+ * works no matter whether the layout or the page renders first. Wrap the reading
222
+ * component in `<Suspense>` so the rest of the layout can stream while it waits.
223
+ *
224
+ * @example
225
+ * ```tsx
226
+ * // components/LanguageSwitcher.tsx (server component)
227
+ * import { readLanguageLinks } from '@flyo/nitro-next/server';
228
+ *
229
+ * export async function LanguageSwitcher() {
230
+ * const links = await readLanguageLinks();
231
+ * return (
232
+ * <nav aria-label="Language">
233
+ * {links.map((l) =>
234
+ * l.exists
235
+ * ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>
236
+ * : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>,
237
+ * )}
238
+ * </nav>
239
+ * );
240
+ * }
241
+ * ```
242
+ */
243
+ declare function readLanguageLinks(): Promise<FlyoLanguageLink[]>;
95
244
  /**
96
245
  * NitroDebugInfo Component
97
246
  *
@@ -232,4 +381,4 @@ declare function nitroEntityGenerateMetadata<T = any>(flyo: FlyoInstance, option
232
381
  resolver: EntityResolver<T>;
233
382
  }): (props: EntityRouteParams<T>) => Promise<Metadata>;
234
383
 
235
- export { type EntityResolver, type FlyoInstance, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute };
384
+ export { type EntityResolver, type FlyoInstance, type FlyoLanguageLink, type LanguageLinksStore, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, createLanguageLinksStore, getLanguageLinks, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, publishLanguageLinks, readLanguageLinks };
package/dist/server.d.ts CHANGED
@@ -1,14 +1,91 @@
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
+ * This is a pure *data* helper — it maps a `translation[]` to switcher links and
40
+ * renders nothing. **The Flyo route helpers already call it for you and publish
41
+ * the result**, so page and entity routes need no switcher code at all; the
42
+ * footer just reads the store (see `publishLanguageLinks` / `readLanguageLinks`
43
+ * in `@flyo/nitro-next/server`). Call this directly only when you publish links
44
+ * by hand from a raw `translation[]` — e.g. on a custom route Flyo doesn't
45
+ * resolve:
46
+ *
47
+ * ```tsx
48
+ * getLanguageLinks(translations, { currentLang, locales });
49
+ * ```
50
+ *
51
+ * **Render each link as a native `<a>`, not `next/link`'s `<Link>`.** A language
52
+ * switch must refresh the shared chrome — the localized nav, footer and
53
+ * `<html lang>` — that lives in the root layout. In the Next.js App Router, soft
54
+ * (client-side) navigation re-renders only the page segment, *not* shared
55
+ * layouts, so a `<Link>` would leave that chrome in the previous language while
56
+ * only the page body updates. A plain `<a>` triggers a full-document navigation,
57
+ * forcing a fresh server render in the new locale so every part updates. (Regular
58
+ * nav links can stay `<Link>` — the nav is identical within a language.)
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * const links = getLanguageLinks(page.translation, {
63
+ * currentLang: lang,
64
+ * locales: flyo.state.locales,
65
+ * });
66
+ * // Native <a> (full-document nav) — NOT next/link, so shared layout chrome
67
+ * // re-renders in the new locale.
68
+ * // links.map(l => l.exists
69
+ * // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>
70
+ * // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)
71
+ * ```
72
+ */
73
+ declare function getLanguageLinks(translations: Translation[] | undefined, options?: {
74
+ currentLang?: string;
75
+ locales?: string[];
76
+ }): FlyoLanguageLink[];
5
77
 
6
78
  /**
7
79
  * Read-only configuration state
8
80
  */
9
81
  interface NitroState {
10
82
  readonly accessToken: string;
83
+ /** The single default language (back-compat). Prefer `defaultLocale` for i18n. */
11
84
  readonly lang: string | null;
85
+ /** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */
86
+ readonly locales: string[];
87
+ /** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */
88
+ readonly defaultLocale: string | null;
12
89
  readonly baseUrl: string | null;
13
90
  readonly components: Record<string, any>;
14
91
  readonly showMissingComponentAlert: boolean;
@@ -42,8 +119,12 @@ type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;
42
119
  interface FlyoInstance {
43
120
  /** Read-only configuration state */
44
121
  readonly state: NitroState;
45
- /** Fetch and cache the Nitro CMS configuration (React-cached per request) */
46
- getNitroConfig(): Promise<ConfigResponse>;
122
+ /**
123
+ * Fetch and cache the Nitro CMS configuration (React-cached per requested locale).
124
+ * When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`
125
+ * header set by the proxy), falling back to `defaultLocale`.
126
+ */
127
+ getNitroConfig(lang?: string): Promise<ConfigResponse>;
47
128
  /** Create a PagesApi client */
48
129
  getNitroPages(): PagesApi;
49
130
  /** Create an EntitiesApi client */
@@ -52,10 +133,17 @@ interface FlyoInstance {
52
133
  getNitroSitemap(): SitemapApi;
53
134
  /** Create a SearchApi client */
54
135
  getNitroSearch(): SearchApi;
136
+ /**
137
+ * Resolve the active request locale from the `x-flyo-locale` header (set by the
138
+ * proxy middleware), falling back to `defaultLocale`. Use it inside entity
139
+ * resolvers or to set `<html lang>`.
140
+ */
141
+ getRequestLocale(): Promise<string | undefined>;
55
142
  /** Resolve a page from catch-all route params (React-cached per request) */
56
143
  pageResolveRoute(props: RouteParams): Promise<{
57
144
  page: Page;
58
145
  path: string;
146
+ lang: string | undefined;
59
147
  cfg: ConfigResponse;
60
148
  }>;
61
149
  /** Generate a Next.js sitemap from Flyo Nitro content */
@@ -82,9 +170,13 @@ interface FlyoInstance {
82
170
  * });
83
171
  * ```
84
172
  */
85
- declare function initNitro({ accessToken, lang, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
173
+ declare function initNitro({ accessToken, lang, locales, defaultLocale, baseUrl, components, showMissingComponentAlert, liveEdit, serverCacheTtl, clientCacheTtl, }: {
86
174
  accessToken: string;
87
175
  lang?: string;
176
+ /** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */
177
+ locales?: string[];
178
+ /** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */
179
+ defaultLocale?: string;
88
180
  baseUrl?: string;
89
181
  components?: Record<string, any>;
90
182
  showMissingComponentAlert?: boolean;
@@ -92,6 +184,63 @@ declare function initNitro({ accessToken, lang, baseUrl, components, showMissing
92
184
  serverCacheTtl?: number;
93
185
  clientCacheTtl?: number;
94
186
  }): FlyoInstance;
187
+ interface LanguageLinksStore {
188
+ /** Publish the links once. Later calls are ignored (first publish wins). */
189
+ publish(links: FlyoLanguageLink[]): void;
190
+ /** A promise that resolves when the links are published. */
191
+ read(): Promise<FlyoLanguageLink[]>;
192
+ }
193
+ /**
194
+ * Create a single deferred language-links channel: one writer, many readers,
195
+ * order-independent. Low-level — most code uses {@link publishLanguageLinks} /
196
+ * {@link readLanguageLinks}, which wrap this in a per-request `cache()`.
197
+ */
198
+ declare function createLanguageLinksStore(): LanguageLinksStore;
199
+ /**
200
+ * Publish the current route's language-switcher links into the request-scoped
201
+ * store, so a switcher rendered in shared chrome (e.g. a footer in the root
202
+ * layout) can read them via {@link readLanguageLinks}.
203
+ *
204
+ * The Flyo route helpers call this for you: `pageResolveRoute` (and therefore
205
+ * `nitroPageRoute`) publishes the page's links, and `nitroEntityRoute` /
206
+ * `nitroEntityGenerateMetadata` publish the entity's. You only need to call it
207
+ * yourself on routes that render the shared switcher **without** going through
208
+ * those helpers — a custom page, or `not-found.tsx` — where you would otherwise
209
+ * leave the footer waiting forever. Pass a fallback there, e.g.
210
+ * `publishLanguageLinks(getLanguageLinks(undefined, { currentLang, locales }))`.
211
+ *
212
+ * Only the first call per request takes effect.
213
+ */
214
+ declare function publishLanguageLinks(links: FlyoLanguageLink[]): void;
215
+ /**
216
+ * Await the current route's language-switcher links from shared chrome — a
217
+ * footer, header, or any component in the root layout. Resolves with whatever
218
+ * the active page/entity route passed to {@link publishLanguageLinks}.
219
+ *
220
+ * Because it awaits, the reader suspends until the links are published, so it
221
+ * works no matter whether the layout or the page renders first. Wrap the reading
222
+ * component in `<Suspense>` so the rest of the layout can stream while it waits.
223
+ *
224
+ * @example
225
+ * ```tsx
226
+ * // components/LanguageSwitcher.tsx (server component)
227
+ * import { readLanguageLinks } from '@flyo/nitro-next/server';
228
+ *
229
+ * export async function LanguageSwitcher() {
230
+ * const links = await readLanguageLinks();
231
+ * return (
232
+ * <nav aria-label="Language">
233
+ * {links.map((l) =>
234
+ * l.exists
235
+ * ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>
236
+ * : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>,
237
+ * )}
238
+ * </nav>
239
+ * );
240
+ * }
241
+ * ```
242
+ */
243
+ declare function readLanguageLinks(): Promise<FlyoLanguageLink[]>;
95
244
  /**
96
245
  * NitroDebugInfo Component
97
246
  *
@@ -232,4 +381,4 @@ declare function nitroEntityGenerateMetadata<T = any>(flyo: FlyoInstance, option
232
381
  resolver: EntityResolver<T>;
233
382
  }): (props: EntityRouteParams<T>) => Promise<Metadata>;
234
383
 
235
- export { type EntityResolver, type FlyoInstance, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute };
384
+ export { type EntityResolver, type FlyoInstance, type FlyoLanguageLink, type LanguageLinksStore, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, createLanguageLinksStore, getLanguageLinks, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, publishLanguageLinks, readLanguageLinks };
package/dist/server.js CHANGED
@@ -25,21 +25,54 @@ __export(server_exports, {
25
25
  NitroEntityJsonLd: () => NitroEntityJsonLd,
26
26
  NitroPage: () => NitroPage,
27
27
  NitroSlot: () => NitroSlot,
28
+ createLanguageLinksStore: () => createLanguageLinksStore,
29
+ getLanguageLinks: () => getLanguageLinks,
28
30
  initNitro: () => initNitro,
29
31
  nitroEntityGenerateMetadata: () => nitroEntityGenerateMetadata,
30
32
  nitroEntityRoute: () => nitroEntityRoute,
31
33
  nitroPageGenerateMetadata: () => nitroPageGenerateMetadata,
32
34
  nitroPageGenerateStaticParams: () => nitroPageGenerateStaticParams,
33
- nitroPageRoute: () => nitroPageRoute
35
+ nitroPageRoute: () => nitroPageRoute,
36
+ publishLanguageLinks: () => publishLanguageLinks,
37
+ readLanguageLinks: () => readLanguageLinks
34
38
  });
35
39
  module.exports = __toCommonJS(server_exports);
36
40
  var import_react = require("react");
37
41
  var import_navigation = require("next/navigation");
42
+ var import_headers = require("next/headers");
38
43
  var import_nitro_typescript = require("@flyo/nitro-typescript");
44
+
45
+ // src/i18n.ts
46
+ function getLanguageLinks(translations, options) {
47
+ const currentLang = options?.currentLang;
48
+ const byShortcode = /* @__PURE__ */ new Map();
49
+ for (const t of translations ?? []) {
50
+ const shortcode = t.language?.shortcode;
51
+ if (shortcode) {
52
+ byShortcode.set(shortcode, t);
53
+ }
54
+ }
55
+ const toLink = (shortcode, t) => ({
56
+ shortcode,
57
+ name: t?.language?.name,
58
+ href: t?.href ?? null,
59
+ title: t?.title,
60
+ isCurrent: shortcode === currentLang,
61
+ exists: t?.href != null
62
+ });
63
+ if (options?.locales && options.locales.length > 0) {
64
+ return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));
65
+ }
66
+ return (translations ?? []).filter((t) => t.language?.shortcode).map((t) => toLink(t.language.shortcode, t));
67
+ }
68
+
69
+ // src/server.tsx
39
70
  var import_jsx_runtime = require("react/jsx-runtime");
40
71
  function initNitro({
41
72
  accessToken,
42
73
  lang,
74
+ locales,
75
+ defaultLocale,
43
76
  baseUrl,
44
77
  components,
45
78
  showMissingComponentAlert,
@@ -48,9 +81,13 @@ function initNitro({
48
81
  clientCacheTtl
49
82
  }) {
50
83
  const configuration = new import_nitro_typescript.Configuration({ apiKey: accessToken });
84
+ const resolvedDefaultLocale = defaultLocale ?? lang ?? null;
85
+ const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);
51
86
  const state = {
52
87
  accessToken,
53
88
  lang: lang ?? null,
89
+ locales: resolvedLocales,
90
+ defaultLocale: resolvedDefaultLocale,
54
91
  baseUrl: baseUrl ?? null,
55
92
  components: components ?? {},
56
93
  showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,
@@ -58,26 +95,44 @@ function initNitro({
58
95
  serverCacheTtl: serverCacheTtl ?? 1200,
59
96
  clientCacheTtl: clientCacheTtl ?? 900
60
97
  };
61
- const getNitroConfig = (0, import_react.cache)(async () => {
98
+ const getRequestLocale = async () => {
99
+ try {
100
+ const requestHeaders = await (0, import_headers.headers)();
101
+ const headerLocale = requestHeaders.get("x-flyo-locale");
102
+ if (headerLocale) {
103
+ return headerLocale;
104
+ }
105
+ } catch {
106
+ }
107
+ return state.defaultLocale ?? state.lang ?? void 0;
108
+ };
109
+ const fetchConfig = (0, import_react.cache)(async (lang2) => {
62
110
  const configApi = new import_nitro_typescript.ConfigApi(configuration);
63
- const useLang = state.lang ?? void 0;
64
- return configApi.config({ lang: useLang });
111
+ return configApi.config({ lang: lang2 ?? void 0 });
65
112
  });
113
+ const getNitroConfig = async (lang2) => {
114
+ const useLang = lang2 ?? await getRequestLocale();
115
+ return fetchConfig(useLang);
116
+ };
66
117
  const pageResolveRoute = (0, import_react.cache)(async ({ params }) => {
67
118
  const { slug } = await params;
68
- const path = slug?.join("/") ?? "";
69
- const cfg = await getNitroConfig();
119
+ const segments = slug ?? [];
120
+ const path = segments.join("/");
121
+ const firstSegment = segments[0];
122
+ const lang2 = firstSegment && state.locales.includes(firstSegment) ? firstSegment : state.defaultLocale ?? void 0;
123
+ const cfg = await getNitroConfig(lang2);
70
124
  if (!cfg.pages?.includes(path)) {
71
125
  (0, import_navigation.notFound)();
72
126
  }
73
- const page = await new import_nitro_typescript.PagesApi(configuration).page({ slug: path }).catch((error) => {
127
+ const page = await new import_nitro_typescript.PagesApi(configuration).page({ slug: path, lang: lang2 }).catch((error) => {
74
128
  console.error("Error fetching page:", path, error);
75
129
  (0, import_navigation.notFound)();
76
130
  });
77
131
  if (!page) {
78
132
  (0, import_navigation.notFound)();
79
133
  }
80
- return { page, path, cfg };
134
+ publishLanguageLinks(getLanguageLinks(page.translation, { currentLang: lang2, locales: state.locales }));
135
+ return { page, path, lang: lang2, cfg };
81
136
  });
82
137
  const sitemap = async () => {
83
138
  const sitemapApi = new import_nitro_typescript.SitemapApi(configuration);
@@ -108,6 +163,7 @@ function initNitro({
108
163
  return {
109
164
  state,
110
165
  getNitroConfig,
166
+ getRequestLocale,
111
167
  getNitroPages: () => new import_nitro_typescript.PagesApi(configuration),
112
168
  getNitroEntities: () => new import_nitro_typescript.EntitiesApi(configuration),
113
169
  getNitroSitemap: () => new import_nitro_typescript.SitemapApi(configuration),
@@ -116,6 +172,31 @@ function initNitro({
116
172
  sitemap
117
173
  };
118
174
  }
175
+ function createLanguageLinksStore() {
176
+ let resolve;
177
+ const promise = new Promise((r) => {
178
+ resolve = r;
179
+ });
180
+ let settled = false;
181
+ return {
182
+ publish(links) {
183
+ if (!settled) {
184
+ settled = true;
185
+ resolve(links);
186
+ }
187
+ },
188
+ read() {
189
+ return promise;
190
+ }
191
+ };
192
+ }
193
+ var languageLinksStore = (0, import_react.cache)(createLanguageLinksStore);
194
+ function publishLanguageLinks(links) {
195
+ languageLinksStore().publish(links);
196
+ }
197
+ function readLanguageLinks() {
198
+ return languageLinksStore().read();
199
+ }
119
200
  var readEnv = (key, fallback = "") => {
120
201
  const value = process.env[key];
121
202
  if (value !== void 0 && value !== "") {
@@ -123,12 +204,32 @@ var readEnv = (key, fallback = "") => {
123
204
  }
124
205
  return fallback;
125
206
  };
126
- function createCachedEntityResolver(resolver) {
207
+ function buildLanguageAlternates(translations, currentLang) {
208
+ const languages = {};
209
+ let canonical;
210
+ for (const t of translations ?? []) {
211
+ const shortcode = t.language?.shortcode;
212
+ if (shortcode && t.href) {
213
+ languages[shortcode] = t.href;
214
+ if (currentLang && shortcode === currentLang) {
215
+ canonical = t.href;
216
+ }
217
+ }
218
+ }
219
+ if (Object.keys(languages).length === 0) {
220
+ return void 0;
221
+ }
222
+ return canonical ? { canonical, languages } : { languages };
223
+ }
224
+ function createCachedEntityResolver(flyo, resolver) {
127
225
  return (0, import_react.cache)(async ({ params }) => {
128
226
  const entity = await resolver(params);
129
227
  if (!entity) {
130
228
  (0, import_navigation.notFound)();
131
229
  }
230
+ publishLanguageLinks(
231
+ getLanguageLinks(entity.translation, { currentLang: entity.language, locales: flyo.state.locales })
232
+ );
132
233
  return entity;
133
234
  });
134
235
  }
@@ -245,16 +346,18 @@ function nitroPageRoute(flyo) {
245
346
  }
246
347
  function nitroPageGenerateMetadata(flyo) {
247
348
  return async (props) => {
248
- const { page } = await flyo.pageResolveRoute(props);
349
+ const { page, lang } = await flyo.pageResolveRoute(props);
249
350
  const meta = page.meta_json;
250
351
  const title = meta?.title || page.title || "";
251
352
  const description = meta?.description ?? "";
252
353
  const image = meta?.image ?? "";
253
354
  const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
254
355
  const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
356
+ const alternates = buildLanguageAlternates(page.translation, lang);
255
357
  return {
256
358
  title,
257
359
  description,
360
+ ...alternates ? { alternates } : {},
258
361
  openGraph: {
259
362
  title,
260
363
  description,
@@ -280,7 +383,7 @@ function nitroPageGenerateStaticParams(flyo) {
280
383
  };
281
384
  }
282
385
  function nitroEntityRoute(flyo, options) {
283
- const cachedResolver = createCachedEntityResolver(options.resolver);
386
+ const cachedResolver = createCachedEntityResolver(flyo, options.resolver);
284
387
  async function entityRoute(props) {
285
388
  const entity = await cachedResolver(props);
286
389
  if (options.render) {
@@ -291,7 +394,7 @@ function nitroEntityRoute(flyo, options) {
291
394
  return entityRoute;
292
395
  }
293
396
  function nitroEntityGenerateMetadata(flyo, options) {
294
- const cachedResolver = createCachedEntityResolver(options.resolver);
397
+ const cachedResolver = createCachedEntityResolver(flyo, options.resolver);
295
398
  return async (props) => {
296
399
  const entity = await cachedResolver(props);
297
400
  const title = entity.entity?.entity_title || "";
@@ -299,9 +402,11 @@ function nitroEntityGenerateMetadata(flyo, options) {
299
402
  const image = entity.entity?.entity_image ?? "";
300
403
  const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
301
404
  const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
405
+ const alternates = buildLanguageAlternates(entity.translation, entity.language);
302
406
  return {
303
407
  title,
304
408
  description,
409
+ ...alternates ? { alternates } : {},
305
410
  openGraph: {
306
411
  title,
307
412
  description,
@@ -324,11 +429,15 @@ function nitroEntityGenerateMetadata(flyo, options) {
324
429
  NitroEntityJsonLd,
325
430
  NitroPage,
326
431
  NitroSlot,
432
+ createLanguageLinksStore,
433
+ getLanguageLinks,
327
434
  initNitro,
328
435
  nitroEntityGenerateMetadata,
329
436
  nitroEntityRoute,
330
437
  nitroPageGenerateMetadata,
331
438
  nitroPageGenerateStaticParams,
332
- nitroPageRoute
439
+ nitroPageRoute,
440
+ publishLanguageLinks,
441
+ readLanguageLinks
333
442
  });
334
443
  //# sourceMappingURL=server.js.map