@lexibase/rdx-next 6.0.1 → 6.1.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/bin/cli.mjs +25 -0
- package/bin/homepage-translations.mjs +115 -12
- package/package.json +3 -3
package/bin/cli.mjs
CHANGED
|
@@ -67,6 +67,9 @@ export async function runRdxCli({ templateDir } = {}) {
|
|
|
67
67
|
console.log(
|
|
68
68
|
' rdx add i18n [--locales en,es-ES] [--package-manager npm|pnpm|yarn]'
|
|
69
69
|
)
|
|
70
|
+
console.log(
|
|
71
|
+
' rdx i18n sync: add missing phrases to homepage translation catalogs'
|
|
72
|
+
)
|
|
70
73
|
console.log(
|
|
71
74
|
'Plugin commands are registered through plugins in rdx.config.ts.'
|
|
72
75
|
)
|
|
@@ -86,6 +89,18 @@ export async function runRdxCli({ templateDir } = {}) {
|
|
|
86
89
|
if (commandArgs.length) throw new Error('Usage: rdx check')
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
const syncHomepage = command === 'i18n' && commandArgs[0] === 'sync'
|
|
93
|
+
if (
|
|
94
|
+
syncHomepage &&
|
|
95
|
+
commandArgs.length === 2 &&
|
|
96
|
+
['--help', '-h'].includes(commandArgs[1])
|
|
97
|
+
) {
|
|
98
|
+
console.log(
|
|
99
|
+
'Usage: rdx i18n sync\nAdd missing homepage phrases to the configured languages’ home.json files.'
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
if (command === 'add') {
|
|
90
105
|
if (
|
|
91
106
|
(commandArgs.length === 1 &&
|
|
@@ -145,6 +160,16 @@ export async function runRdxCli({ templateDir } = {}) {
|
|
|
145
160
|
rootDir: projectRoot,
|
|
146
161
|
command,
|
|
147
162
|
})
|
|
163
|
+
if (
|
|
164
|
+
syncHomepage &&
|
|
165
|
+
!project.commands.some((entry) => entry.name === 'i18n')
|
|
166
|
+
) {
|
|
167
|
+
if (commandArgs.length !== 1) throw new Error('Usage: rdx i18n sync')
|
|
168
|
+
const { syncHomepageTranslations } =
|
|
169
|
+
await import('./homepage-translations.mjs')
|
|
170
|
+
await syncHomepageTranslations(project)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
148
173
|
let homepageTranslations
|
|
149
174
|
if (
|
|
150
175
|
project.config.homepage?.enabled &&
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
lstat,
|
|
4
|
+
readFile,
|
|
5
|
+
realpath,
|
|
6
|
+
rename,
|
|
7
|
+
rm,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from 'node:fs/promises'
|
|
2
10
|
import path from 'node:path'
|
|
3
11
|
|
|
4
12
|
const sourceFile = 'components/home.tsx'
|
|
@@ -16,7 +24,7 @@ async function readOptional(file) {
|
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
async function sourcePhrases(rootDir) {
|
|
27
|
+
async function sourcePhrases(rootDir, report) {
|
|
20
28
|
const source = await readOptional(path.join(rootDir, sourceFile))
|
|
21
29
|
if (source === undefined)
|
|
22
30
|
throw new Error(`Create ${sourceFile} to use homepage.translations.`)
|
|
@@ -99,20 +107,118 @@ async function sourcePhrases(rootDir) {
|
|
|
99
107
|
ts.forEachChild(node, visit)
|
|
100
108
|
}
|
|
101
109
|
visit(file)
|
|
110
|
+
if (report) {
|
|
111
|
+
for (const { line, message } of warnings.sort((a, b) => a.line - b.line))
|
|
112
|
+
console.warn(`[RDX] ${sourceFile}:${line}: ${message}`)
|
|
113
|
+
}
|
|
114
|
+
phrases.delete(metadataKey)
|
|
102
115
|
return { phrases, warnings }
|
|
103
116
|
}
|
|
104
117
|
|
|
118
|
+
function parseCatalog(raw) {
|
|
119
|
+
const parsed = JSON.parse(raw)
|
|
120
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
121
|
+
throw new Error('Expected a JSON object.')
|
|
122
|
+
return parsed
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function syncHomepageTranslations(project) {
|
|
126
|
+
const { rootDir, config } = project
|
|
127
|
+
const locales = project.sources.find((source) => source.id === 'current')
|
|
128
|
+
?.localization?.locales
|
|
129
|
+
if (!locales)
|
|
130
|
+
throw new Error('Enable the i18n plugin before running rdx i18n sync.')
|
|
131
|
+
if (!config.homepage?.enabled || !config.homepage.translations)
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Set homepage.enabled and homepage.translations to true before running rdx i18n sync.'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const { phrases } = await sourcePhrases(rootDir, true)
|
|
137
|
+
if (!phrases.size) {
|
|
138
|
+
console.log(`[RDX] No literal homepage phrases found in ${sourceFile}.`)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
const root = await realpath(rootDir)
|
|
142
|
+
const updates = []
|
|
143
|
+
for (const { folder } of locales) {
|
|
144
|
+
if (!folder) continue
|
|
145
|
+
const file = `locale/${folder}/home.json`
|
|
146
|
+
const directory = await realpath(path.join(root, 'locale', folder))
|
|
147
|
+
const relative = path.relative(root, directory)
|
|
148
|
+
if (
|
|
149
|
+
!relative ||
|
|
150
|
+
relative === '..' ||
|
|
151
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
152
|
+
path.isAbsolute(relative)
|
|
153
|
+
)
|
|
154
|
+
throw new Error(`${file} must remain inside the project.`)
|
|
155
|
+
const target = path.join(directory, 'home.json')
|
|
156
|
+
const info = await lstat(target).catch((error) => {
|
|
157
|
+
if (!missingFile(error)) throw error
|
|
158
|
+
})
|
|
159
|
+
if (info && !info.isFile())
|
|
160
|
+
throw new Error(`${file} must be a regular file.`)
|
|
161
|
+
const raw = info ? await readFile(target, 'utf8') : undefined
|
|
162
|
+
let messages = {}
|
|
163
|
+
if (raw !== undefined) {
|
|
164
|
+
try {
|
|
165
|
+
messages = parseCatalog(raw)
|
|
166
|
+
} catch {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Cannot sync ${file}: expected a JSON object of homepage phrases.`
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const missing = [...phrases].filter(
|
|
173
|
+
(phrase) => !Object.hasOwn(messages, phrase)
|
|
174
|
+
)
|
|
175
|
+
if (missing.length)
|
|
176
|
+
updates.push({
|
|
177
|
+
file,
|
|
178
|
+
target,
|
|
179
|
+
info,
|
|
180
|
+
raw,
|
|
181
|
+
count: missing.length,
|
|
182
|
+
messages: Object.fromEntries([
|
|
183
|
+
...Object.entries(messages),
|
|
184
|
+
...missing.map((phrase) => [phrase, '']),
|
|
185
|
+
]),
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const { file, target, info, raw, count, messages } of updates) {
|
|
190
|
+
const content = JSON.stringify(messages, null, 2) + '\n'
|
|
191
|
+
if (raw === undefined) {
|
|
192
|
+
await writeFile(target, content, { flag: 'wx' })
|
|
193
|
+
} else {
|
|
194
|
+
const temporary = `${target}.${randomUUID()}.tmp`
|
|
195
|
+
try {
|
|
196
|
+
await writeFile(temporary, content, { flag: 'wx', mode: info.mode })
|
|
197
|
+
if (
|
|
198
|
+
!(await lstat(target)).isFile() ||
|
|
199
|
+
(await readFile(target, 'utf8')) !== raw
|
|
200
|
+
)
|
|
201
|
+
throw new Error(
|
|
202
|
+
`${file} changed during sync. Run rdx i18n sync again.`
|
|
203
|
+
)
|
|
204
|
+
await rename(temporary, target)
|
|
205
|
+
} finally {
|
|
206
|
+
await rm(temporary, { force: true })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log(
|
|
210
|
+
`[RDX] ${file}: added ${count} ${count === 1 ? 'phrase' : 'phrases'}.`
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
if (!updates.length) console.log('[RDX] Homepage catalogs are up to date.')
|
|
214
|
+
}
|
|
215
|
+
|
|
105
216
|
export async function readHomepageTranslations(
|
|
106
217
|
project,
|
|
107
218
|
{ report = false } = {}
|
|
108
219
|
) {
|
|
109
220
|
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)
|
|
221
|
+
const { phrases, warnings } = await sourcePhrases(rootDir, report)
|
|
116
222
|
const catalogs = []
|
|
117
223
|
for (const locale of localization?.locales ?? []) {
|
|
118
224
|
if (!locale.folder) continue
|
|
@@ -121,10 +227,7 @@ export async function readHomepageTranslations(
|
|
|
121
227
|
let messages = {}
|
|
122
228
|
if (raw !== undefined) {
|
|
123
229
|
try {
|
|
124
|
-
|
|
125
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
126
|
-
throw new Error('Expected a JSON object.')
|
|
127
|
-
messages = parsed
|
|
230
|
+
messages = parseCatalog(raw)
|
|
128
231
|
catalogs.push({ file, locale: locale.path })
|
|
129
232
|
} catch {
|
|
130
233
|
console.warn(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexibase/rdx-next",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "The Next.js runtime for RDX documentation projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
"react": "19.3.1",
|
|
83
83
|
"react-dom": "19.3.1",
|
|
84
84
|
"@rdx/eslint-config": "1.0.0",
|
|
85
|
-
"@rdx
|
|
86
|
-
"@
|
|
85
|
+
"@lexibase/rdx-ui": "3.5.6",
|
|
86
|
+
"@rdx/typescript-config": "1.0.0"
|
|
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",
|