docs-i18n 0.11.1 → 0.12.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.
@@ -11,7 +11,7 @@ function isCfWorkers() {
11
11
  async function initAdmin(config) {
12
12
  _config = config;
13
13
  if (!isCfWorkers()) {
14
- const { initStatus } = await import("./status-B1AGLvHn.js");
14
+ const { initStatus } = await import("./status-BYF_y9fT.js");
15
15
  const { setConfig } = await import("./job-manager-D9Ab9hgu.js");
16
16
  initStatus(config);
17
17
  setConfig(config);
@@ -15,7 +15,7 @@ var fetchJobs_createServerFn_handler = createServerRpc({
15
15
  }, (opts) => fetchJobs.__executeServer(opts));
16
16
  var fetchJobs = createServerFn({ method: "GET" }).handler(fetchJobs_createServerFn_handler, async () => {
17
17
  if (isCfWorkers()) return [];
18
- const { ensureInit } = await import("./init-CJJUsPDL.js");
18
+ const { ensureInit } = await import("./init-SZW7wW3l.js");
19
19
  await ensureInit();
20
20
  return (await getJobManager()).list().map((j) => ({
21
21
  ...j,
@@ -29,7 +29,7 @@ var createJob_createServerFn_handler = createServerRpc({
29
29
  }, (opts) => createJob.__executeServer(opts));
30
30
  var createJob = createServerFn({ method: "POST" }).inputValidator((d) => d).handler(createJob_createServerFn_handler, async ({ data }) => {
31
31
  if (isCfWorkers()) throw new Error("Job creation is not available on CF Workers");
32
- const { ensureInit } = await import("./init-CJJUsPDL.js");
32
+ const { ensureInit } = await import("./init-SZW7wW3l.js");
33
33
  await ensureInit();
34
34
  if (!data.lang || !data.version) throw new Error("Missing lang or version");
35
35
  return (await getJobManager()).start(data);
@@ -11,7 +11,7 @@ var fetchVersion_createServerFn_handler = createServerRpc({
11
11
  }, (opts) => fetchVersion.__executeServer(opts));
12
12
  var fetchVersion = createServerFn({ method: "GET" }).handler(fetchVersion_createServerFn_handler, async () => {
13
13
  if (isCfWorkers()) return { version: "cf-workers" };
14
- const { getPackageVersion } = await import("./dist-DBv71kqn.js").then((n) => n.n);
14
+ const { getPackageVersion } = await import("./dist-BnKtYOU0.js").then((n) => n.n);
15
15
  return { version: getPackageVersion() };
16
16
  });
17
17
  var fetchLlmConfig_createServerFn_handler = createServerRpc({
@@ -25,14 +25,14 @@ var fetchLlmConfig = createServerFn({ method: "GET" }).handler(fetchLlmConfig_cr
25
25
  provider: null,
26
26
  hasApiKey: false
27
27
  };
28
- const { ensureInit, getConfig } = await import("./init-CJJUsPDL.js");
28
+ const { ensureInit, getConfig } = await import("./init-SZW7wW3l.js");
29
29
  await ensureInit();
30
30
  const llm = getConfig()?.llm;
31
31
  let envVars = {};
32
32
  try {
33
33
  const { readFileSync, existsSync } = await import("node:fs");
34
34
  const { resolve } = await import("node:path");
35
- const { getProjectRoot } = await import("./init-CJJUsPDL.js");
35
+ const { getProjectRoot } = await import("./init-SZW7wW3l.js");
36
36
  const envPath = resolve(getProjectRoot(), ".env");
37
37
  if (existsSync(envPath)) {
38
38
  const envContent = readFileSync(envPath, "utf-8");
@@ -1,7 +1,7 @@
1
1
  import { i as __toESM } from "./chunk-CNvmzFzq.js";
2
2
  import { l as require_react, r as require_jsx_runtime, t as useRouter } from "./useRouter-BXJvr8to.js";
3
- import { N as escapeHtml, j as deepEqual, r as useStore, s as useHydrated } from "./__tanstack-start-server-fn-resolver-N1N5mZVv.js";
4
- import { A as skipToken, C as noop, D as resolveStaleTime, F as lazyRouteComponent, I as createFileRoute, L as createLazyFileRoute, N as focusManager, P as Subscribable, R as createRootRouteWithContext, S as matchQuery, _ as functionalUpdate, a as Mutation, g as ensureQueryFn, h as addToStart, l as onlineManager, m as addToEnd, p as addConsumeAwareSignal, r as QueryClientProvider, s as Query, t as Route$2, u as notifyManager, v as hashKey, w as partialMatchKey, x as matchMutation, y as hashQueryKeyByOptions } from "./routes-DtWFPDUc.js";
3
+ import { N as escapeHtml, j as deepEqual, r as useStore, s as useHydrated } from "./__tanstack-start-server-fn-resolver-D1Ni2lpa.js";
4
+ import { A as skipToken, C as noop, D as resolveStaleTime, F as lazyRouteComponent, I as createFileRoute, L as createLazyFileRoute, N as focusManager, P as Subscribable, R as createRootRouteWithContext, S as matchQuery, _ as functionalUpdate, a as Mutation, g as ensureQueryFn, h as addToStart, l as onlineManager, m as addToEnd, p as addConsumeAwareSignal, r as QueryClientProvider, s as Query, t as Route$2, u as notifyManager, v as hashKey, w as partialMatchKey, x as matchMutation, y as hashQueryKeyByOptions } from "./routes-CLdSUfb7.js";
5
5
  import { c as getAssetCrossOrigin, d as createNonReactiveMutableStore, f as createNonReactiveReadonlyStore, i as Outlet, l as resolveManifestAssetLink, u as RouterCore } from "../server.js";
6
6
  //#endregion
7
7
  //#region ../../node_modules/.bun/@tanstack+react-router@1.168.3+bf16f8eded5e12ee/node_modules/@tanstack/react-router/dist/esm/routerStores.js
@@ -1,6 +1,6 @@
1
1
  import { i as __toESM } from "./chunk-CNvmzFzq.js";
2
2
  import { l as require_react, r as require_jsx_runtime } from "./useRouter-BXJvr8to.js";
3
- import { C as noop, D as resolveStaleTime, E as resolveEnabled, M as timeoutManager, N as focusManager, O as shallowEqualObjects, P as Subscribable, T as replaceData, b as isValidTimeout, c as fetchState, d as pendingThenable, f as environmentManager, i as useQueryClient, j as timeUntilStale, k as shouldThrowError, n as createSsrRpc, o as getDefaultState, t as Route, u as notifyManager, v as hashKey } from "./routes-DtWFPDUc.js";
3
+ import { C as noop, D as resolveStaleTime, E as resolveEnabled, M as timeoutManager, N as focusManager, O as shallowEqualObjects, P as Subscribable, T as replaceData, b as isValidTimeout, c as fetchState, d as pendingThenable, f as environmentManager, i as useQueryClient, j as timeUntilStale, k as shouldThrowError, n as createSsrRpc, o as getDefaultState, t as Route, u as notifyManager, v as hashKey } from "./routes-CLdSUfb7.js";
4
4
  import { t as useNavigate } from "./useNavigate-0H08s_Q2.js";
5
5
  import { t as createServerFn } from "../server.js";
6
6
  import { t as authClient } from "./auth-client-CoUpyQIM.js";
@@ -1,6 +1,6 @@
1
1
  import { i as __toESM } from "./chunk-CNvmzFzq.js";
2
2
  import { a as useForwardedRef, i as reactUse, l as require_react, o as useIntersectionObserver, r as require_jsx_runtime, t as useRouter } from "./useRouter-BXJvr8to.js";
3
- import { B as replaceEqualDeep$1, E as invariant, F as isDangerousProtocol, I as isModuleNotFoundError, P as functionalUpdate$1, a as matchContext, c as rootRouteId, f as exactPathTest, h as removeTrailingSlash, i as dummyMatchContext, j as deepEqual, m as joinPaths, n as require_react_dom, r as useStore, s as useHydrated, t as getServerFnById, v as trimPathLeft, y as trimPathRight } from "./__tanstack-start-server-fn-resolver-N1N5mZVv.js";
3
+ import { B as replaceEqualDeep$1, E as invariant, F as isDangerousProtocol, I as isModuleNotFoundError, P as functionalUpdate$1, a as matchContext, c as rootRouteId, f as exactPathTest, h as removeTrailingSlash, i as dummyMatchContext, j as deepEqual, m as joinPaths, n as require_react_dom, r as useStore, s as useHydrated, t as getServerFnById, v as trimPathLeft, y as trimPathRight } from "./__tanstack-start-server-fn-resolver-D1Ni2lpa.js";
4
4
  import { t as useNavigate } from "./useNavigate-0H08s_Q2.js";
5
5
  import { n as TSS_SERVER_FUNCTION, p as redirect, t as createServerFn } from "../server.js";
6
6
  //#region ../../node_modules/.bun/@tanstack+router-core@1.168.3/node_modules/@tanstack/router-core/dist/esm/link.js
@@ -2113,7 +2113,7 @@ var getSession = createServerFn({ method: "GET" }).handler(createSsrRpc("539c27f
2113
2113
  */
2114
2114
  //#endregion
2115
2115
  //#region app/routes/index.tsx
2116
- var $$splitComponentImporter = () => import("./routes-CxGhew8Z.js");
2116
+ var $$splitComponentImporter = () => import("./routes-BG89QZ0_.js");
2117
2117
  /**
2118
2118
  * Parse version keys into project/version structure.
2119
2119
  * Multi-project keys look like "query/v5", single-project keys look like "v5".
@@ -1,4 +1,4 @@
1
- import { i as parseMdx, r as flattenSources, t as TranslationCache } from "./dist-DBv71kqn.js";
1
+ import { i as parseMdx, r as flattenSources, t as TranslationCache } from "./dist-BnKtYOU0.js";
2
2
  import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  //#region server/services/status.ts
@@ -1,7 +1,7 @@
1
1
  import { t as createServerFn } from "../server.js";
2
2
  import { t as createServerRpc } from "./createServerRpc-Cyyxq9HQ.js";
3
- import { getCache, getFileBlocks, getFileCoverage, getLangs, getOverview, getVersions, rescan } from "./status-B1AGLvHn.js";
4
- import { ensureInit } from "./init-CJJUsPDL.js";
3
+ import { getCache, getFileBlocks, getFileCoverage, getLangs, getOverview, getVersions, rescan } from "./status-BYF_y9fT.js";
4
+ import { ensureInit } from "./init-SZW7wW3l.js";
5
5
  //#region server/services/d1-status.ts
6
6
  /**
7
7
  * D1-backed status service for CF Workers deployment.
@@ -1,6 +1,6 @@
1
1
  import { i as __toESM, r as __require, t as __commonJSMin } from "./assets/chunk-CNvmzFzq.js";
2
2
  import { l as require_react, n as routerContext, r as require_jsx_runtime, t as useRouter } from "./assets/useRouter-BXJvr8to.js";
3
- import { A as decodePath, B as replaceEqualDeep, C as processRouteMasks, D as DEFAULT_PROTOCOL_ALLOWLIST, E as invariant, F as isDangerousProtocol, L as isPromise, M as encodePathLikeUrl, N as escapeHtml, O as arraysEqual, P as functionalUpdate, R as last, S as findSingleMatch, T as createLRUCache, _ as trimPath, a as matchContext, b as findFlatMatch, c as rootRouteId, d as compileDecodeCharMap, g as resolvePath, j as deepEqual, k as createControlledPromise, l as isNotFound, m as joinPaths, n as require_react_dom, o as ClientOnly, p as interpolatePath, r as useStore, t as getServerFnById, u as cleanPath, w as processRouteTree, x as findRouteMatch, y as trimPathRight, z as nullReplaceEqualDeep } from "./assets/__tanstack-start-server-fn-resolver-N1N5mZVv.js";
3
+ import { A as decodePath, B as replaceEqualDeep, C as processRouteMasks, D as DEFAULT_PROTOCOL_ALLOWLIST, E as invariant, F as isDangerousProtocol, L as isPromise, M as encodePathLikeUrl, N as escapeHtml, O as arraysEqual, P as functionalUpdate, R as last, S as findSingleMatch, T as createLRUCache, _ as trimPath, a as matchContext, b as findFlatMatch, c as rootRouteId, d as compileDecodeCharMap, g as resolvePath, j as deepEqual, k as createControlledPromise, l as isNotFound, m as joinPaths, n as require_react_dom, o as ClientOnly, p as interpolatePath, r as useStore, t as getServerFnById, u as cleanPath, w as processRouteTree, x as findRouteMatch, y as trimPathRight, z as nullReplaceEqualDeep } from "./assets/__tanstack-start-server-fn-resolver-D1Ni2lpa.js";
4
4
  import { n as getAuth } from "./assets/auth.server-BLVDnTCZ.js";
5
5
  import { ReadableStream as ReadableStream$1 } from "node:stream/web";
6
6
  import { PassThrough, Readable } from "node:stream";
@@ -15001,7 +15001,7 @@ var NullProtoObj = /* @__PURE__ */ (() => {
15001
15001
  return e.prototype = Object.create(null), Object.freeze(e.prototype), e;
15002
15002
  })();
15003
15003
  //#endregion
15004
- //#region ../../node_modules/.bun/srvx@0.11.12/node_modules/srvx/dist/_chunks/_url.mjs
15004
+ //#region ../../node_modules/.bun/srvx@0.11.13/node_modules/srvx/dist/_chunks/_url.mjs
15005
15005
  function lazyInherit(target, source, sourceKey) {
15006
15006
  for (const key of [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)]) {
15007
15007
  if (key === "constructor") continue;
@@ -15056,7 +15056,8 @@ var FastURL = /* @__PURE__ */ (() => {
15056
15056
  #searchParams;
15057
15057
  #pos;
15058
15058
  constructor(url) {
15059
- if (typeof url === "string") this.#href = url;
15059
+ if (typeof url === "string") if (url[0] === "/") this.#href = url;
15060
+ else this.#url = new NativeURL(url);
15060
15061
  else if (_needsNormRE.test(url.pathname)) this.#url = new NativeURL(`${url.protocol || "http:"}//${url.host || "localhost"}${url.pathname}${url.search || ""}`);
15061
15062
  else {
15062
15063
  this.#protocol = url.protocol;
@@ -15144,7 +15145,7 @@ var FastURL = /* @__PURE__ */ (() => {
15144
15145
  return FastURL;
15145
15146
  })();
15146
15147
  //#endregion
15147
- //#region ../../node_modules/.bun/srvx@0.11.12/node_modules/srvx/dist/adapters/node.mjs
15148
+ //#region ../../node_modules/.bun/srvx@0.11.13/node_modules/srvx/dist/adapters/node.mjs
15148
15149
  /**
15149
15150
  * Fast Response for Node.js runtime
15150
15151
  *
@@ -16403,7 +16404,7 @@ var baseManifestPromise;
16403
16404
  */
16404
16405
  var cachedFinalManifestPromise;
16405
16406
  async function loadEntries() {
16406
- const routerEntry = await import("./assets/router-Cxm33tn3.js");
16407
+ const routerEntry = await import("./assets/router-noqexgr0.js");
16407
16408
  return {
16408
16409
  startEntry: await import("./assets/start-BiybVoR2.js"),
16409
16410
  routerEntry
@@ -0,0 +1,111 @@
1
+ import {
2
+ flattenSources
3
+ } from "./chunk-TRURQFP4.js";
4
+
5
+ // src/plugins/runner.ts
6
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs";
7
+ import { join, resolve } from "path";
8
+ import matter from "gray-matter";
9
+ function walkFiles(dir, extensions) {
10
+ const results = [];
11
+ if (!existsSync(dir)) return results;
12
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
13
+ const fullPath = join(dir, entry.name);
14
+ if (entry.isDirectory()) {
15
+ results.push(...walkFiles(fullPath, extensions));
16
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
17
+ results.push(fullPath);
18
+ }
19
+ }
20
+ return results;
21
+ }
22
+ function runBeforeScan(plugins, config, opts) {
23
+ const beforeScanPlugins = plugins.filter((p) => p.beforeScan);
24
+ if (beforeScanPlugins.length === 0) return 0;
25
+ const sources = flattenSources(config);
26
+ const extensions = (config.include ?? ["**/*.mdx", "**/*.md"]).map(
27
+ (p) => p.replace("**/*", "")
28
+ );
29
+ let modifiedCount = 0;
30
+ for (const source of sources) {
31
+ if (opts?.project && source.project !== opts.project) continue;
32
+ if (opts?.version && source.version !== opts.version) continue;
33
+ const enDir = resolve(source.sourcePath);
34
+ if (!existsSync(enDir)) {
35
+ console.log(` [SKIP] ${source.versionKey}: source dir not found`);
36
+ continue;
37
+ }
38
+ const files = walkFiles(enDir, extensions);
39
+ for (const filePath of files) {
40
+ const content = readFileSync(filePath, "utf-8");
41
+ let parsed;
42
+ try {
43
+ parsed = matter(content);
44
+ } catch {
45
+ continue;
46
+ }
47
+ const readFile = (refPath) => {
48
+ const fromDir = filePath.substring(0, filePath.lastIndexOf("/"));
49
+ const candidates = [
50
+ resolve(fromDir, refPath),
51
+ resolve(enDir, refPath),
52
+ resolve(process.cwd(), refPath)
53
+ ];
54
+ for (const candidate of candidates) {
55
+ if (existsSync(candidate)) {
56
+ return readFileSync(candidate, "utf-8");
57
+ }
58
+ }
59
+ return null;
60
+ };
61
+ let modified = content;
62
+ let wasModified = false;
63
+ for (const plugin of beforeScanPlugins) {
64
+ const result = plugin.beforeScan({
65
+ filePath,
66
+ content: modified,
67
+ frontmatter: parsed.data,
68
+ readFile,
69
+ project: source.project,
70
+ version: source.version
71
+ });
72
+ if (typeof result === "string") {
73
+ modified = result;
74
+ wasModified = true;
75
+ try {
76
+ parsed = matter(modified);
77
+ } catch {
78
+ }
79
+ }
80
+ }
81
+ if (wasModified) {
82
+ const relPath = filePath.slice(enDir.length + 1);
83
+ if (opts?.dryRun) {
84
+ console.log(` [DRY-RUN] ${source.versionKey}/${relPath}`);
85
+ } else {
86
+ writeFileSync(filePath, modified, "utf-8");
87
+ console.log(` [RESOLVED] ${source.versionKey}/${relPath}`);
88
+ }
89
+ modifiedCount++;
90
+ }
91
+ }
92
+ }
93
+ return modifiedCount;
94
+ }
95
+ function createShouldScanBody(plugins) {
96
+ const hooks = plugins.filter((p) => p.shouldScanBody);
97
+ if (hooks.length === 0) return void 0;
98
+ return (ctx) => {
99
+ for (const plugin of hooks) {
100
+ if (plugin.shouldScanBody(ctx) === false) {
101
+ return false;
102
+ }
103
+ }
104
+ return true;
105
+ };
106
+ }
107
+
108
+ export {
109
+ runBeforeScan,
110
+ createShouldScanBody
111
+ };
package/dist/cli.js CHANGED
@@ -144,6 +144,8 @@ Commands:
144
144
  translate Translate content to target language(s)
145
145
  assemble Assemble translated files from EN + cache
146
146
  rescan Rescan source files and clean orphans
147
+ resolve Run beforeScan plugins (e.g., resolve ref directives)
148
+ pipeline Run full pipeline: sync \u2192 resolve \u2192 rescan \u2192 translate \u2192 upload \u2192 status
147
149
  status Show translation coverage
148
150
  admin Start admin dashboard
149
151
  site Manage documentation site (dev, build, upload, deploy) [--custom]
@@ -159,6 +161,9 @@ Options:
159
161
  --max-tokens <n> Max output tokens (default: 16384)
160
162
  --context-length <n> Model context window (default: 32768)
161
163
  --dry-run Preview without making changes
164
+ --skip-sync Pipeline: skip sync step
165
+ --skip-translate Pipeline: skip translate step
166
+ --skip-upload Pipeline: skip upload step
162
167
  --port <n> Admin dashboard port (default: 3456) / Site dev port (default: 3000)
163
168
  `);
164
169
  return;
@@ -176,7 +181,7 @@ Options:
176
181
  console.error("Error: --lang is required");
177
182
  process.exit(1);
178
183
  }
179
- const { translate } = await import("./translate-A5X6MX4Y.js");
184
+ const { translate } = await import("./translate-AGUNMZMQ.js");
180
185
  await translate(config, {
181
186
  lang,
182
187
  project: getOpt("project") || void 0,
@@ -193,7 +198,7 @@ Options:
193
198
  break;
194
199
  }
195
200
  case "assemble": {
196
- const { assembleAll } = await import("./assemble-CP2BRYQJ.js");
201
+ const { assembleAll } = await import("./assemble-S3MWVLBI.js");
197
202
  await assembleAll(config, {
198
203
  project: getOpt("project") || void 0,
199
204
  version: getOpt("version") || void 0,
@@ -202,15 +207,49 @@ Options:
202
207
  break;
203
208
  }
204
209
  case "rescan": {
205
- const { rescan } = await import("./rescan-HXMWFAOC.js");
210
+ const { rescan } = await import("./rescan-7XDHUNH5.js");
211
+ const plugins = config.plugins ?? [];
212
+ const shouldScanBody = plugins.length > 0 ? (await import("./runner-KUGSO4CL.js")).createShouldScanBody(plugins) : void 0;
206
213
  await rescan(config, {
207
214
  project: getOpt("project") || void 0,
208
- version: getOpt("version") || void 0
215
+ version: getOpt("version") || void 0,
216
+ shouldScanBody
217
+ });
218
+ break;
219
+ }
220
+ case "resolve": {
221
+ const resolvePlugins = config.plugins ?? [];
222
+ if (resolvePlugins.length === 0) {
223
+ console.log("No plugins configured. Nothing to resolve.");
224
+ break;
225
+ }
226
+ const { runBeforeScan } = await import("./runner-KUGSO4CL.js");
227
+ const modified = runBeforeScan(resolvePlugins, config, {
228
+ project: getOpt("project") || void 0,
229
+ version: getOpt("version") || void 0,
230
+ dryRun: hasFlag("dry-run")
231
+ });
232
+ console.log(`
233
+ ${modified} file(s) resolved`);
234
+ break;
235
+ }
236
+ case "pipeline": {
237
+ const { pipeline } = await import("./pipeline-3XGFR4QL.js");
238
+ await pipeline(config, {
239
+ skipSync: hasFlag("skip-sync"),
240
+ skipTranslate: hasFlag("skip-translate"),
241
+ skipUpload: hasFlag("skip-upload"),
242
+ dryRun: hasFlag("dry-run"),
243
+ project: getOpt("project") || void 0,
244
+ version: getOpt("version") || void 0,
245
+ lang: getOpt("lang") || void 0,
246
+ max: getOpt("max") ? Number(getOpt("max")) : void 0,
247
+ concurrency: getOpt("concurrency") ? Number(getOpt("concurrency")) : void 0
209
248
  });
210
249
  break;
211
250
  }
212
251
  case "status": {
213
- const { status } = await import("./status-AGZDXOTZ.js");
252
+ const { status } = await import("./status-NX47XZIU.js");
214
253
  await status(config, { lang: getOpt("lang") || void 0 });
215
254
  break;
216
255
  }
package/dist/index.d.ts CHANGED
@@ -2,10 +2,44 @@ import * as unified from 'unified';
2
2
  import * as mdast from 'mdast';
3
3
  import { Root } from 'hast';
4
4
 
5
+ /**
6
+ * Plugin interface for docs-i18n.
7
+ *
8
+ * Plugins can hook into the scan pipeline to transform source files
9
+ * before they are parsed and to control which parts of a file are scanned.
10
+ */
11
+ interface DocsI18nPlugin {
12
+ name: string;
13
+ /**
14
+ * Called before scanning a source file.
15
+ * Receives the file content and frontmatter, and can return modified content.
16
+ * If the plugin returns a string, the file content is replaced with that string.
17
+ * If undefined/void is returned, the file is left unchanged.
18
+ */
19
+ beforeScan?: (ctx: {
20
+ filePath: string;
21
+ content: string;
22
+ frontmatter: Record<string, any>;
23
+ readFile: (refPath: string) => string | null;
24
+ project: string;
25
+ version: string;
26
+ }) => string | undefined | void;
27
+ /**
28
+ * Called to determine whether a file's body should be scanned for translation nodes.
29
+ * If any plugin returns false, only frontmatter fields will be scanned.
30
+ * If all plugins return true (or don't implement this hook), the full body is scanned.
31
+ */
32
+ shouldScanBody?: (ctx: {
33
+ filePath: string;
34
+ frontmatter: Record<string, any>;
35
+ }) => boolean;
36
+ }
37
+
5
38
  /**
6
39
  * docs-i18n configuration schema.
7
40
  * Each project provides a `docs-i18n.config.ts` file.
8
41
  */
42
+
9
43
  interface ProjectConfig {
10
44
  /** Map of version → source directory (relative to project root) */
11
45
  sources: Record<string, string>;
@@ -37,6 +71,15 @@ interface DocsI18nConfig {
37
71
  version: string;
38
72
  lang: string;
39
73
  }) => Promise<void>;
74
+ /** Shell command to run before pipeline (e.g., sync upstream content) */
75
+ syncScript?: string;
76
+ /** Plugins for the scan pipeline */
77
+ plugins?: DocsI18nPlugin[];
78
+ /** Cloudflare D1 database configuration for remote upload */
79
+ d1?: {
80
+ database: string;
81
+ accountId?: string;
82
+ };
40
83
  }
41
84
  declare function defineConfig(config: DocsI18nConfig): DocsI18nConfig;
42
85
  /** Load config from file */
@@ -296,4 +339,68 @@ declare function normalize(content: string): string;
296
339
  */
297
340
  declare function getPackageVersion(): string;
298
341
 
299
- export { type DocsI18nConfig, type Heading, type ProjectConfig, type RenderedMarkdown, TranslationCache, assemble, defineConfig, extractHeadings, extractTranslatableFields, flattenSources, getPackageVersion, loadConfig, normalize, parseMdx, processor, reconstructFrontmatter, renderMarkdown };
342
+ /**
343
+ * Plugin execution engine.
344
+ *
345
+ * Provides functions to run plugin hooks across source files.
346
+ */
347
+
348
+ /**
349
+ * Run beforeScan hooks for all plugins on all source files.
350
+ *
351
+ * Walks every source directory, reads each file, runs every plugin's
352
+ * beforeScan hook, and writes back modified content.
353
+ *
354
+ * Returns the number of files that were modified.
355
+ */
356
+ declare function runBeforeScan(plugins: DocsI18nPlugin[], config: DocsI18nConfig, opts?: {
357
+ project?: string;
358
+ version?: string;
359
+ dryRun?: boolean;
360
+ }): number;
361
+ /**
362
+ * Create a shouldScanBody function that checks all plugins.
363
+ *
364
+ * Returns a function that takes a file path and frontmatter and returns
365
+ * false if any plugin says the body should not be scanned.
366
+ */
367
+ declare function createShouldScanBody(plugins: DocsI18nPlugin[]): ((ctx: {
368
+ filePath: string;
369
+ frontmatter: Record<string, any>;
370
+ }) => boolean) | undefined;
371
+
372
+ /**
373
+ * TanStack ref resolver plugin.
374
+ *
375
+ * Resolves `ref` frontmatter directives in markdown files, following ref chains
376
+ * up to a maximum depth. Applies `replace{}` text substitutions and section
377
+ * replacements from the origin file's frontmatter.
378
+ *
379
+ * Ported from tanstack-i18n-docs/scripts/resolve-refs.ts
380
+ */
381
+
382
+ /**
383
+ * Create the TanStack ref resolver plugin.
384
+ *
385
+ * This plugin resolves `ref` frontmatter directives by following ref chains
386
+ * and applying replace/section substitutions.
387
+ */
388
+ declare function tanstackRefPlugin(): DocsI18nPlugin;
389
+
390
+ /**
391
+ * Next.js source filter plugin.
392
+ *
393
+ * Files with a `source:` field in frontmatter reference external content
394
+ * that will be fetched at build time. Their body should not be scanned
395
+ * for translation since it's just a placeholder / empty.
396
+ */
397
+
398
+ /**
399
+ * Create the Next.js source filter plugin.
400
+ *
401
+ * When a file has `source:` in its frontmatter, shouldScanBody returns false
402
+ * so only translatable frontmatter fields (title, description, etc.) are scanned.
403
+ */
404
+ declare function nextjsSourcePlugin(): DocsI18nPlugin;
405
+
406
+ export { type DocsI18nConfig, type DocsI18nPlugin, type Heading, type ProjectConfig, type RenderedMarkdown, TranslationCache, assemble, createShouldScanBody, defineConfig, extractHeadings, extractTranslatableFields, flattenSources, getPackageVersion, loadConfig, nextjsSourcePlugin, normalize, parseMdx, processor, reconstructFrontmatter, renderMarkdown, runBeforeScan, tanstackRefPlugin };