@nitrogenbuilder/connector-payload 1.1.0 → 1.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/collections/NitrogenComponentUsage.js +3 -21
- package/dist/collections/NitrogenTemplates.js +8 -0
- package/dist/components/NitrogenComponentInventoryClient.js +1 -1
- package/dist/endpoints/all.js +3 -0
- package/dist/endpoints/batch.js +9 -0
- package/dist/endpoints/collection-endpoints.js +119 -3
- package/dist/endpoints/helpers.js +2 -1
- package/dist/endpoints/menu.js +2 -0
- package/dist/endpoints/sitemap.js +47 -7
- package/dist/endpoints/templates.js +116 -0
- package/dist/endpoints/translation-status.d.ts +11 -0
- package/dist/endpoints/translation-status.js +105 -0
- package/dist/frontend/NitrogenPageClient.d.ts +6 -1
- package/dist/frontend/NitrogenPageClient.js +6 -2
- package/dist/frontend/NitrogenWrapper.d.ts +8 -1
- package/dist/frontend/NitrogenWrapper.js +25 -3
- package/dist/index.d.ts +22 -0
- package/dist/index.js +69 -2
- package/dist/inventory/indexing.d.ts +2 -5
- package/dist/inventory/indexing.js +129 -73
- package/dist/localization.d.ts +121 -0
- package/dist/localization.js +563 -0
- package/dist/types.d.ts +3 -5
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getNitrogenSettings, buildDynamicData, buildResolvedPageResponse, buildResolvedListItemResponse, getDocumentPermalink, getDocumentRelativePermalink, getTemplateForType, requireAuth, } from './helpers.js';
|
|
2
|
+
import { buildDynamicDataMeta, computeDocTranslationStatus, fetchLocaleAllDoc, getDefaultLocaleTitle, getLocalizationSettings, getPayloadLocalization, getRequestLanguage, resolveLocaleOptions, } from '../localization.js';
|
|
2
3
|
function isContentTemplateCollection(collection) {
|
|
3
4
|
return !!collection && collection !== 'header' && collection !== 'footer';
|
|
4
5
|
}
|
|
@@ -25,6 +26,7 @@ export const templatesEndpoints = [
|
|
|
25
26
|
handler: async (req) => {
|
|
26
27
|
const { payload, routeParams } = req;
|
|
27
28
|
const slug = routeParams?.slug;
|
|
29
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
28
30
|
try {
|
|
29
31
|
const [result, headerTemplate, footerTemplate] = await Promise.all([
|
|
30
32
|
payload.find({
|
|
@@ -32,6 +34,7 @@ export const templatesEndpoints = [
|
|
|
32
34
|
where: { slug: { equals: slug } },
|
|
33
35
|
limit: 1,
|
|
34
36
|
depth: 1,
|
|
37
|
+
...localeOptions,
|
|
35
38
|
}),
|
|
36
39
|
getTemplateForType(payload, 'header'),
|
|
37
40
|
getTemplateForType(payload, 'footer'),
|
|
@@ -50,6 +53,7 @@ export const templatesEndpoints = [
|
|
|
50
53
|
collection: associatedCollection,
|
|
51
54
|
limit: 1,
|
|
52
55
|
depth: 1,
|
|
56
|
+
...localeOptions,
|
|
53
57
|
});
|
|
54
58
|
if (associated.docs.length) {
|
|
55
59
|
associatedDoc = associated.docs[0];
|
|
@@ -61,9 +65,24 @@ export const templatesEndpoints = [
|
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData);
|
|
68
|
+
// When a language was requested, the top-level `title` must stay the
|
|
69
|
+
// default-locale one — the editor edits default-language field data
|
|
70
|
+
// only and PATCHes `title` back on save. The meta may describe the
|
|
71
|
+
// associated doc, but the round-tripped title is the template's own.
|
|
72
|
+
const templateAllDoc = localeOptions.locale
|
|
73
|
+
? await fetchLocaleAllDoc(payload, 'nitrogen-templates', doc.id)
|
|
74
|
+
: undefined;
|
|
75
|
+
const dynamicDataMeta = associatedDoc
|
|
76
|
+
? await buildDynamicDataMeta(payload, associatedCollection, associatedDoc.id)
|
|
77
|
+
: await buildDynamicDataMeta(payload, 'nitrogen-templates', doc.id, templateAllDoc);
|
|
78
|
+
const titleOverride = localeOptions.locale
|
|
79
|
+
? getDefaultLocaleTitle(payload, templateAllDoc ?? null)
|
|
80
|
+
: null;
|
|
64
81
|
const previewTarget = getTemplatePreviewTarget(doc, settings, associatedCollection, associatedDoc);
|
|
65
82
|
return Response.json({
|
|
66
83
|
...response,
|
|
84
|
+
...(titleOverride !== null ? { title: titleOverride } : {}),
|
|
85
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
67
86
|
slug: previewTarget.slug,
|
|
68
87
|
permalink: previewTarget.permalink,
|
|
69
88
|
relative_permalink: previewTarget.relative_permalink,
|
|
@@ -87,6 +106,7 @@ export const templatesEndpoints = [
|
|
|
87
106
|
const settings = await getNitrogenSettings(payload);
|
|
88
107
|
const url = new URL(req.url || '', 'http://localhost');
|
|
89
108
|
const templateType = url.searchParams.get('templateType');
|
|
109
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
90
110
|
const where = {
|
|
91
111
|
status: { equals: 'published' },
|
|
92
112
|
};
|
|
@@ -98,6 +118,7 @@ export const templatesEndpoints = [
|
|
|
98
118
|
where,
|
|
99
119
|
limit: 0,
|
|
100
120
|
depth: 1,
|
|
121
|
+
...localeOptions,
|
|
101
122
|
});
|
|
102
123
|
const items = await Promise.all(result.docs.map(async (doc) => {
|
|
103
124
|
const response = await buildResolvedListItemResponse(payload, doc, settings);
|
|
@@ -118,12 +139,14 @@ export const templatesEndpoints = [
|
|
|
118
139
|
handler: async (req) => {
|
|
119
140
|
const { payload, routeParams } = req;
|
|
120
141
|
const id = routeParams?.id;
|
|
142
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
121
143
|
try {
|
|
122
144
|
const [doc, headerTemplate, footerTemplate] = await Promise.all([
|
|
123
145
|
payload.findByID({
|
|
124
146
|
collection: 'nitrogen-templates',
|
|
125
147
|
id,
|
|
126
148
|
depth: 1,
|
|
149
|
+
...localeOptions,
|
|
127
150
|
}),
|
|
128
151
|
getTemplateForType(payload, 'header'),
|
|
129
152
|
getTemplateForType(payload, 'footer'),
|
|
@@ -140,6 +163,7 @@ export const templatesEndpoints = [
|
|
|
140
163
|
collection: associatedCollection,
|
|
141
164
|
limit: 1,
|
|
142
165
|
depth: 1,
|
|
166
|
+
...localeOptions,
|
|
143
167
|
});
|
|
144
168
|
if (associated.docs.length) {
|
|
145
169
|
associatedDoc = associated.docs[0];
|
|
@@ -151,9 +175,22 @@ export const templatesEndpoints = [
|
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData);
|
|
178
|
+
// See GET slug/:slug — keep the round-tripped `title` at the default
|
|
179
|
+
// locale.
|
|
180
|
+
const templateAllDoc = localeOptions.locale
|
|
181
|
+
? await fetchLocaleAllDoc(payload, 'nitrogen-templates', doc.id)
|
|
182
|
+
: undefined;
|
|
183
|
+
const dynamicDataMeta = associatedDoc
|
|
184
|
+
? await buildDynamicDataMeta(payload, associatedCollection, associatedDoc.id)
|
|
185
|
+
: await buildDynamicDataMeta(payload, 'nitrogen-templates', doc.id, templateAllDoc);
|
|
186
|
+
const titleOverride = localeOptions.locale
|
|
187
|
+
? getDefaultLocaleTitle(payload, templateAllDoc ?? null)
|
|
188
|
+
: null;
|
|
154
189
|
const previewTarget = getTemplatePreviewTarget(doc, settings, associatedCollection, associatedDoc);
|
|
155
190
|
return Response.json({
|
|
156
191
|
...response,
|
|
192
|
+
...(titleOverride !== null ? { title: titleOverride } : {}),
|
|
193
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
157
194
|
slug: previewTarget.slug,
|
|
158
195
|
permalink: previewTarget.permalink,
|
|
159
196
|
relative_permalink: previewTarget.relative_permalink,
|
|
@@ -168,6 +205,56 @@ export const templatesEndpoints = [
|
|
|
168
205
|
}
|
|
169
206
|
},
|
|
170
207
|
},
|
|
208
|
+
// GET /api/nitrogen/v1/nitrogen-templates/:id/dynamic-data — Dynamic data
|
|
209
|
+
// (+ meta) for a template, optionally in a specific language (?lang=es).
|
|
210
|
+
{
|
|
211
|
+
path: '/nitrogen/v1/nitrogen-templates/:id/dynamic-data',
|
|
212
|
+
method: 'get',
|
|
213
|
+
handler: async (req) => {
|
|
214
|
+
const { payload, routeParams } = req;
|
|
215
|
+
const id = routeParams?.id;
|
|
216
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
217
|
+
try {
|
|
218
|
+
const doc = (await payload.findByID({
|
|
219
|
+
collection: 'nitrogen-templates',
|
|
220
|
+
id,
|
|
221
|
+
depth: 1,
|
|
222
|
+
...localeOptions,
|
|
223
|
+
}));
|
|
224
|
+
const settings = await getNitrogenSettings(payload);
|
|
225
|
+
let dynamicData = buildDynamicData(doc, settings);
|
|
226
|
+
let associatedDoc;
|
|
227
|
+
const associatedCollection = doc.associatedCollection;
|
|
228
|
+
if (isContentTemplateCollection(associatedCollection)) {
|
|
229
|
+
try {
|
|
230
|
+
const associated = await payload.find({
|
|
231
|
+
collection: associatedCollection,
|
|
232
|
+
limit: 1,
|
|
233
|
+
depth: 1,
|
|
234
|
+
...localeOptions,
|
|
235
|
+
});
|
|
236
|
+
if (associated.docs.length) {
|
|
237
|
+
associatedDoc = associated.docs[0];
|
|
238
|
+
dynamicData = buildDynamicData(associatedDoc, settings);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Collection may not exist — use template's own data
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const dynamicDataMeta = associatedDoc
|
|
246
|
+
? await buildDynamicDataMeta(payload, associatedCollection, associatedDoc.id)
|
|
247
|
+
: await buildDynamicDataMeta(payload, 'nitrogen-templates', doc.id);
|
|
248
|
+
return Response.json({
|
|
249
|
+
dynamic_data: dynamicData,
|
|
250
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
},
|
|
171
258
|
// PATCH /api/nitrogen/v1/templates/:id — Update a template
|
|
172
259
|
{
|
|
173
260
|
path: '/nitrogen/v1/nitrogen-templates/:id',
|
|
@@ -206,10 +293,39 @@ export const templatesEndpoints = [
|
|
|
206
293
|
if (Object.keys(updateData).length === 0) {
|
|
207
294
|
return Response.json({ error: 'No update data provided' }, { status: 400 });
|
|
208
295
|
}
|
|
296
|
+
// Localization enabled → recompute per-language translation status from
|
|
297
|
+
// the saved module tree. Advisory only — never blocks the save.
|
|
298
|
+
if (updateData.nitrogenData !== undefined) {
|
|
299
|
+
try {
|
|
300
|
+
const settings = await getNitrogenSettings(payload);
|
|
301
|
+
const localization = getLocalizationSettings(settings);
|
|
302
|
+
if (localization) {
|
|
303
|
+
const translationStatus = await computeDocTranslationStatus({
|
|
304
|
+
payload,
|
|
305
|
+
collectionSlug: 'nitrogen-templates',
|
|
306
|
+
docId: id,
|
|
307
|
+
nitrogenData: updateData.nitrogenData,
|
|
308
|
+
localization,
|
|
309
|
+
});
|
|
310
|
+
if (translationStatus) {
|
|
311
|
+
updateData.nitrogenTranslationStatus = translationStatus;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Status is advisory — never block the save.
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// The editor edits default-language field data only — pin the write to
|
|
320
|
+
// the default locale so it can never target a request-derived locale.
|
|
321
|
+
const payloadLocalization = getPayloadLocalization(payload);
|
|
209
322
|
await payload.update({
|
|
210
323
|
collection: 'nitrogen-templates',
|
|
211
324
|
id,
|
|
212
325
|
data: updateData,
|
|
326
|
+
...(payloadLocalization
|
|
327
|
+
? { locale: payloadLocalization.defaultLocale }
|
|
328
|
+
: {}),
|
|
213
329
|
});
|
|
214
330
|
return Response.json({ success: true });
|
|
215
331
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation status route — lists per-language translation coverage for
|
|
3
|
+
* every Nitrogen-enabled document and template, for the editor's translation
|
|
4
|
+
* dashboard.
|
|
5
|
+
*
|
|
6
|
+
* The status is computed and stored (in `nitrogenTranslationStatus`) by the
|
|
7
|
+
* save handlers; this route reads the stored value and only computes on the
|
|
8
|
+
* fly for docs that have never been saved since localization was enabled.
|
|
9
|
+
*/
|
|
10
|
+
import type { Endpoint } from 'payload';
|
|
11
|
+
export declare function createTranslationStatusEndpoints(collections: string[]): Endpoint[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation status route — lists per-language translation coverage for
|
|
3
|
+
* every Nitrogen-enabled document and template, for the editor's translation
|
|
4
|
+
* dashboard.
|
|
5
|
+
*
|
|
6
|
+
* The status is computed and stored (in `nitrogenTranslationStatus`) by the
|
|
7
|
+
* save handlers; this route reads the stored value and only computes on the
|
|
8
|
+
* fly for docs that have never been saved since localization was enabled.
|
|
9
|
+
*/
|
|
10
|
+
import { getNitrogenSettings, requireAuth } from './helpers.js';
|
|
11
|
+
import { findAllDocs } from '../inventory/indexing.js';
|
|
12
|
+
import { computeDocTranslationStatus, getLocalizationSettings, getPayloadLocalization, loadTranslatablePropDefs, } from '../localization.js';
|
|
13
|
+
function isStoredStatus(value) {
|
|
14
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Docs fetched at locale 'all' carry localized fields as locale-keyed
|
|
18
|
+
* objects — read the default language's title in that case.
|
|
19
|
+
*/
|
|
20
|
+
function docTitle(doc, defaultLanguage) {
|
|
21
|
+
const title = doc.title;
|
|
22
|
+
if (typeof title === 'string')
|
|
23
|
+
return title;
|
|
24
|
+
if (title && typeof title === 'object' && !Array.isArray(title)) {
|
|
25
|
+
const record = title;
|
|
26
|
+
const value = record[defaultLanguage];
|
|
27
|
+
if (typeof value === 'string')
|
|
28
|
+
return value;
|
|
29
|
+
// No default-language title — fall back to the first stored one, matching
|
|
30
|
+
// the renderer's language-resolution fallback chain.
|
|
31
|
+
for (const candidate of Object.values(record)) {
|
|
32
|
+
if (typeof candidate === 'string' && candidate)
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
export function createTranslationStatusEndpoints(collections) {
|
|
39
|
+
return [
|
|
40
|
+
// GET /api/nitrogen/v1/translation-status — TranslationStatusEntry[] across
|
|
41
|
+
// configured collections + nitrogen-templates
|
|
42
|
+
{
|
|
43
|
+
path: '/nitrogen/v1/translation-status',
|
|
44
|
+
method: 'get',
|
|
45
|
+
handler: async (req) => {
|
|
46
|
+
const authError = requireAuth(req);
|
|
47
|
+
if (authError)
|
|
48
|
+
return authError;
|
|
49
|
+
const { payload } = req;
|
|
50
|
+
const settings = await getNitrogenSettings(payload);
|
|
51
|
+
const localization = getLocalizationSettings(settings);
|
|
52
|
+
if (!localization) {
|
|
53
|
+
return Response.json([]);
|
|
54
|
+
}
|
|
55
|
+
// One catalog load shared by every on-the-fly computation.
|
|
56
|
+
const propDefs = await loadTranslatablePropDefs(payload);
|
|
57
|
+
// With Payload-native localization, fetch docs at locale 'all' so
|
|
58
|
+
// field-level coverage can be read without per-doc re-fetches.
|
|
59
|
+
const locale = getPayloadLocalization(payload) ? 'all' : undefined;
|
|
60
|
+
const entries = [];
|
|
61
|
+
const allCollections = Array.from(new Set([...collections, 'nitrogen-templates']));
|
|
62
|
+
for (const collectionSlug of allCollections) {
|
|
63
|
+
let docs;
|
|
64
|
+
try {
|
|
65
|
+
docs = await findAllDocs(payload, collectionSlug, { locale });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// One bad collection shouldn't 500 the whole listing — skip it.
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const type = collectionSlug === 'nitrogen-templates' ? 'template' : 'page';
|
|
72
|
+
for (const doc of docs) {
|
|
73
|
+
let languages = null;
|
|
74
|
+
if (isStoredStatus(doc.nitrogenTranslationStatus)) {
|
|
75
|
+
languages = doc.nitrogenTranslationStatus;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
try {
|
|
79
|
+
languages = await computeDocTranslationStatus({
|
|
80
|
+
payload,
|
|
81
|
+
collectionSlug,
|
|
82
|
+
docId: doc.id,
|
|
83
|
+
nitrogenData: doc.nitrogenData,
|
|
84
|
+
localization,
|
|
85
|
+
propDefs,
|
|
86
|
+
localeAllDoc: locale ? doc : null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
languages = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
entries.push({
|
|
94
|
+
id: doc.id,
|
|
95
|
+
title: docTitle(doc, localization.defaultLanguage),
|
|
96
|
+
type,
|
|
97
|
+
languages: languages || {},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return Response.json(entries);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
@@ -5,6 +5,11 @@ export interface NitrogenPageClientProps {
|
|
|
5
5
|
dynamicData: Record<string, unknown>;
|
|
6
6
|
isBuilder?: boolean;
|
|
7
7
|
collection?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Language to render translatable builder props in (a configured language
|
|
10
|
+
* code). Forwarded to NitrogenRenderer. Omit for the default language.
|
|
11
|
+
*/
|
|
12
|
+
language?: string;
|
|
8
13
|
}
|
|
9
14
|
/**
|
|
10
15
|
* Client component that renders Nitrogen pages using React.
|
|
@@ -12,5 +17,5 @@ export interface NitrogenPageClientProps {
|
|
|
12
17
|
* Uses `useNitrogenData` hook for live preview updates in builder mode,
|
|
13
18
|
* and `NitrogenRenderer` for React-based rendering of the component tree.
|
|
14
19
|
*/
|
|
15
|
-
export declare function NitrogenPageClient({ pageId, pageData, dynamicData: initialDynamicData, isBuilder, collection, }: NitrogenPageClientProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function NitrogenPageClient({ pageId, pageData, dynamicData: initialDynamicData, isBuilder, collection, language, }: NitrogenPageClientProps): import("react/jsx-runtime").JSX.Element;
|
|
16
21
|
export default NitrogenPageClient;
|
|
@@ -9,7 +9,7 @@ import { Nitrogen } from "@nitrogenbuilder/client-core";
|
|
|
9
9
|
* Uses `useNitrogenData` hook for live preview updates in builder mode,
|
|
10
10
|
* and `NitrogenRenderer` for React-based rendering of the component tree.
|
|
11
11
|
*/
|
|
12
|
-
export function NitrogenPageClient({ pageId, pageData, dynamicData: initialDynamicData, isBuilder = false, collection, }) {
|
|
12
|
+
export function NitrogenPageClient({ pageId, pageData, dynamicData: initialDynamicData, isBuilder = false, collection, language, }) {
|
|
13
13
|
// Use the hook for live preview updates
|
|
14
14
|
const page = useNitrogenData({
|
|
15
15
|
type: "page",
|
|
@@ -50,6 +50,10 @@ export function NitrogenPageClient({ pageId, pageData, dynamicData: initialDynam
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
}, [isBuilder]);
|
|
53
|
-
|
|
53
|
+
// The `language` prop is being added to NitrogenRenderer in the client-react
|
|
54
|
+
// package; spread it loosely so this compiles against both old and new
|
|
55
|
+
// renderer typings (older renderers simply ignore the extra prop).
|
|
56
|
+
const languageProp = language ? { language } : {};
|
|
57
|
+
return (_jsx("div", { "data-nitrogen-location": "page", "data-page-id": pageId, "data-collection": collection, "data-language": language, children: _jsx(NitrogenRenderer, { page: page?.data ?? [], dynamicData: page?.dynamicData ?? initialDynamicData, requestedData: {}, ...languageProp }) }));
|
|
54
58
|
}
|
|
55
59
|
export default NitrogenPageClient;
|
|
@@ -25,6 +25,13 @@ export interface NitrogenWrapperProps {
|
|
|
25
25
|
* Regular page content to render when not in Nitrogen mode.
|
|
26
26
|
*/
|
|
27
27
|
children: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Language to render the page in (a configured locale code, e.g. 'es').
|
|
30
|
+
* Dynamic data is rebuilt from the doc fetched at that locale, and the
|
|
31
|
+
* language is forwarded to the renderer so translatable builder props
|
|
32
|
+
* resolve to it. Omit for the default language.
|
|
33
|
+
*/
|
|
34
|
+
language?: string;
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
30
37
|
* Server component that wraps your page content and automatically handles
|
|
@@ -45,5 +52,5 @@ export interface NitrogenWrapperProps {
|
|
|
45
52
|
* }
|
|
46
53
|
* ```
|
|
47
54
|
*/
|
|
48
|
-
export declare function NitrogenWrapper({ page, searchParams, collection, config, children, }: NitrogenWrapperProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
55
|
+
export declare function NitrogenWrapper({ page, searchParams, collection, config, children, language, }: NitrogenWrapperProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
49
56
|
export default NitrogenWrapper;
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { getPayload } from 'payload';
|
|
3
3
|
import { NitrogenPageClient } from './NitrogenPageClient.js';
|
|
4
4
|
import { buildDynamicData, getNitrogenSettings } from '../endpoints/helpers.js';
|
|
5
|
+
import { resolveLocaleOptions } from '../localization.js';
|
|
5
6
|
/**
|
|
6
7
|
* Server component that wraps your page content and automatically handles
|
|
7
8
|
* Nitrogen builder mode and Nitrogen-rendered pages.
|
|
@@ -21,7 +22,7 @@ import { buildDynamicData, getNitrogenSettings } from '../endpoints/helpers.js';
|
|
|
21
22
|
* }
|
|
22
23
|
* ```
|
|
23
24
|
*/
|
|
24
|
-
export async function NitrogenWrapper({ page, searchParams, collection, config, children, }) {
|
|
25
|
+
export async function NitrogenWrapper({ page, searchParams, collection, config, children, language, }) {
|
|
25
26
|
const isBuilder = searchParams['nitrogen-builder'] !== undefined;
|
|
26
27
|
const hasNitrogenData = Array.isArray(page.nitrogenData) && page.nitrogenData.length > 0;
|
|
27
28
|
// If not in builder mode and no Nitrogen data, render regular content
|
|
@@ -31,7 +32,28 @@ export async function NitrogenWrapper({ page, searchParams, collection, config,
|
|
|
31
32
|
// Fetch Nitrogen settings and build dynamic data
|
|
32
33
|
const payload = await getPayload({ config });
|
|
33
34
|
const settings = await getNitrogenSettings(payload);
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// When a language is requested (and Payload localization is configured),
|
|
36
|
+
// re-fetch the doc at that locale so dynamic data carries the translated
|
|
37
|
+
// field values. Falls back to the passed-in page on any failure.
|
|
38
|
+
let sourceDoc = page;
|
|
39
|
+
const localeOptions = resolveLocaleOptions(payload, language);
|
|
40
|
+
if (localeOptions.locale) {
|
|
41
|
+
try {
|
|
42
|
+
const localized = await payload.findByID({
|
|
43
|
+
collection: collection,
|
|
44
|
+
id: page.id,
|
|
45
|
+
depth: 1,
|
|
46
|
+
...localeOptions,
|
|
47
|
+
});
|
|
48
|
+
if (localized) {
|
|
49
|
+
sourceDoc = localized;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Locale fetch is best-effort — keep the caller's doc.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const dynamicData = buildDynamicData(sourceDoc, settings);
|
|
57
|
+
return (_jsx(NitrogenPageClient, { pageId: String(page.id), pageData: page.nitrogenData || [], dynamicData: dynamicData, isBuilder: isBuilder, collection: collection, language: language }));
|
|
36
58
|
}
|
|
37
59
|
export default NitrogenWrapper;
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,27 @@ export interface NitrogenConnectorPluginOptions {
|
|
|
34
34
|
* `users`).
|
|
35
35
|
*/
|
|
36
36
|
userCollection?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Enables Payload-native localization for field data. When set (and the
|
|
39
|
+
* Payload config does not already configure `localization` itself), the
|
|
40
|
+
* plugin injects `localization: { locales, defaultLocale, fallback: true }`
|
|
41
|
+
* into the Payload config and marks eligible text fields (`title` on
|
|
42
|
+
* Nitrogen-enabled collections and on the plugin's own template collection)
|
|
43
|
+
* as `localized: true`. Localize your own custom fields yourself.
|
|
44
|
+
*
|
|
45
|
+
* IMPORTANT: the locales here must match the site languages configured in
|
|
46
|
+
* the Nitrogen settings (`nitrogenConfig.localization.languages`), and
|
|
47
|
+
* `defaultLocale` must match `defaultLanguage` — Payload stores the field
|
|
48
|
+
* translations, Nitrogen stores the builder-content translations, and the
|
|
49
|
+
* language codes are the join key.
|
|
50
|
+
*
|
|
51
|
+
* If your Payload config already has `localization` configured, that config
|
|
52
|
+
* is respected and this option does not override it.
|
|
53
|
+
*/
|
|
54
|
+
localization?: {
|
|
55
|
+
locales: string[];
|
|
56
|
+
defaultLocale: string;
|
|
57
|
+
};
|
|
37
58
|
}
|
|
38
59
|
export declare const nitrogenConnectorPlugin: (options?: NitrogenConnectorPluginOptions) => any;
|
|
39
60
|
export { NitrogenTemplates } from "./collections/NitrogenTemplates.js";
|
|
@@ -41,6 +62,7 @@ export { NitrogenSettings } from "./globals/NitrogenSettings.js";
|
|
|
41
62
|
export { NitrogenEditButton } from "./components/NitrogenEditButton.js";
|
|
42
63
|
export { NitrogenViewButton } from "./components/NitrogenViewButton.js";
|
|
43
64
|
export { buildDynamicData, getNitrogenSettings } from "./endpoints/helpers.js";
|
|
65
|
+
export { buildDynamicDataMeta, getRequestLanguage, resolveLocaleOptions, } from "./localization.js";
|
|
44
66
|
export { createCollectionEndpoints } from "./endpoints/collection-endpoints.js";
|
|
45
67
|
export { nitrogen } from "@nitrogenbuilder/client-core";
|
|
46
68
|
export type { ComponentSettings, ComponentSettingsToProps, } from "@nitrogenbuilder/types";
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createCollectionEndpoints } from "./endpoints/collection-endpoints.js";
|
|
|
13
13
|
import { createComponentInventoryEndpoints } from './endpoints/component-inventory.js';
|
|
14
14
|
import { batchEndpoints } from "./endpoints/batch.js";
|
|
15
15
|
import { sitemapEndpoints } from "./endpoints/sitemap.js";
|
|
16
|
+
import { createTranslationStatusEndpoints } from "./endpoints/translation-status.js";
|
|
16
17
|
import { createAgentAuthEndpoints } from "./endpoints/agent-auth.js";
|
|
17
18
|
import { registerCollection } from "./collection-registry.js";
|
|
18
19
|
import { deleteDocumentUsage, reindexDocumentUsage, syncComponentCatalog, } from './inventory/indexing.js';
|
|
@@ -38,12 +39,57 @@ const nitrogenRequiredFields = [
|
|
|
38
39
|
},
|
|
39
40
|
},
|
|
40
41
|
},
|
|
42
|
+
{
|
|
43
|
+
name: "nitrogenTranslationStatus",
|
|
44
|
+
type: "json",
|
|
45
|
+
admin: {
|
|
46
|
+
hidden: true,
|
|
47
|
+
description: "Per-language translation coverage, computed by Nitrogen on save",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
41
50
|
];
|
|
51
|
+
/**
|
|
52
|
+
* Marks `title` text fields as `localized: true` (recursing into rows, tabs,
|
|
53
|
+
* groups, collapsibles). Fields that already declare `localized` explicitly
|
|
54
|
+
* are left untouched.
|
|
55
|
+
*/
|
|
56
|
+
function localizeTitleFields(fields) {
|
|
57
|
+
return fields.map((field) => {
|
|
58
|
+
const f = field;
|
|
59
|
+
if (f.type === "text" && f.name === "title" && f.localized === undefined) {
|
|
60
|
+
return { ...field, localized: true };
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(f.fields)) {
|
|
63
|
+
return { ...field, fields: localizeTitleFields(f.fields) };
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(f.tabs)) {
|
|
66
|
+
return {
|
|
67
|
+
...field,
|
|
68
|
+
tabs: f.tabs.map((tab) => ({
|
|
69
|
+
...tab,
|
|
70
|
+
fields: localizeTitleFields(tab.fields || []),
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return field;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
42
77
|
export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
|
|
43
78
|
if (options.disabled) {
|
|
44
79
|
return incomingConfig;
|
|
45
80
|
}
|
|
46
81
|
const config = { ...incomingConfig };
|
|
82
|
+
// Payload-native localization for field data. Only injected when the host
|
|
83
|
+
// config doesn't configure localization itself — an existing config is
|
|
84
|
+
// always respected.
|
|
85
|
+
if (options.localization && !incomingConfig.localization) {
|
|
86
|
+
config.localization = {
|
|
87
|
+
locales: options.localization.locales,
|
|
88
|
+
defaultLocale: options.localization.defaultLocale,
|
|
89
|
+
fallback: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const localizationActive = Boolean(config.localization);
|
|
47
93
|
// The auth collection that owns MCP agent credentials. Prefer the explicit
|
|
48
94
|
// option, else the first auth-enabled collection, else `users`.
|
|
49
95
|
const userCollectionSlug = options.userCollection ||
|
|
@@ -83,10 +129,19 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
|
|
|
83
129
|
},
|
|
84
130
|
};
|
|
85
131
|
};
|
|
86
|
-
// Add Nitrogen templates collection
|
|
132
|
+
// Add Nitrogen templates collection. With localization active, template
|
|
133
|
+
// titles are localized (translated in the Payload admin's locale
|
|
134
|
+
// switcher); internal catalog/usage collections stay non-localized —
|
|
135
|
+
// their text fields are machine identifiers, not content.
|
|
136
|
+
const templatesCollection = localizationActive
|
|
137
|
+
? {
|
|
138
|
+
...NitrogenTemplates,
|
|
139
|
+
fields: localizeTitleFields(NitrogenTemplates.fields),
|
|
140
|
+
}
|
|
141
|
+
: NitrogenTemplates;
|
|
87
142
|
config.collections = [
|
|
88
143
|
...(config.collections || []),
|
|
89
|
-
withInventoryHooks(
|
|
144
|
+
withInventoryHooks(templatesCollection, 'nitrogen-templates'),
|
|
90
145
|
NitrogenComponentCatalog,
|
|
91
146
|
NitrogenComponentUsage,
|
|
92
147
|
];
|
|
@@ -103,6 +158,7 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
|
|
|
103
158
|
...menuEndpoints,
|
|
104
159
|
...batchEndpoints,
|
|
105
160
|
...sitemapEndpoints,
|
|
161
|
+
...createTranslationStatusEndpoints(options.collections || []),
|
|
106
162
|
...createAgentAuthEndpoints(userCollectionSlug),
|
|
107
163
|
...createComponentInventoryEndpoints({
|
|
108
164
|
collections: inventoryCollections,
|
|
@@ -191,6 +247,16 @@ export const nitrogenConnectorPlugin = (options = {}) => (incomingConfig) => {
|
|
|
191
247
|
fields: [...existing.fields, ...fieldsToAdd],
|
|
192
248
|
};
|
|
193
249
|
}
|
|
250
|
+
// With localization active, the collection's title becomes localized so
|
|
251
|
+
// it can be translated via the Payload admin's native locale switcher.
|
|
252
|
+
// (Users localize their own custom fields themselves.)
|
|
253
|
+
if (localizationActive) {
|
|
254
|
+
const current = config.collections[existingIndex];
|
|
255
|
+
config.collections[existingIndex] = {
|
|
256
|
+
...current,
|
|
257
|
+
fields: localizeTitleFields(current.fields),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
194
260
|
// Inject NitrogenEditButton admin component
|
|
195
261
|
const col = config.collections[existingIndex];
|
|
196
262
|
const adminConfig = col.admin || {};
|
|
@@ -301,6 +367,7 @@ export { NitrogenEditButton } from "./components/NitrogenEditButton.js";
|
|
|
301
367
|
export { NitrogenViewButton } from "./components/NitrogenViewButton.js";
|
|
302
368
|
// Helper exports for consumers building custom frontend routes
|
|
303
369
|
export { buildDynamicData, getNitrogenSettings } from "./endpoints/helpers.js";
|
|
370
|
+
export { buildDynamicDataMeta, getRequestLanguage, resolveLocaleOptions, } from "./localization.js";
|
|
304
371
|
// Collection endpoint factory for consumers who need custom endpoint generation
|
|
305
372
|
export { createCollectionEndpoints } from "./endpoints/collection-endpoints.js";
|
|
306
373
|
// Re-export @nitrogenbuilder packages for convenience
|
|
@@ -6,12 +6,7 @@ export declare const NITROGEN_COMPONENT_CATALOG_COLLECTION = "nitrogen-component
|
|
|
6
6
|
export declare const NITROGEN_COMPONENT_USAGE_COLLECTION = "nitrogen-component-usage";
|
|
7
7
|
type ComponentUsageRecord = {
|
|
8
8
|
componentName: string;
|
|
9
|
-
depth: number;
|
|
10
9
|
moduleId: string;
|
|
11
|
-
modulePath: string;
|
|
12
|
-
parentComponentName?: string;
|
|
13
|
-
parentModuleId?: string;
|
|
14
|
-
parentModulePath?: string;
|
|
15
10
|
};
|
|
16
11
|
type ReindexResult = {
|
|
17
12
|
catalogCount: number;
|
|
@@ -21,6 +16,8 @@ type ReindexResult = {
|
|
|
21
16
|
export declare function extractComponentUsageRecords(modules: unknown): ComponentUsageRecord[];
|
|
22
17
|
export declare function findAllDocs<T extends object>(payload: Payload, collection: string, options?: {
|
|
23
18
|
limit?: number;
|
|
19
|
+
/** e.g. 'all' to fetch every locale's value for localized fields */
|
|
20
|
+
locale?: string;
|
|
24
21
|
select?: Record<string, true>;
|
|
25
22
|
where?: Where;
|
|
26
23
|
}, req?: PayloadRequest): Promise<T[]>;
|