@nitrogenbuilder/connector-payload 1.1.1 → 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.
@@ -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
- return (_jsx("div", { "data-nitrogen-location": "page", "data-page-id": pageId, "data-collection": collection, children: _jsx(NitrogenRenderer, { page: page?.data ?? [], dynamicData: page?.dynamicData ?? initialDynamicData, requestedData: {} }) }));
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
- const dynamicData = buildDynamicData(page, settings);
35
- return (_jsx(NitrogenPageClient, { pageId: String(page.id), pageData: page.nitrogenData || [], dynamicData: dynamicData, isBuilder: isBuilder, collection: collection }));
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(NitrogenTemplates, 'nitrogen-templates'),
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
@@ -16,6 +16,8 @@ type ReindexResult = {
16
16
  export declare function extractComponentUsageRecords(modules: unknown): ComponentUsageRecord[];
17
17
  export declare function findAllDocs<T extends object>(payload: Payload, collection: string, options?: {
18
18
  limit?: number;
19
+ /** e.g. 'all' to fetch every locale's value for localized fields */
20
+ locale?: string;
19
21
  select?: Record<string, true>;
20
22
  where?: Where;
21
23
  }, req?: PayloadRequest): Promise<T[]>;
@@ -62,6 +62,7 @@ export async function findAllDocs(payload, collection, options, req) {
62
62
  collection: collection,
63
63
  depth: 0,
64
64
  limit: options?.limit ?? 200,
65
+ locale: options?.locale,
65
66
  page,
66
67
  overrideAccess: true,
67
68
  pagination: true,
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Localization support — locale threading for read endpoints, dynamic-data
3
+ * translation metadata, and per-language translation status for builder
4
+ * content.
5
+ *
6
+ * Two independent layers cooperate here:
7
+ *
8
+ * 1. **Payload-native localization** (field data). When the host project's
9
+ * Payload config has `localization` configured (either directly or injected
10
+ * from the plugin's `localization` option), eligible text fields are
11
+ * localized and read endpoints thread `?lang=` through to
12
+ * `payload.find`/`findByID` as `locale` + `fallbackLocale`.
13
+ * 2. **Nitrogen settings localization** (builder content). Site languages live
14
+ * in the `nitrogen-settings` global under `nitrogenConfig.localization`.
15
+ * Translatable prop values inside `nitrogenData` are stored language-keyed
16
+ * with the language as the OUTER key (`{ en: ..., es: ... }`); legacy bare
17
+ * values read as the default language.
18
+ *
19
+ * Everything is gated: with no localization configured anywhere, every helper
20
+ * returns null/empty and callers behave exactly as before.
21
+ */
22
+ import type { Field, Payload, PayloadRequest, SanitizedCollectionConfig } from 'payload';
23
+ import type { ComponentManifestProp, DynamicDataMeta, LanguageStatus, LocalizationSettings } from '@nitrogenbuilder/types';
24
+ import type { NitrogenSettingsGlobal } from './types.js';
25
+ export interface PayloadLocaleConfig {
26
+ locales: string[];
27
+ defaultLocale: string;
28
+ }
29
+ /**
30
+ * Reads the sanitized Payload localization config at runtime. Returns null
31
+ * when the project has no Payload-level localization configured.
32
+ */
33
+ export declare function getPayloadLocalization(payload: Payload): PayloadLocaleConfig | null;
34
+ /** Reads `?lang=` (alias `?locale=`) off a request URL. */
35
+ export declare function getRequestLanguage(req: PayloadRequest): string | null;
36
+ export interface LocaleQueryOptions {
37
+ locale?: string;
38
+ fallbackLocale?: string;
39
+ }
40
+ /**
41
+ * Turns a requested language into `locale`/`fallbackLocale` options for
42
+ * `payload.find`/`findByID`. Returns `{}` (default behavior) when no language
43
+ * was requested, the project has no Payload localization, or the language is
44
+ * not a configured locale.
45
+ */
46
+ export declare function resolveLocaleOptions(payload: Payload, lang?: string | null): LocaleQueryOptions;
47
+ export declare function getCollectionConfig(payload: Payload, collectionSlug: string): SanitizedCollectionConfig | undefined;
48
+ /**
49
+ * Dot-paths of localized text-typed fields of a collection config. Walks
50
+ * groups, rows, collapsibles, and tabs; named groups/tabs contribute a path
51
+ * segment.
52
+ */
53
+ export declare function getLocalizedTextFieldPaths(fields: Field[] | undefined, prefix?: string): string[];
54
+ /**
55
+ * The site language config from the `nitrogen-settings` global. Null when
56
+ * localization is absent or disabled — callers must treat null as
57
+ * "no localization behavior at all".
58
+ */
59
+ export declare function getLocalizationSettings(settings: NitrogenSettingsGlobal): LocalizationSettings | null;
60
+ /**
61
+ * Fetches a doc with every locale's value for localized fields (one
62
+ * `locale: 'all'` query). Returns null when the project has no Payload
63
+ * localization configured or the doc can't be read — advisory callers keep
64
+ * their default behavior in that case.
65
+ */
66
+ export declare function fetchLocaleAllDoc(payload: Payload, collectionSlug: string, docId: string | number): Promise<Record<string, unknown> | null>;
67
+ /**
68
+ * The default-locale title from a `locale: 'all'` doc, for editor-facing
69
+ * single-doc responses. The editor edits default-language field data only
70
+ * (the CMS admin owns field translations), so the `title` it displays and
71
+ * PATCHes back on save must never be a locale-resolved one. Returns null when
72
+ * there is nothing to override (no localization, title not localized, or no
73
+ * stored title) — callers then keep the response title as-is.
74
+ */
75
+ export declare function getDefaultLocaleTitle(payload: Payload, localeAllDoc: Record<string, unknown> | null): string | null;
76
+ /**
77
+ * Builds the connector-provided metadata the editor uses to badge
78
+ * untranslated CMS fields. Eligible keys are the localized text-typed fields
79
+ * of the collection config; translated keys per language are the fields whose
80
+ * locale-specific value exists (checked via one `locale: 'all'` fetch, or a
81
+ * caller-preloaded `localeAllDoc` to avoid a duplicate query).
82
+ *
83
+ * Returns null when the project has no Payload localization configured, so
84
+ * callers can attach `dynamic_data_meta` conditionally with zero change to
85
+ * the default response shape.
86
+ */
87
+ export declare function buildDynamicDataMeta(payload: Payload, collectionSlug: string, docId?: string | number, localeAllDoc?: Record<string, unknown> | null): Promise<DynamicDataMeta | null>;
88
+ type CatalogPropDef = ComponentManifestProp & {
89
+ translatable?: boolean;
90
+ };
91
+ /**
92
+ * componentName → group key → propKey → prop definition. Module prop values
93
+ * are stored group-keyed (`props[groupKey][propKey]`, mirroring the renderer's
94
+ * `spreadProps`), so the group level is preserved for lookups during the walk.
95
+ */
96
+ export type TranslatablePropDefs = Map<string, Record<string, Record<string, CatalogPropDef>>>;
97
+ /**
98
+ * Loads component prop definitions from the nitrogen-component-catalog
99
+ * collection so translatable props can be identified precisely. Returns an
100
+ * empty map when the catalog is unavailable — the walk then falls back to the
101
+ * lang-map heuristic.
102
+ */
103
+ export declare function loadTranslatablePropDefs(payload: Payload): Promise<TranslatablePropDefs>;
104
+ export interface ComputeTranslationStatusArgs {
105
+ payload: Payload;
106
+ collectionSlug: string;
107
+ docId: string | number;
108
+ nitrogenData: unknown;
109
+ localization: LocalizationSettings;
110
+ /** Preloaded catalog defs (avoids a re-fetch when batching). */
111
+ propDefs?: TranslatablePropDefs;
112
+ /** Preloaded `locale: 'all'` doc (avoids a re-fetch when batching). */
113
+ localeAllDoc?: Record<string, unknown> | null;
114
+ }
115
+ /**
116
+ * Computes per-language translation status for one page/template. Returns a
117
+ * `{ [langCode]: LanguageStatus }` map covering every configured non-default
118
+ * language, or null when there is nothing to compute.
119
+ */
120
+ export declare function computeDocTranslationStatus(args: ComputeTranslationStatusArgs): Promise<Record<string, LanguageStatus> | null>;
121
+ export {};