fimo 0.16.0 → 0.18.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/assets/astro/components/DateTime.astro +22 -0
- package/assets/astro/components/index.d.ts +1 -0
- package/assets/astro/components/index.js +1 -0
- package/assets/content-templates/shared/types.eta +2 -0
- package/assets/skills/fimo-cli/SKILL.md +5 -2
- package/assets/skills/fimo-cli/references/content.md +2 -2
- package/assets/skills/fimo-cli/references/deploy.md +2 -0
- package/assets/skills/fimo-cli/references/init.md +11 -4
- package/assets/skills/fimo-cli/references/locales.md +38 -14
- package/assets/skills/fimo-cli/references/pages.md +70 -0
- package/assets/skills/fimo-cli/references/webhooks.md +1 -1
- package/assets/skills/fimo-code/SKILL.md +11 -0
- package/assets/skills/fimo-code/assets/react-router-locales/LocaleLayout.tsx +12 -0
- package/assets/skills/fimo-code/assets/react-router-locales/routes.ts +7 -6
- package/assets/skills/fimo-code/references/best-practices/astro.md +47 -0
- package/assets/skills/fimo-code/references/best-practices/next.md +42 -0
- package/assets/skills/fimo-code/references/best-practices/sveltekit.md +17 -0
- package/assets/skills/fimo-code/references/content/richtext.md +5 -0
- package/assets/skills/fimo-code/references/content/schema.md +4 -1
- package/assets/skills/fimo-code/references/frameworks/astro.md +5 -3
- package/assets/skills/fimo-code/references/frameworks/hosting.md +5 -2
- package/assets/skills/fimo-code/references/frameworks/next/app-router.md +6 -0
- package/assets/skills/fimo-code/references/frameworks/next/locales.md +12 -2
- package/assets/skills/fimo-code/references/frameworks/next.md +7 -0
- package/assets/skills/fimo-code/references/frameworks/react-router.md +15 -7
- package/assets/skills/fimo-code/references/frameworks/sveltekit.md +15 -8
- package/assets/skills/fimo-code/references/labels.md +9 -1
- package/assets/skills/fimo-code/references/locales.md +8 -2
- package/assets/skills/fimo-code/references/migrations/react-router-route-ownership.md +93 -0
- package/assets/skills/fimo-code/references/ui.md +21 -2
- package/assets/skills/fimo-migration/SKILL.md +276 -51
- package/assets/skills/fimo-migration/references/content.md +119 -35
- package/assets/skills/fimo-migration/references/decision.md +72 -0
- package/assets/skills/fimo-migration/references/forms.md +38 -6
- package/assets/skills/fimo-migration/references/frameworks/nextjs.md +179 -23
- package/assets/skills/fimo-migration/references/import-contract.md +71 -19
- package/assets/skills/fimo-migration/references/manifest.md +11 -161
- package/assets/skills/fimo-migration/references/media.md +33 -1
- package/assets/skills/fimo-migration/references/platforms.md +26 -2
- package/assets/skills/fimo-migration/references/qa.md +167 -13
- package/assets/skills/fimo-migration/references/workers/content-execution.md +39 -0
- package/assets/skills/fimo-migration/references/workers/content-planner.md +64 -0
- package/assets/skills/fimo-migration/references/workers/framework.md +37 -0
- package/assets/skills/fimo-migration/references/workers/validator.md +25 -0
- package/assets/skills/fimo-migration/references/workers.md +82 -0
- package/assets/skills/fimo-migration/references/workflow.md +140 -192
- package/assets/skills/fimo-migration/scripts/apply-decision.mjs +534 -0
- package/assets/skills/fimo-migration/scripts/build-canonical-manifest.mjs +7 -4
- package/assets/skills/fimo-migration/scripts/write-migration-report.mjs +191 -4
- package/assets/skills/fimo-studio/references/cms.md +25 -7
- package/assets/skills/fimo-studio/references/webhooks.md +1 -1
- package/assets/sveltekit/components/DateTime.svelte +35 -0
- package/assets/sveltekit/components/FimoProvider.svelte +2 -2
- package/assets/sveltekit/components/index.d.ts +1 -0
- package/assets/sveltekit/components/index.js +1 -0
- package/dist/build/next/config.d.ts +4 -1
- package/dist/build/next/config.js +21 -5
- package/dist/build/sveltekit/vite.d.ts +3 -4
- package/dist/build/sveltekit/vite.js +3 -4
- package/dist/build/vite/plugins/runtime-env.d.ts +2 -1
- package/dist/build/vite/plugins/runtime-env.js +14 -10
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +76 -78
- package/dist/runtime/app/FimoProviders.js +25 -14
- package/dist/runtime/app/prefetch.d.ts +3 -1
- package/dist/runtime/app/prefetch.js +11 -55
- package/dist/runtime/astro/preview.js +7 -9
- package/dist/runtime/content/client/internal.js +1 -0
- package/dist/runtime/i18n/locale.d.ts +4 -0
- package/dist/runtime/i18n/locale.js +14 -14
- package/dist/runtime/next/client-runtime.js +5 -1
- package/dist/runtime/next/client.d.ts +3 -2
- package/dist/runtime/next/client.js +2 -1
- package/dist/runtime/next/labels.d.ts +2 -2
- package/dist/runtime/next/labels.js +2 -2
- package/dist/runtime/next/preview.d.ts +13 -0
- package/dist/runtime/next/preview.js +34 -0
- package/dist/runtime/next/primitives/server.d.ts +2 -0
- package/dist/runtime/next/primitives/server.js +8 -0
- package/dist/runtime/next/provider.d.ts +5 -0
- package/dist/runtime/next/provider.js +17 -3
- package/dist/runtime/next/server-runtime.js +4 -1
- package/dist/runtime/next/server.d.ts +1 -1
- package/dist/runtime/next/server.js +1 -1
- package/dist/runtime/paths/get-fimo-paths.d.ts +6 -5
- package/dist/runtime/paths/get-fimo-paths.js +43 -42
- package/dist/runtime/preview/location.d.ts +19 -2
- package/dist/runtime/preview/location.js +16 -2
- package/dist/runtime/primitives/index.d.ts +1 -0
- package/dist/runtime/primitives/index.js +1 -0
- package/dist/runtime/react/primitives/editable/DateTime.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/DateTime.js +13 -0
- package/dist/runtime/react/primitives/renderers/DateRenderer.d.ts +7 -1
- package/dist/runtime/react/primitives/renderers/DateRenderer.js +11 -8
- package/dist/runtime/react-router/{routes.d.ts → legacy/routes.d.ts} +6 -1
- package/dist/runtime/react-router/{routes.js → legacy/routes.js} +22 -7
- package/dist/runtime/routes/contract.d.ts +116 -0
- package/dist/runtime/routes/contract.js +377 -0
- package/dist/runtime/routes/index.d.ts +3 -0
- package/dist/runtime/routes/index.js +3 -0
- package/dist/runtime/routing/AppRoutesProvider.js +4 -3
- package/dist/runtime/seo/resolver.d.ts +5 -0
- package/dist/runtime/seo/resolver.js +10 -42
- package/dist/runtime/shared/fimo-config.types.d.ts +34 -8
- package/dist/runtime/shared/preview-location.d.ts +1 -22
- package/dist/runtime/shared/preview-location.js +5 -93
- package/dist/runtime/sveltekit/preview.d.ts +12 -3
- package/dist/runtime/sveltekit/preview.js +13 -8
- package/dist/scripts/sync-forms.js +1 -1
- package/dist/scripts/sync-schemas.js +1 -4
- package/dist/scripts/validate-fimo-config.js +5 -1
- package/dist/scripts/validate-page-registry.d.ts +2 -0
- package/dist/scripts/validate-page-registry.js +167 -0
- package/dist/scripts/validate-route-metadata.js +132 -41
- package/package.json +7 -3
- package/templates/astro/.fimo/config.json +13 -5
- package/templates/astro/AGENTS.md +5 -1
- package/templates/astro/astro.config.mjs +7 -0
- package/templates/astro/package.json +1 -1
- package/templates/astro/pnpm-workspace.yaml +0 -3
- package/templates/astro/src/components/ProjectOverview.astro +9 -6
- package/templates/astro/src/layouts/Base.astro +1 -1
- package/templates/astro/src/locales.ts +4 -0
- package/templates/astro/src/pages/[locale]/index.astro +19 -0
- package/templates/astro/src/pages/[locale]/projects/[slug].astro +35 -0
- package/templates/astro/src/pages/[locale]/projects/index.astro +19 -0
- package/templates/astro/src/pages/index.astro +5 -2
- package/templates/astro/src/pages/projects/[slug].astro +4 -2
- package/templates/astro/src/pages/projects/index.astro +5 -2
- package/templates/nextjs/.fimo/config.json +2 -1
- package/templates/nextjs/AGENTS.md +5 -0
- package/templates/nextjs/package.json +1 -1
- package/templates/nextjs/src/app/(default)/layout.tsx +10 -0
- package/templates/nextjs/src/app/[locale]/layout.tsx +22 -0
- package/templates/nextjs/src/app/[locale]/page.tsx +1 -0
- package/templates/nextjs/src/components/Document.tsx +26 -0
- package/templates/nextjs/src/components/FimoPreviewBridge.tsx +13 -0
- package/templates/nextjs/src/locales.ts +13 -0
- package/templates/react-router/.fimo/config.json +3 -6
- package/templates/react-router/AGENTS.md +4 -0
- package/templates/react-router/package.json +2 -2
- package/templates/react-router/pnpm-workspace.yaml +1 -3
- package/templates/react-router/react-router.config.ts +4 -10
- package/templates/react-router/src/pages/LocaleLayout.tsx +12 -0
- package/templates/react-router/src/root.tsx +2 -4
- package/templates/react-router/src/routes.ts +5 -3
- package/templates/sveltekit/.fimo/config.json +2 -1
- package/templates/sveltekit/AGENTS.md +5 -0
- package/templates/sveltekit/package.json +1 -1
- package/templates/sveltekit/src/app.html +1 -1
- package/templates/sveltekit/src/hooks.server.ts +11 -0
- package/templates/sveltekit/src/locales.ts +8 -0
- package/templates/sveltekit/src/params/locale.ts +5 -0
- package/templates/sveltekit/src/routes/+layout.server.ts +6 -1
- package/templates/sveltekit/src/routes/+layout.svelte +5 -0
- package/assets/skills/fimo-migration/references/inventory/coordinator.md +0 -40
- package/assets/skills/fimo-migration/references/inventory/locales/next-intl.md +0 -39
- package/assets/skills/fimo-migration/references/inventory/locales.md +0 -41
- package/assets/skills/fimo-migration/scripts/inventory/locales/next-intl.mjs +0 -459
- package/templates/nextjs/src/app/layout.tsx +0 -17
- /package/templates/nextjs/src/app/{page.tsx → (default)/page.tsx} +0 -0
- /package/templates/sveltekit/src/routes/{+page.svelte → [[locale=locale]]/+page.svelte} +0 -0
|
@@ -15,6 +15,7 @@ import { ScreenshotProvider } from '../iframe/ScreenshotProvider.js';
|
|
|
15
15
|
import { getSafePreviewNavigationPath, isPreviewRuntimeEnabled } from '../preview/bridge.js';
|
|
16
16
|
import { getPreviewLocationChange, getPreviewParentOrigin } from '../preview/location.js';
|
|
17
17
|
import { getActiveRouterRoutes } from '../react-router/active-routes.js';
|
|
18
|
+
import { matchFimoRoute } from '../routes/contract.js';
|
|
18
19
|
import { OptionalTracking } from '../t/OptionalTracking.js';
|
|
19
20
|
export { getPreviewLocationChange, getPreviewLocationPath } from '../preview/location.js';
|
|
20
21
|
const titleCase = (seg) => seg
|
|
@@ -45,6 +46,21 @@ function getConfigDisplayRoutes() {
|
|
|
45
46
|
function getCanonicalRoutePath(path) {
|
|
46
47
|
return resolveFimoLocale({ pathname: path }, fimoConfig).routePath;
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The locale a pathname renders.
|
|
51
|
+
*
|
|
52
|
+
* The declared route decides it. A route that declares no `:locale` segment is
|
|
53
|
+
* app-owned and renders the default locale whatever its first segment spells,
|
|
54
|
+
* which is what stops `/fr` from switching the page to French when `fr` is
|
|
55
|
+
* really an entry slug under a top-level `/:slug` route.
|
|
56
|
+
*
|
|
57
|
+
* Only a URL that no declared route answers falls back to the compatibility
|
|
58
|
+
* prefix resolver. Existing apps can still rely on that inference while new
|
|
59
|
+
* route-owned integrations pass an explicit locale to `FimoProviders`.
|
|
60
|
+
*/
|
|
61
|
+
function getRouteLocale(pathname) {
|
|
62
|
+
return (matchFimoRoute(pathname, fimoConfig)?.locale ?? resolveFimoLocale({ pathname }, fimoConfig).locale);
|
|
63
|
+
}
|
|
48
64
|
function hasReadableFrameworkManifest(manifest) {
|
|
49
65
|
return Boolean(manifest &&
|
|
50
66
|
typeof manifest === 'object' &&
|
|
@@ -84,27 +100,20 @@ export function getDisplayRoutes(frameworkManifest) {
|
|
|
84
100
|
* `nav/read-routes`) and reads routes through the `readRoutes` callback, so a
|
|
85
101
|
* future non-React-Router runtime reuses it by supplying its own reader.
|
|
86
102
|
*/
|
|
87
|
-
function PreviewBridge({ readRoutes }) {
|
|
103
|
+
function PreviewBridge({ readRoutes, locale }) {
|
|
88
104
|
const navigate = useNavigate();
|
|
89
105
|
const location = useLocation();
|
|
90
|
-
const localeResolution = useMemo(() => resolveFimoLocale({ pathname: location.pathname }, fimoConfig), [location.pathname]);
|
|
91
106
|
const sendRoutes = useCallback(() => {
|
|
92
107
|
// Read at send time: the router can change its route table in place
|
|
93
108
|
// during HMR, so an explicit nav/read-routes must see the current state.
|
|
94
109
|
window.parent.postMessage({ type: 'nav/routes', payload: { routes: readRoutes() } }, getPreviewParentOrigin() ?? '*');
|
|
95
110
|
}, [readRoutes]);
|
|
111
|
+
// `locale` is a dependency on purpose: an app that settles a new locale
|
|
112
|
+
// without moving the pathname still has to tell Studio about it.
|
|
96
113
|
const sendLocationChange = useCallback(() => {
|
|
97
|
-
const locationChange = getPreviewLocationChange(location);
|
|
98
114
|
const parentOrigin = getPreviewParentOrigin();
|
|
99
|
-
window.parent.postMessage({
|
|
100
|
-
|
|
101
|
-
payload: {
|
|
102
|
-
...(parentOrigin ? locationChange : { path: locationChange.path }),
|
|
103
|
-
routePath: localeResolution.routePath,
|
|
104
|
-
locale: localeResolution.locale,
|
|
105
|
-
},
|
|
106
|
-
}, parentOrigin ?? '*');
|
|
107
|
-
}, [location.hash, location.pathname, location.search, localeResolution.locale, localeResolution.routePath]);
|
|
115
|
+
window.parent.postMessage({ type: 'nav/locationChange', payload: getPreviewLocationChange(location, locale, parentOrigin) }, parentOrigin ?? '*');
|
|
116
|
+
}, [location.hash, location.pathname, location.search, locale]);
|
|
108
117
|
useEffect(sendRoutes, [sendRoutes]);
|
|
109
118
|
useEffect(sendLocationChange, [sendLocationChange]);
|
|
110
119
|
useEffect(() => {
|
|
@@ -156,7 +165,9 @@ export function FimoProviders({ children, locale }) {
|
|
|
156
165
|
const readRoutes = useCallback(() => getDisplayRoutes(frameworkContext?.manifest), [frameworkContext?.manifest]);
|
|
157
166
|
const routes = readRoutes();
|
|
158
167
|
const localeResolution = useMemo(() => resolveFimoLocale({ pathname: location.pathname }, fimoConfig), [location.pathname]);
|
|
159
|
-
|
|
168
|
+
// The app is authoritative: an explicit `locale` wins over anything Fimo can
|
|
169
|
+
// infer, and inference itself goes through the declared route contract.
|
|
170
|
+
const activeLocale = useMemo(() => locale ?? getRouteLocale(location.pathname), [locale, location.pathname]);
|
|
160
171
|
const activeDir = getFimoTextDirection(activeLocale);
|
|
161
172
|
// @ts-ignore - import.meta.env is provided by Vite and React Router.
|
|
162
173
|
const previewEnabled = isPreviewRuntimeEnabled(import.meta.env.VITE_PREVIEW_SCRIPT_URL);
|
|
@@ -171,5 +182,5 @@ export function FimoProviders({ children, locale }) {
|
|
|
171
182
|
...localeResolution,
|
|
172
183
|
locale: activeLocale,
|
|
173
184
|
dir: activeDir,
|
|
174
|
-
}, children: _jsx(DataSourcesProvider, { children: _jsx(ScreenshotProvider, { children: _jsx(ViteLifecycles, { children: _jsxs(RefreshContentProvider, { children: [previewEnabled ? _jsx(PreviewBridge, { readRoutes: readRoutes }) : null, _jsx(OptionalTracking, { routes: routes }), _jsx(Suspense, { children: children })] }) }) }) }) }) }) }));
|
|
185
|
+
}, children: _jsx(DataSourcesProvider, { children: _jsx(ScreenshotProvider, { children: _jsx(ViteLifecycles, { children: _jsxs(RefreshContentProvider, { children: [previewEnabled ? _jsx(PreviewBridge, { readRoutes: readRoutes, locale: activeLocale }) : null, _jsx(OptionalTracking, { routes: routes }), _jsx(Suspense, { children: children })] }) }) }) }) }) }) }));
|
|
175
186
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare function resolveFimoLocaleForUrl(url: string): import("./index.js").FimoLocaleResolution;
|
|
2
2
|
/**
|
|
3
3
|
* Fetch route data for SEO meta resolution.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Only entry params are fetched: a locale param is resolved from the URL and
|
|
6
|
+
* never corresponds to a CMS schema, so it must not produce a request.
|
|
5
7
|
*/
|
|
6
8
|
export declare function resolveMetaEntries(url: string): Promise<Record<string, unknown> | undefined>;
|
|
7
9
|
//# sourceMappingURL=prefetch.d.ts.map
|
|
@@ -1,57 +1,15 @@
|
|
|
1
1
|
// @ts-ignore - virtual module provided by fimoConfigPlugin in fimo/vite
|
|
2
2
|
import fimoConfig from 'virtual:fimo-config';
|
|
3
3
|
import { resolveFimoLocale } from '../i18n/locale.js';
|
|
4
|
-
|
|
5
|
-
* Match a concrete URL against the routes in .fimo/config.json.
|
|
6
|
-
* Returns the matching route config or null.
|
|
7
|
-
*/
|
|
8
|
-
function matchRoute(url) {
|
|
9
|
-
const config = fimoConfig;
|
|
10
|
-
const localeResolution = resolveFimoLocale(url, config);
|
|
11
|
-
const pathname = localeResolution.routePath;
|
|
12
|
-
const routes = config.routes ?? {};
|
|
13
|
-
for (const [id, config] of Object.entries(routes)) {
|
|
14
|
-
if (!config.path) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const patternSegments = config.path.split('/').filter(Boolean);
|
|
18
|
-
const urlSegments = pathname.split('/').filter(Boolean);
|
|
19
|
-
if (patternSegments.length !== urlSegments.length) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const paramValues = {};
|
|
23
|
-
let match = true;
|
|
24
|
-
for (let i = 0; i < patternSegments.length; i++) {
|
|
25
|
-
const pattern = patternSegments[i];
|
|
26
|
-
const value = urlSegments[i];
|
|
27
|
-
if (pattern.startsWith(':')) {
|
|
28
|
-
paramValues[pattern.slice(1)] = value;
|
|
29
|
-
}
|
|
30
|
-
else if (pattern !== value) {
|
|
31
|
-
match = false;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (match) {
|
|
36
|
-
return { id, config, paramValues };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Handle index route
|
|
40
|
-
if (pathname === '/' || pathname === '') {
|
|
41
|
-
for (const [id, config] of Object.entries(routes)) {
|
|
42
|
-
if (config.path === '/') {
|
|
43
|
-
return { id, config, paramValues: {} };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
4
|
+
import { matchFimoRoute } from '../routes/contract.js';
|
|
49
5
|
export function resolveFimoLocaleForUrl(url) {
|
|
50
6
|
return resolveFimoLocale(url, fimoConfig);
|
|
51
7
|
}
|
|
52
8
|
/**
|
|
53
9
|
* Fetch route data for SEO meta resolution.
|
|
54
|
-
*
|
|
10
|
+
*
|
|
11
|
+
* Only entry params are fetched: a locale param is resolved from the URL and
|
|
12
|
+
* never corresponds to a CMS schema, so it must not produce a request.
|
|
55
13
|
*/
|
|
56
14
|
export async function resolveMetaEntries(url) {
|
|
57
15
|
// @ts-ignore – import.meta.env is a Vite feature, replaced at build time for both client and SSR
|
|
@@ -59,24 +17,22 @@ export async function resolveMetaEntries(url) {
|
|
|
59
17
|
if (!base) {
|
|
60
18
|
return undefined;
|
|
61
19
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
if (!route?.config.params) {
|
|
20
|
+
const match = matchFimoRoute(url, fimoConfig);
|
|
21
|
+
if (!match || match.contract.entryParams.length === 0) {
|
|
65
22
|
return undefined;
|
|
66
23
|
}
|
|
67
24
|
const data = {};
|
|
68
|
-
await Promise.all(
|
|
69
|
-
const value =
|
|
25
|
+
await Promise.all(match.contract.entryParams.map(async (param) => {
|
|
26
|
+
const value = match.entryParams[param.name];
|
|
70
27
|
if (!value) {
|
|
71
28
|
return;
|
|
72
29
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
entryUrl.searchParams.set('locale', localeResolution.locale);
|
|
30
|
+
const entryUrl = new URL(`${base}/entries/${param.schema}/field/${encodeURIComponent(param.queryField)}/${encodeURIComponent(value)}`);
|
|
31
|
+
entryUrl.searchParams.set('locale', match.locale);
|
|
76
32
|
const res = await fetch(entryUrl);
|
|
77
33
|
if (res.ok) {
|
|
78
34
|
const json = await res.json();
|
|
79
|
-
data[
|
|
35
|
+
data[param.name] = json.data;
|
|
80
36
|
}
|
|
81
37
|
}));
|
|
82
38
|
return Object.keys(data).length > 0 ? data : undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFimoDefaultLocale } from '../i18n/locale.js';
|
|
2
2
|
import { getSafePreviewNavigationPath, isPreviewRuntimeEnabled } from '../preview/bridge.js';
|
|
3
|
-
import { getPreviewLocationChange, getPreviewParentOrigin } from '../preview/location.js';
|
|
3
|
+
import { getDocumentLocale, getPreviewLocationChange, getPreviewParentOrigin } from '../preview/location.js';
|
|
4
4
|
const ASTRO_PAGE_LOAD_EVENT = 'astro:page-load';
|
|
5
5
|
const ASTRO_AFTER_SWAP_EVENT = 'astro:after-swap';
|
|
6
6
|
const CONNECTION_KEY = '__fimoAstroPreviewDisconnect__';
|
|
@@ -43,16 +43,14 @@ export function connectAstroPreview(options, browser = currentBrowser()) {
|
|
|
43
43
|
const sendRoutes = () => {
|
|
44
44
|
postToStudio({ type: 'nav/routes', payload: { routes: options.routes } });
|
|
45
45
|
};
|
|
46
|
+
// Astro settles the locale server-side and renders it into `<html lang>`, so
|
|
47
|
+
// reading it back reports the locale the document is actually showing rather
|
|
48
|
+
// than one inferred from the URL. `astro:page-load` fires after a ClientRouter
|
|
49
|
+
// swap as well, which is what keeps this correct across in-place navigation.
|
|
46
50
|
const sendLocation = () => {
|
|
47
|
-
const location = getPreviewLocationChange(browser.window.location);
|
|
48
|
-
const locale = resolveFimoLocale({ pathname: browser.window.location.pathname }, { i18n: options.i18n });
|
|
49
51
|
postToStudio({
|
|
50
52
|
type: 'nav/locationChange',
|
|
51
|
-
payload: {
|
|
52
|
-
...(parentOrigin ? location : { path: location.path }),
|
|
53
|
-
routePath: locale.routePath,
|
|
54
|
-
locale: browser.document.documentElement.lang || locale.locale,
|
|
55
|
-
},
|
|
53
|
+
payload: getPreviewLocationChange(browser.window.location, getDocumentLocale(browser.document, getFimoDefaultLocale({ i18n: options.i18n })), parentOrigin),
|
|
56
54
|
});
|
|
57
55
|
};
|
|
58
56
|
const announceDocumentChange = () => {
|
|
@@ -203,6 +203,7 @@ function wrapField(field, value, source, resolve) {
|
|
|
203
203
|
case 'string':
|
|
204
204
|
case 'text':
|
|
205
205
|
case 'date':
|
|
206
|
+
case 'datetime':
|
|
206
207
|
return value != null ? new FimoString(String(value), source) : value;
|
|
207
208
|
case 'richtext':
|
|
208
209
|
return value != null ? new FimoRichText(value, source) : value;
|
|
@@ -16,6 +16,10 @@ export interface FimoLocaleRequestLike {
|
|
|
16
16
|
export declare function getFimoDefaultLocale(config: Pick<FimoConfig, 'i18n'> | undefined): string;
|
|
17
17
|
export declare function getFimoLocales(config: Pick<FimoConfig, 'i18n'> | undefined): string[];
|
|
18
18
|
export declare function resolveFimoLocale(requestLike: string | URL | FimoLocaleRequestLike, config: Pick<FimoConfig, 'i18n'> | undefined): FimoLocaleResolution;
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite a path so it renders in `locale`. The default locale is always
|
|
21
|
+
* unprefixed — `/en/pricing` is not a path Fimo produces or recognizes.
|
|
22
|
+
*/
|
|
19
23
|
export declare function localizeFimoPath(path: string, locale: string, config: Pick<FimoConfig, 'i18n'> | undefined): string;
|
|
20
24
|
export declare function getFimoTextDirection(locale: string): 'ltr' | 'rtl';
|
|
21
25
|
export declare function toHtmlLang(locale: string): string;
|
|
@@ -16,12 +16,14 @@ export function resolveFimoLocale(requestLike, config) {
|
|
|
16
16
|
const pathname = normalizePathname(readPathname(requestLike));
|
|
17
17
|
const defaultLocale = getFimoDefaultLocale(config);
|
|
18
18
|
const locales = getFimoLocales(config);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
19
|
+
// New route-owned projects do not need this setting. Keep reading it for
|
|
20
|
+
// older apps whose provider or navigation still relies on global prefixes.
|
|
21
|
+
const strategy = config?.i18n?.routing?.strategy ?? 'path-prefix';
|
|
22
22
|
if (strategy === 'path-prefix') {
|
|
23
23
|
const [firstSegment, ...rest] = pathname.split('/').filter(Boolean);
|
|
24
|
-
|
|
24
|
+
// The default locale has no prefixed form, so a leading `/en` is an
|
|
25
|
+
// ordinary route segment rather than a locale marker.
|
|
26
|
+
if (firstSegment && firstSegment !== defaultLocale && locales.includes(firstSegment)) {
|
|
25
27
|
const routePath = normalizePathname(`/${rest.join('/')}`);
|
|
26
28
|
return {
|
|
27
29
|
locale: firstSegment,
|
|
@@ -30,7 +32,7 @@ export function resolveFimoLocale(requestLike, config) {
|
|
|
30
32
|
pathname,
|
|
31
33
|
routePath,
|
|
32
34
|
prefix: firstSegment,
|
|
33
|
-
isDefaultLocale:
|
|
35
|
+
isDefaultLocale: false,
|
|
34
36
|
dir: getFimoTextDirection(firstSegment),
|
|
35
37
|
};
|
|
36
38
|
}
|
|
@@ -40,7 +42,7 @@ export function resolveFimoLocale(requestLike, config) {
|
|
|
40
42
|
locales,
|
|
41
43
|
pathname,
|
|
42
44
|
routePath: pathname,
|
|
43
|
-
prefix:
|
|
45
|
+
prefix: null,
|
|
44
46
|
isDefaultLocale: true,
|
|
45
47
|
dir: getFimoTextDirection(defaultLocale),
|
|
46
48
|
};
|
|
@@ -56,16 +58,14 @@ export function resolveFimoLocale(requestLike, config) {
|
|
|
56
58
|
dir: getFimoTextDirection(defaultLocale),
|
|
57
59
|
};
|
|
58
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Rewrite a path so it renders in `locale`. The default locale is always
|
|
63
|
+
* unprefixed — `/en/pricing` is not a path Fimo produces or recognizes.
|
|
64
|
+
*/
|
|
59
65
|
export function localizeFimoPath(path, locale, config) {
|
|
60
66
|
const routePath = resolveFimoLocale(path, config).routePath;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const strategy = routing?.strategy ?? 'path-prefix';
|
|
64
|
-
const prefixDefaultLocale = routing?.prefixDefaultLocale === true;
|
|
65
|
-
if (strategy !== 'path-prefix') {
|
|
66
|
-
return routePath;
|
|
67
|
-
}
|
|
68
|
-
if (locale === defaultLocale && !prefixDefaultLocale) {
|
|
67
|
+
const strategy = config?.i18n?.routing?.strategy ?? 'path-prefix';
|
|
68
|
+
if (strategy !== 'path-prefix' || locale === getFimoDefaultLocale(config)) {
|
|
69
69
|
return routePath;
|
|
70
70
|
}
|
|
71
71
|
return normalizePathname(`/${locale}${routePath === '/' ? '' : routePath}`);
|
|
@@ -4,7 +4,11 @@ export const nextClientContentRuntime = {
|
|
|
4
4
|
return requireContentApiUrl(process.env.NEXT_PUBLIC_FIMO_API_URL, 'NEXT_PUBLIC_FIMO_API_URL');
|
|
5
5
|
},
|
|
6
6
|
defaultLocale() {
|
|
7
|
-
|
|
7
|
+
// The application resolves locale from its own configuration and routes and
|
|
8
|
+
// passes it through `FimoProvider`. `undefined` leaves `locale` off the
|
|
9
|
+
// request so the API answers with the project default; an environment
|
|
10
|
+
// variable must never become the project's source of truth for it.
|
|
11
|
+
return undefined;
|
|
8
12
|
},
|
|
9
13
|
headers(extra) {
|
|
10
14
|
const headers = { ...extra };
|
|
@@ -13,6 +13,7 @@ export type { CollectionContentCore, CollectionPage, CollectionPageInfo, Content
|
|
|
13
13
|
export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../content/index.js';
|
|
14
14
|
export { Boolean } from '../react/primitives/editable/Boolean.js';
|
|
15
15
|
export { Date } from '../react/primitives/editable/Date.js';
|
|
16
|
+
export { DateTime } from '../react/primitives/editable/DateTime.js';
|
|
16
17
|
export { Image } from '../react/primitives/editable/Image.js';
|
|
17
18
|
export { Json } from '../react/primitives/editable/Json.js';
|
|
18
19
|
export { RichText } from '../react/primitives/editable/RichText.js';
|
|
@@ -21,7 +22,7 @@ export { Text } from '../react/primitives/editable/Text.js';
|
|
|
21
22
|
export { Video } from '../react/primitives/editable/Video.js';
|
|
22
23
|
export type { BoolOps, DateOps, Field, Fields, FilterFor, FimoPart, FimoPolymorphicReference, FimoReference, FimoRichTextContent, FimoSource, NullOnlyOps, NumberOps, Populate, Populated, PopulatedResult, Projected, ProjectedWithPopulate, Query, ReferenceField, ReferenceMap, ScalarOps, Sort, SortField, StringOps, Where, WithReferences, } from '../content/index.js';
|
|
23
24
|
export type { FimoRichTextComponents, FimoRichTextMarkProps, FimoRichTextNodeProps, } from '../react/primitives/contracts.js';
|
|
24
|
-
export { FimoProvider, useLabels } from './provider.js';
|
|
25
|
-
export type { FimoProviderProps } from './provider.js';
|
|
25
|
+
export { FimoPreviewBridge, FimoProvider, useLabels } from './provider.js';
|
|
26
|
+
export type { FimoPreviewBridgeProps, FimoProviderProps } from './provider.js';
|
|
26
27
|
export type { Labels, LabelsSnapshot } from '../labels/core.js';
|
|
27
28
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -15,10 +15,11 @@ export { createContentCore, createSingletonCore } from '../content/index.js';
|
|
|
15
15
|
export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../content/index.js';
|
|
16
16
|
export { Boolean } from '../react/primitives/editable/Boolean.js';
|
|
17
17
|
export { Date } from '../react/primitives/editable/Date.js';
|
|
18
|
+
export { DateTime } from '../react/primitives/editable/DateTime.js';
|
|
18
19
|
export { Image } from '../react/primitives/editable/Image.js';
|
|
19
20
|
export { Json } from '../react/primitives/editable/Json.js';
|
|
20
21
|
export { RichText } from '../react/primitives/editable/RichText.js';
|
|
21
22
|
export { StaticImage } from '../react/primitives/renderers/StaticImage.js';
|
|
22
23
|
export { Text } from '../react/primitives/editable/Text.js';
|
|
23
24
|
export { Video } from '../react/primitives/editable/Video.js';
|
|
24
|
-
export { FimoProvider, useLabels } from './provider.js';
|
|
25
|
+
export { FimoPreviewBridge, FimoProvider, useLabels } from './provider.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type GetLabelsOptions } from '../labels/fetch.js';
|
|
2
2
|
/**
|
|
3
|
-
* Read the labels for an application-resolved locale. When omitted,
|
|
4
|
-
*
|
|
3
|
+
* Read the labels for an application-resolved locale. When omitted, the request
|
|
4
|
+
* carries no locale and the Fimo API answers with the project default locale.
|
|
5
5
|
*/
|
|
6
6
|
export declare function getLabels(options?: GetLabelsOptions): Promise<import("./client.js").Labels>;
|
|
7
7
|
//# sourceMappingURL=labels.d.ts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fetchLabels } from '../labels/fetch.js';
|
|
2
2
|
import { nextServerContentRuntime } from './server-runtime.js';
|
|
3
3
|
/**
|
|
4
|
-
* Read the labels for an application-resolved locale. When omitted,
|
|
5
|
-
*
|
|
4
|
+
* Read the labels for an application-resolved locale. When omitted, the request
|
|
5
|
+
* carries no locale and the Fimo API answers with the project default locale.
|
|
6
6
|
*/
|
|
7
7
|
export function getLabels(options) {
|
|
8
8
|
return fetchLabels(nextServerContentRuntime, options);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface NextPreviewBrowser {
|
|
2
|
+
readonly window: Pick<Window, 'addEventListener' | 'location' | 'parent' | 'removeEventListener' | 'sessionStorage'>;
|
|
3
|
+
readonly document: Pick<Document, 'referrer'>;
|
|
4
|
+
}
|
|
5
|
+
type NextPreviewConnection = {
|
|
6
|
+
readonly locale: string;
|
|
7
|
+
readonly navigate: (to: string) => unknown;
|
|
8
|
+
readonly pathname: string;
|
|
9
|
+
readonly previewScriptUrl: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare function connectNextPreview({ locale, navigate, pathname, previewScriptUrl }: NextPreviewConnection, browser?: NextPreviewBrowser): () => void;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getSafePreviewNavigationPath, isPreviewRuntimeEnabled } from '../preview/bridge.js';
|
|
2
|
+
import { getPreviewLocationChange, getPreviewParentOrigin } from '../preview/location.js';
|
|
3
|
+
export function connectNextPreview({ locale, navigate, pathname, previewScriptUrl }, browser = { window, document }) {
|
|
4
|
+
if (!isPreviewRuntimeEnabled(previewScriptUrl)) {
|
|
5
|
+
return () => undefined;
|
|
6
|
+
}
|
|
7
|
+
const parentOrigin = getPreviewParentOrigin(browser);
|
|
8
|
+
const sendLocationChange = () => {
|
|
9
|
+
browser.window.parent.postMessage({
|
|
10
|
+
type: 'nav/locationChange',
|
|
11
|
+
payload: getPreviewLocationChange({ pathname, search: browser.window.location.search, hash: browser.window.location.hash }, locale, parentOrigin),
|
|
12
|
+
}, parentOrigin ?? '*');
|
|
13
|
+
};
|
|
14
|
+
const handleMessage = (event) => {
|
|
15
|
+
if (event.source !== browser.window.parent || !parentOrigin || event.origin !== parentOrigin) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (event.data?.type === 'nav/navigate') {
|
|
19
|
+
const target = getSafePreviewNavigationPath(event.data?.payload?.to, browser.window.location.origin);
|
|
20
|
+
if (target) {
|
|
21
|
+
navigate(target);
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (event.data?.type === 'nav/read-routes') {
|
|
26
|
+
sendLocationChange();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
browser.window.addEventListener('message', handleMessage);
|
|
30
|
+
sendLocationChange();
|
|
31
|
+
return () => {
|
|
32
|
+
browser.window.removeEventListener('message', handleMessage);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -2,6 +2,8 @@ import { type ElementType } from 'react';
|
|
|
2
2
|
import type { BooleanProps, DateProps, ImageProps, JsonProps, RichTextProps, VideoProps } from '../../react/primitives/contracts.js';
|
|
3
3
|
export declare function Boolean<T extends ElementType = 'span'>({ value, as, children, ...props }: BooleanProps<T>): import("react/jsx-runtime").JSX.Element | null;
|
|
4
4
|
export declare function Date({ value, ...props }: DateProps): import("react/jsx-runtime").JSX.Element | null;
|
|
5
|
+
/** Renders a `datetime` field: the `dateTime` attribute keeps the whole instant. */
|
|
6
|
+
export declare function DateTime({ value, ...props }: DateProps): import("react/jsx-runtime").JSX.Element | null;
|
|
5
7
|
export declare function Image(props: ImageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
8
|
export declare function Json<T extends ElementType = 'span'>({ value, as, children, ...props }: JsonProps<T>): import("react/jsx-runtime").JSX.Element | null;
|
|
7
9
|
export declare function RichText({ value, components, ...props }: RichTextProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -19,6 +19,14 @@ export function Date({ value, ...props }) {
|
|
|
19
19
|
const source = typeof value === 'object' && FIMO_SOURCE in value ? (value[FIMO_SOURCE] ?? undefined) : undefined;
|
|
20
20
|
return _jsx(DateRenderer, { ...props, value: value, source: source });
|
|
21
21
|
}
|
|
22
|
+
/** Renders a `datetime` field: the `dateTime` attribute keeps the whole instant. */
|
|
23
|
+
export function DateTime({ value, ...props }) {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const source = typeof value === 'object' && FIMO_SOURCE in value ? (value[FIMO_SOURCE] ?? undefined) : undefined;
|
|
28
|
+
return _jsx(DateRenderer, { ...props, value: value, source: source, precision: "instant" });
|
|
29
|
+
}
|
|
22
30
|
export function Image(props) {
|
|
23
31
|
if (!props.value) {
|
|
24
32
|
return null;
|
|
@@ -8,6 +8,11 @@ export interface FimoProviderProps {
|
|
|
8
8
|
queryClient?: QueryClient;
|
|
9
9
|
}
|
|
10
10
|
export declare function FimoProvider({ children, labels, locale, queryClient }: FimoProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export interface FimoPreviewBridgeProps {
|
|
12
|
+
readonly navigate: (to: string) => unknown;
|
|
13
|
+
readonly pathname: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function FimoPreviewBridge({ navigate, pathname }: FimoPreviewBridgeProps): ReactNode;
|
|
11
16
|
export declare function useNextContentLocale(requestedLocale: string | undefined, defaultLocale: string): string;
|
|
12
17
|
export declare function useLabels(): Labels;
|
|
13
18
|
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
-
import { createContext, useContext, useMemo, useState } from 'react';
|
|
4
|
+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import { createLabels } from '../labels/core.js';
|
|
6
|
-
import {
|
|
6
|
+
import { connectNextPreview } from './preview.js';
|
|
7
|
+
/**
|
|
8
|
+
* Last resort when an application renders `FimoProvider` without a locale and
|
|
9
|
+
* without labels. The project's default locale lives in
|
|
10
|
+
* `.fimo/config.json#i18n.defaultLocale`, which the application reads and passes
|
|
11
|
+
* in; no environment variable participates.
|
|
12
|
+
*/
|
|
13
|
+
const UNRESOLVED_LOCALE = 'en';
|
|
7
14
|
const FimoNextContext = createContext({});
|
|
8
15
|
export function FimoProvider({ children, labels, locale, queryClient }) {
|
|
9
16
|
const [client] = useState(() => queryClient ?? new QueryClient());
|
|
@@ -11,13 +18,20 @@ export function FimoProvider({ children, labels, locale, queryClient }) {
|
|
|
11
18
|
const value = useMemo(() => ({ labels, locale: activeLocale }), [activeLocale, labels]);
|
|
12
19
|
return (_jsx(FimoNextContext.Provider, { value: value, children: _jsx(QueryClientProvider, { client: client, children: children }) }));
|
|
13
20
|
}
|
|
21
|
+
export function FimoPreviewBridge({ navigate, pathname }) {
|
|
22
|
+
const context = useContext(FimoNextContext);
|
|
23
|
+
const locale = context.locale ?? UNRESOLVED_LOCALE;
|
|
24
|
+
const previewScriptUrl = process.env.NEXT_PUBLIC_FIMO_PREVIEW_SCRIPT_URL;
|
|
25
|
+
useEffect(() => connectNextPreview({ locale, navigate, pathname, previewScriptUrl }), [locale, navigate, pathname, previewScriptUrl]);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
14
28
|
export function useNextContentLocale(requestedLocale, defaultLocale) {
|
|
15
29
|
const context = useContext(FimoNextContext);
|
|
16
30
|
return requestedLocale ?? context.locale ?? defaultLocale;
|
|
17
31
|
}
|
|
18
32
|
export function useLabels() {
|
|
19
33
|
const context = useContext(FimoNextContext);
|
|
20
|
-
const locale = context.locale ??
|
|
34
|
+
const locale = context.locale ?? UNRESOLVED_LOCALE;
|
|
21
35
|
const snapshot = context.labels?.locale === locale
|
|
22
36
|
? context.labels
|
|
23
37
|
: { locale, labels: {}, updatedAt: null };
|
|
@@ -4,7 +4,10 @@ export const nextServerContentRuntime = {
|
|
|
4
4
|
return requireContentApiUrl(process.env.FIMO_CONTENT_API_URL, 'FIMO_CONTENT_API_URL');
|
|
5
5
|
},
|
|
6
6
|
defaultLocale() {
|
|
7
|
-
|
|
7
|
+
// `undefined` leaves `locale` off the request so the tenant API answers with
|
|
8
|
+
// the project default declared in `.fimo/config.json#i18n.defaultLocale`.
|
|
9
|
+
// Route-resolved locales reach Fimo through `createFimo`/`getLabels`.
|
|
10
|
+
return undefined;
|
|
8
11
|
},
|
|
9
12
|
headers(extra) {
|
|
10
13
|
const headers = { ...extra };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CollectionContentCore, ContentClientConfig, SingletonContentCore } from '../content/client/core-types.js';
|
|
2
2
|
export { StaticImage } from '../react/primitives/renderers/StaticImage.js';
|
|
3
3
|
export { Text } from './primitives/Text.js';
|
|
4
|
-
export { Boolean, Date, Image, Json, RichText, Video } from './primitives/server.js';
|
|
4
|
+
export { Boolean, Date, DateTime, Image, Json, RichText, Video } from './primitives/server.js';
|
|
5
5
|
export { createFimo } from './facade.js';
|
|
6
6
|
export type { CreateFimoOptions, NextContentRegistry } from './facade.js';
|
|
7
7
|
export { getLabels } from './labels.js';
|
|
@@ -3,7 +3,7 @@ import { createFormClient } from '../forms/index.js';
|
|
|
3
3
|
import { nextServerContentRuntime } from './server-runtime.js';
|
|
4
4
|
export { StaticImage } from '../react/primitives/renderers/StaticImage.js';
|
|
5
5
|
export { Text } from './primitives/Text.js';
|
|
6
|
-
export { Boolean, Date, Image, Json, RichText, Video } from './primitives/server.js';
|
|
6
|
+
export { Boolean, Date, DateTime, Image, Json, RichText, Video } from './primitives/server.js';
|
|
7
7
|
export { createFimo } from './facade.js';
|
|
8
8
|
export { getLabels } from './labels.js';
|
|
9
9
|
export function createContentClient(config) {
|
|
@@ -5,11 +5,12 @@ import type { FimoPath, GetFimoPathsOptions } from './types.js';
|
|
|
5
5
|
* robots.txt, or any per-route logic.
|
|
6
6
|
*
|
|
7
7
|
* - Static paths in config: emitted as-is.
|
|
8
|
-
* - Dynamic paths (with `:slug`-style params): expanded by fetching
|
|
9
|
-
* entries from `VITE_API_URL`. One returned entry per
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - Dynamic paths (with `:slug`-style entry params): expanded by fetching
|
|
9
|
+
* schema entries from `VITE_API_URL`. One returned entry per expansion.
|
|
10
|
+
* - Locales come from the route contract, not from the project locale list: a
|
|
11
|
+
* route is emitted once per locale only when its path declares a `:locale`
|
|
12
|
+
* segment. The default locale is always unprefixed. A route without a
|
|
13
|
+
* `:locale` segment yields exactly one default-locale path.
|
|
13
14
|
* - Pure-string return shape (`p.path`) is suitable for RR's `prerender`.
|
|
14
15
|
*
|
|
15
16
|
* If no API URL is set, dynamic routes are skipped with a console warning.
|