@lexibase/rdx-next 5.2.0 → 6.0.1

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/bin/cli.mjs CHANGED
@@ -26,7 +26,7 @@ export async function runRdxCli({ templateDir } = {}) {
26
26
  templateDir ?? path.join(packageRoot, 'templates', 'runtime')
27
27
  const runtimeDirectory = path.join(projectRoot, '.rdx')
28
28
 
29
- async function prepareRuntime(project) {
29
+ async function prepareRuntime(project, homepageTranslations) {
30
30
  await mkdir(runtimeDirectory, { recursive: true })
31
31
  await preparePublicAssets(projectRoot, runtimeDirectory)
32
32
  await cp(runtimeTemplate, runtimeDirectory, {
@@ -35,6 +35,11 @@ export async function runRdxCli({ templateDir } = {}) {
35
35
  })
36
36
  await prepareProjectStyles(projectRoot, runtimeDirectory)
37
37
  await prepareLocaleRoutes(project, runtimeDirectory, runtimeTemplate)
38
+ if (homepageTranslations) {
39
+ const { prepareHomepageTranslations } =
40
+ await import('./homepage-translations.mjs')
41
+ await prepareHomepageTranslations(runtimeDirectory, homepageTranslations)
42
+ }
38
43
  // Resolve in Node before Next processes this file: UI may be transitive,
39
44
  // and native Tailwind plugins must stay outside the Turbopack module graph.
40
45
  const resolvePlugin = createRequire(import.meta.url)
@@ -140,6 +145,20 @@ export async function runRdxCli({ templateDir } = {}) {
140
145
  rootDir: projectRoot,
141
146
  command,
142
147
  })
148
+ let homepageTranslations
149
+ if (
150
+ project.config.homepage?.enabled &&
151
+ project.config.homepage.translations &&
152
+ (supportedCommands.has(command) ||
153
+ command === 'check' ||
154
+ command === 'prepare')
155
+ ) {
156
+ const { readHomepageTranslations } =
157
+ await import('./homepage-translations.mjs')
158
+ homepageTranslations = await readHomepageTranslations(project, {
159
+ report: command === 'check' || command === 'build',
160
+ })
161
+ }
143
162
  if (command === 'check' || command === 'build') {
144
163
  const { checkDocumentation, formatDocumentationCheck } =
145
164
  await import('@lexibase/rdx-core/check')
@@ -153,7 +172,7 @@ export async function runRdxCli({ templateDir } = {}) {
153
172
  if (command === 'check') return
154
173
  }
155
174
  if (command === 'prepare') {
156
- await prepareRuntime(project)
175
+ await prepareRuntime(project, homepageTranslations)
157
176
  console.log('RDX runtime prepared in .rdx/')
158
177
  return
159
178
  }
@@ -174,7 +193,7 @@ export async function runRdxCli({ templateDir } = {}) {
174
193
  return
175
194
  }
176
195
 
177
- await prepareRuntime(project)
196
+ await prepareRuntime(project, homepageTranslations)
178
197
  const projectRequire = createRequire(path.join(projectRoot, 'package.json'))
179
198
  const nextCli = projectRequire.resolve('next/dist/bin/next')
180
199
  const child = spawn(
@@ -0,0 +1,197 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ const sourceFile = 'components/home.tsx'
5
+ const metadataKey = '$metadata'
6
+
7
+ function missingFile(error) {
8
+ return error.code === 'ENOENT' || error.code === 'ENOTDIR'
9
+ }
10
+
11
+ async function readOptional(file) {
12
+ try {
13
+ return await readFile(file, 'utf8')
14
+ } catch (error) {
15
+ if (!missingFile(error)) throw error
16
+ }
17
+ }
18
+
19
+ async function sourcePhrases(rootDir) {
20
+ const source = await readOptional(path.join(rootDir, sourceFile))
21
+ if (source === undefined)
22
+ throw new Error(`Create ${sourceFile} to use homepage.translations.`)
23
+ const { default: ts } = await import('typescript')
24
+ const file = ts.createSourceFile(
25
+ sourceFile,
26
+ source,
27
+ ts.ScriptTarget.Latest,
28
+ true
29
+ )
30
+ for (const statement of file.statements) {
31
+ if (
32
+ !ts.isExpressionStatement(statement) ||
33
+ !ts.isStringLiteral(statement.expression)
34
+ )
35
+ break
36
+ if (statement.expression.text === 'use client')
37
+ throw new Error(
38
+ `${sourceFile} must be a Server Component when homepage.translations is enabled. Move 'use client' into interactive child components.`
39
+ )
40
+ }
41
+ const phrases = new Set()
42
+ const warnings = []
43
+ const passedOn =
44
+ 't is passed on or renamed; phrases translated elsewhere are left out of the translation report'
45
+ const needsLiteral =
46
+ 't() needs a literal phrase; this call is left out of the translation report'
47
+ function warn(node, message) {
48
+ const { line } = file.getLineAndCharacterOfPosition(node.getStart(file))
49
+ warnings.push({ line: line + 1, message })
50
+ }
51
+
52
+ // Bind identifiers within this file so an unrelated or shadowed t is ignored.
53
+ const options = { noLib: true, noResolve: true, types: [] }
54
+ const program = ts.createProgram([sourceFile], options, {
55
+ ...ts.createCompilerHost(options),
56
+ getSourceFile: (name) => (name === sourceFile ? file : undefined),
57
+ })
58
+ const checker = program.getTypeChecker()
59
+ const bindings = new Set()
60
+ function findBindings(node) {
61
+ if (
62
+ ts.isBindingElement(node) &&
63
+ ts.isObjectBindingPattern(node.parent) &&
64
+ ts.isIdentifier(node.name)
65
+ ) {
66
+ const key = node.propertyName ?? node.name
67
+ if (ts.isStringLiteralLike(key) || ts.isIdentifier(key)) {
68
+ if (key.text === 't') {
69
+ if (node.name.text === 't')
70
+ bindings.add(checker.getSymbolAtLocation(node.name))
71
+ else warn(node, passedOn)
72
+ }
73
+ }
74
+ }
75
+ ts.forEachChild(node, findBindings)
76
+ }
77
+ findBindings(file)
78
+
79
+ function visit(node) {
80
+ if (ts.isIdentifier(node) && node.text === 't') {
81
+ const parent = node.parent
82
+ const symbol = ts.isShorthandPropertyAssignment(parent)
83
+ ? checker.getShorthandAssignmentValueSymbol(parent)
84
+ : checker.getSymbolAtLocation(node)
85
+ if (
86
+ symbol &&
87
+ bindings.has(symbol) &&
88
+ !ts.isBindingElement(parent) &&
89
+ !ts.isTypeQueryNode(parent)
90
+ ) {
91
+ if (ts.isCallExpression(parent) && parent.expression === node) {
92
+ const [argument] = parent.arguments
93
+ if (parent.arguments.length === 1 && ts.isStringLiteralLike(argument))
94
+ phrases.add(argument.text)
95
+ else warn(node, needsLiteral)
96
+ } else warn(node, passedOn)
97
+ }
98
+ }
99
+ ts.forEachChild(node, visit)
100
+ }
101
+ visit(file)
102
+ return { phrases, warnings }
103
+ }
104
+
105
+ export async function readHomepageTranslations(
106
+ project,
107
+ { report = false } = {}
108
+ ) {
109
+ const { rootDir, localization } = project
110
+ const { phrases, warnings } = await sourcePhrases(rootDir)
111
+ if (report) {
112
+ for (const { line, message } of warnings.sort((a, b) => a.line - b.line))
113
+ console.warn(`[RDX] ${sourceFile}:${line}: ${message}`)
114
+ }
115
+ phrases.delete(metadataKey)
116
+ const catalogs = []
117
+ for (const locale of localization?.locales ?? []) {
118
+ if (!locale.folder) continue
119
+ const file = `locale/${locale.folder}/home.json`
120
+ const raw = await readOptional(path.join(rootDir, file))
121
+ let messages = {}
122
+ if (raw !== undefined) {
123
+ try {
124
+ const parsed = JSON.parse(raw)
125
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
126
+ throw new Error('Expected a JSON object.')
127
+ messages = parsed
128
+ catalogs.push({ file, locale: locale.path })
129
+ } catch {
130
+ console.warn(
131
+ `[RDX] ${file}: expected a JSON object of homepage phrases. Using the primary text.`
132
+ )
133
+ }
134
+ }
135
+ if (!report) continue
136
+ for (const phrase of phrases) {
137
+ if (
138
+ !Object.hasOwn(messages, phrase) ||
139
+ typeof messages[phrase] !== 'string' ||
140
+ !messages[phrase].trim()
141
+ )
142
+ console.warn(
143
+ `[RDX] ${file}: missing translation for ${JSON.stringify(phrase)}`
144
+ )
145
+ }
146
+ // An unrecognized call may use any of the remaining keys.
147
+ if (!warnings.length) {
148
+ for (const phrase of Object.keys(messages)) {
149
+ if (phrase !== metadataKey && !phrases.has(phrase))
150
+ console.warn(
151
+ `[RDX] ${file}: orphaned translation for ${JSON.stringify(phrase)}`
152
+ )
153
+ }
154
+ }
155
+ }
156
+ return { catalogs, phrases }
157
+ }
158
+
159
+ export async function prepareHomepageTranslations(
160
+ runtime,
161
+ { catalogs, phrases }
162
+ ) {
163
+ const imports = catalogs.map(
164
+ ({ file }, index) =>
165
+ `import messages${index} from ${JSON.stringify(`../${file}`)}`
166
+ )
167
+ const entries = catalogs.map(
168
+ ({ locale }, index) => ` [${JSON.stringify(locale)}, messages${index}],`
169
+ )
170
+ await writeFile(
171
+ path.join(runtime, 'rdx-home.ts'),
172
+ `import translatedComponent from '../components/home'
173
+ ${imports.join('\n')}
174
+
175
+ const catalogs = new Map<string, Record<string, unknown>>([
176
+ ${entries.join('\n')}
177
+ ])
178
+
179
+ const phrases: string[] = ${JSON.stringify([...phrases])}
180
+
181
+ export function getHomepageTranslation(locale?: string) {
182
+ return { translatedComponent, messages: locale ? catalogs.get(locale) : undefined }
183
+ }
184
+
185
+ export function getHomepageSeo(locale?: string) {
186
+ const translatedLocales = new Set<string>()
187
+ for (const [path, catalog] of catalogs) {
188
+ if (phrases.some((phrase) => {
189
+ const text = Object.hasOwn(catalog, phrase) ? catalog[phrase] : undefined
190
+ return typeof text === 'string' && text.trim() && text.trim() !== phrase.trim()
191
+ })) translatedLocales.add(path)
192
+ }
193
+ return { translatedLocales, metadata: locale ? catalogs.get(locale)?.${metadataKey} : undefined }
194
+ }
195
+ `
196
+ )
197
+ }
@@ -84,7 +84,7 @@ export async function prepareLocaleRoutes(project, runtime, template) {
84
84
  await emit(
85
85
  `app/(rdx-default)/${file}`,
86
86
  text
87
- .replace(/from '(\.\.\/[^']*rdx-project)'/g, "from '../$1'")
87
+ .replace(/from '(\.\.\/[^']*rdx-(?:home|project))'/g, "from '../$1'")
88
88
  .replace("'./globals.css'", "'../globals.css'")
89
89
  )
90
90
  }
@@ -110,49 +110,16 @@ export default async function Layout({ children, params }: { children: React.Rea
110
110
  }
111
111
  `
112
112
  )
113
- const homeRoute = 'app/(rdx-locales)/[locale]/page.tsx'
114
- const localizedHomes = []
115
- if (project.config.homepage?.enabled) {
116
- for (const locale of project.localization.locales) {
117
- if (!locale.folder || locale.code === project.localization.defaultLocale)
118
- continue
119
-
120
- const file = path.join(
121
- project.rootDir,
122
- 'locale',
123
- locale.folder,
124
- 'home.tsx'
125
- )
126
- const exists = await stat(file).then(
127
- (entry) => entry.isFile(),
128
- (error) => {
129
- if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false
130
- throw error
131
- }
132
- )
133
- if (!exists) continue
134
-
135
- // Literal imports let Next compile and trace project-owned components.
136
- const specifier = path
137
- .relative(path.dirname(path.join(runtime, homeRoute)), file)
138
- .split(path.sep)
139
- .join('/')
140
- .slice(0, -'.tsx'.length)
141
- localizedHomes.push(
142
- ` case ${JSON.stringify(locale.path)}: return (await import(${JSON.stringify(specifier)})).default`
143
- )
144
- }
145
- }
146
113
  await emit(
147
- homeRoute,
148
- `import { HomePage, getLocalizedProject } from '${provider}'
114
+ 'app/(rdx-locales)/[locale]/page.tsx',
115
+ `import { HomePage, getHomepageMetadata, getLocalizedProject } from '${provider}'
149
116
  import { notFound, permanentRedirect } from 'next/navigation'
117
+ import { getHomepageSeo, getHomepageTranslation } from '../../../rdx-home'
150
118
  import { getProject } from '../../../rdx-project'
151
- async function getLocalizedHome(locale: string) {
152
- switch (locale) {
153
- ${localizedHomes.join('\n')}
154
- default: return undefined
155
- }
119
+ export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
120
+ const project = await getProject()
121
+ const { locale } = await params
122
+ return getHomepageMetadata({ project, locale, ...getHomepageSeo(locale) })
156
123
  }
157
124
  export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
158
125
  const project = await getProject()
@@ -160,8 +127,7 @@ export default async function Page({ params }: { params: Promise<{ locale: strin
160
127
  const selected = project.localization?.locales.find(entry => entry.path === locale)
161
128
  if (!project.config.homepage?.enabled || !selected) notFound()
162
129
  if (selected.code === project.localization!.defaultLocale) permanentRedirect('/')
163
- const component = await getLocalizedHome(locale)
164
- return <HomePage project={getLocalizedProject(project, locale)} component={component} />
130
+ return <HomePage project={getLocalizedProject(project, locale)} {...getHomepageTranslation(locale)} />
165
131
  }
166
132
  `
167
133
  )
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { defineConfig } from '@lexibase/rdx-core/config';
2
- export { RDXConfig, RdxHomepage, RdxHomepageProps, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
2
+ export { RDXConfig, RdxHomepage, RdxHomepageProps, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig, RdxTranslatedHomepageProps } from '@lexibase/rdx-types';
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { RdxProject, RDXConfig, RdxContentSource, RdxDocPage } from '@lexibase/rdx-types';
2
+ import { ComponentType, ReactNode } from 'react';
3
+ import { RdxProject, RDXConfig, RdxContentSource, RdxDocPage, RdxTranslatedHomepageProps } from '@lexibase/rdx-types';
4
4
  export { createRdxProject, getLocalizedProject, getProjectDocsPaths } from '@lexibase/rdx-core';
5
5
  import { MetadataRoute, Metadata } from 'next';
6
6
 
@@ -32,6 +32,12 @@ declare function getMarkdownResponse(project: RdxProject, path: string[]): Respo
32
32
  declare function markdownNotFound(): Response;
33
33
  declare function getDocumentMarkdownResponse(project: RdxProject, source: RdxContentSource | undefined, slug: string, documentPath?: DocumentPath): Response;
34
34
 
35
+ declare function getHomepageMetadata({ project, locale, translatedLocales, metadata, }: {
36
+ project: RdxProject;
37
+ locale?: string;
38
+ translatedLocales?: ReadonlySet<string>;
39
+ metadata?: unknown;
40
+ }): Metadata;
35
41
  /** Read metadata from the same content source and index used by the page. */
36
42
  declare function getDocumentMetadata({ project, slug, }: {
37
43
  project: RdxProject;
@@ -47,9 +53,10 @@ declare function RdxLayout({ children, config, bodyClassName, }: {
47
53
  config: RDXConfig;
48
54
  bodyClassName?: string;
49
55
  }): react.JSX.Element;
50
- declare function HomePage({ project, component, }: {
56
+ declare function HomePage({ project, translatedComponent: TranslatedHome, messages, }: {
51
57
  project: RdxProject;
52
- component?: NonNullable<RDXConfig['homepage']>['component'];
58
+ translatedComponent?: ComponentType<RdxTranslatedHomepageProps>;
59
+ messages?: Record<string, unknown>;
53
60
  }): react.JSX.Element;
54
61
  declare function NotFoundPage({ project }: {
55
62
  project: RdxProject;
@@ -59,4 +66,4 @@ declare function DocsPage({ slug, project, }: {
59
66
  project: RdxProject;
60
67
  }): Promise<react.JSX.Element>;
61
68
 
62
- export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
69
+ export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getHomepageMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
@@ -318,6 +318,67 @@ import {
318
318
  getSourceOptions as getSourceOptions4
319
319
  } from "@lexibase/rdx-core";
320
320
  import { notFound } from "next/navigation";
321
+ function getHomepageMetadata({
322
+ project,
323
+ locale,
324
+ translatedLocales,
325
+ metadata
326
+ }) {
327
+ const { config, localization } = project;
328
+ if (!config.homepage?.enabled || !localization) return {};
329
+ const selected = localization.locales.find((entry) => entry.path === locale);
330
+ if (locale && (!selected || selected.code === localization.defaultLocale))
331
+ return {};
332
+ const base = config.metadata.metadataBase;
333
+ const primaryUrl = base ? new URL("/", base).href : void 0;
334
+ if (locale && !translatedLocales?.has(locale)) {
335
+ if (config.homepage.component) return {};
336
+ return {
337
+ robots: { index: false, follow: true },
338
+ ...primaryUrl ? { alternates: { canonical: primaryUrl } } : {}
339
+ };
340
+ }
341
+ const url = base && new URL(locale ? `/${locale}` : "/", base).href;
342
+ const site = getRdxMetadata(config);
343
+ const fields = metadata && typeof metadata === "object" ? metadata : {};
344
+ const title = "title" in fields && typeof fields.title === "string" ? fields.title.trim() : "";
345
+ const description = "description" in fields && typeof fields.description === "string" ? fields.description.trim() : "";
346
+ const defaults = {
347
+ title: config.metadata.applicationName,
348
+ description: config.description
349
+ };
350
+ const social = {
351
+ ...title ? { title: { absolute: title } } : {},
352
+ ...description ? { description } : {}
353
+ };
354
+ return {
355
+ ...social,
356
+ ...site.openGraph || title || description ? {
357
+ openGraph: {
358
+ ...defaults,
359
+ ...site.openGraph,
360
+ ...social,
361
+ ...url ? { url } : {}
362
+ }
363
+ } : {},
364
+ ...site.twitter || title || description ? { twitter: { ...defaults, ...site.twitter, ...social } } : {},
365
+ ...base && url && primaryUrl ? {
366
+ alternates: {
367
+ canonical: url,
368
+ languages: {
369
+ [localization.defaultLocale]: primaryUrl,
370
+ "x-default": primaryUrl,
371
+ ...Object.fromEntries(
372
+ localization.locales.filter((entry) => translatedLocales?.has(entry.path)).map((entry) => [
373
+ entry.code,
374
+ new URL(`/${entry.path}`, base).href
375
+ ])
376
+ )
377
+ }
378
+ }
379
+ } : {}
380
+ };
381
+ }
321
382
  function getDocumentMetadata({
322
383
  project,
323
384
  slug
@@ -488,16 +549,17 @@ function RdxHeader({
488
549
  }
489
550
  function HomePage({
490
551
  project,
491
- component
552
+ translatedComponent: TranslatedHome,
553
+ messages
492
554
  }) {
493
555
  const { config } = project;
494
556
  const docsHref = getDocsHref4(project);
495
557
  if (!config.homepage?.enabled) redirect(docsHref);
496
558
  const homepage = config.homepage;
497
- const CustomHome = component ?? homepage.component;
559
+ const CustomHome = homepage.component;
498
560
  const docsLabel = getMessages(config.lang).docs;
499
561
  const source = project.sources[0];
500
- const categories = CustomHome ? [] : getDocsIndex4(getSourceOptions5(project, source));
562
+ const categories = CustomHome || TranslatedHome ? [] : getDocsIndex4(getSourceOptions5(project, source));
501
563
  const firstPage = categories[0]?.pages[0];
502
564
  return /* @__PURE__ */ jsxs(Fragment, { children: [
503
565
  /* @__PURE__ */ jsx(RdxHeader, { project, onHomepage: true }),
@@ -507,7 +569,17 @@ function HomePage({
507
569
  id: "rdx-main",
508
570
  tabIndex: -1,
509
571
  className: "relative flex-1 min-h-0 overflow-y-auto",
510
- children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, { lang: config.lang, docsHref }) : /* @__PURE__ */ jsx(
572
+ children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, { lang: config.lang, docsHref }) : TranslatedHome ? /* @__PURE__ */ jsx(
573
+ TranslatedHome,
574
+ {
575
+ lang: config.lang,
576
+ docsHref,
577
+ t: (text2) => {
578
+ const translation = messages && Object.hasOwn(messages, text2) ? messages[text2] : void 0;
579
+ return typeof translation === "string" && translation.trim() ? translation : text2;
580
+ }
581
+ }
582
+ ) : /* @__PURE__ */ jsx(
511
583
  Home,
512
584
  {
513
585
  title: homepage.title ?? config.metadata.applicationName,
@@ -810,6 +882,7 @@ export {
810
882
  createRdxProject,
811
883
  getDocumentMarkdownResponse,
812
884
  getDocumentMetadata,
885
+ getHomepageMetadata,
813
886
  getLlmIndexResponse,
814
887
  getLocalizedProject4 as getLocalizedProject,
815
888
  getMarkdownHref,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "5.2.0",
3
+ "version": "6.0.1",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,8 +52,8 @@
52
52
  "next-mdx-remote": "^6.0.0",
53
53
  "remark-gfm": "^4",
54
54
  "typescript": "6.0.3",
55
- "@lexibase/rdx-core": "3.1.1",
56
- "@lexibase/rdx-types": "2.5.0"
55
+ "@lexibase/rdx-core": "3.2.0",
56
+ "@lexibase/rdx-types": "2.6.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@lexibase/rdx-ui": "^3.5.0",
@@ -81,9 +81,9 @@
81
81
  "next": "^16.4.2",
82
82
  "react": "19.3.1",
83
83
  "react-dom": "19.3.1",
84
- "@lexibase/rdx-ui": "3.5.4",
85
84
  "@rdx/eslint-config": "1.0.0",
86
- "@rdx/typescript-config": "1.0.0"
85
+ "@rdx/typescript-config": "1.0.0",
86
+ "@lexibase/rdx-ui": "3.5.5"
87
87
  },
88
88
  "scripts": {
89
89
  "dev": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --watch --external @lexibase/rdx-core --external @lexibase/rdx-types --external @lexibase/rdx-ui --external next --external react --external react-dom --external next-mdx-remote",
@@ -5,4 +5,3 @@
5
5
  @source '../../components/**/*.{ts,tsx}';
6
6
  @source '../../docs/**/*.mdx';
7
7
  @source '../../locale/*/docs/**/*.mdx';
8
- @source '../../locale/*/home.tsx';
@@ -1,7 +1,15 @@
1
- import { HomePage } from '@lexibase/rdx-next/runtime'
1
+ import { getHomepageMetadata, HomePage } from '@lexibase/rdx-next/runtime'
2
2
 
3
+ import { getHomepageSeo, getHomepageTranslation } from '../rdx-home'
3
4
  import { getProject } from '../rdx-project'
4
5
 
6
+ export async function generateMetadata() {
7
+ return getHomepageMetadata({
8
+ project: await getProject(),
9
+ ...getHomepageSeo(),
10
+ })
11
+ }
12
+
5
13
  export default async function Home() {
6
- return <HomePage project={await getProject()} />
14
+ return <HomePage project={await getProject()} {...getHomepageTranslation()} />
7
15
  }
@@ -0,0 +1,2 @@
1
+ export const getHomepageTranslation: (locale?: string) => object = () => ({})
2
+ export const getHomepageSeo: (locale?: string) => object = () => ({})