@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/client.d.mts +73 -2
- package/dist/client.d.ts +73 -2
- 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 +154 -5
- package/dist/server.d.ts +154 -5
- package/dist/server.js +122 -13
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +117 -12
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
46
|
-
|
|
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
|
-
/**
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
69
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|