fumadocs-core 15.1.0 → 15.1.2

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.
@@ -2,7 +2,7 @@ import {
2
2
  _highlight,
3
3
  _renderHighlight,
4
4
  highlight
5
- } from "./chunk-E7AASGCN.js";
5
+ } from "./chunk-BUCUQ3WX.js";
6
6
 
7
7
  // src/highlight/client.tsx
8
8
  import {
@@ -19,9 +19,8 @@ var defaultThemes = {
19
19
  light: "github-light",
20
20
  dark: "github-dark"
21
21
  };
22
- var defaultEngine;
22
+ var highlighters = /* @__PURE__ */ new Map();
23
23
  async function _highlight(code, options) {
24
- const { getSingletonHighlighter } = await import("shiki");
25
24
  const { lang, components: _, engine, ...rest } = options;
26
25
  let themes = { themes: defaultThemes };
27
26
  if ("theme" in options && options.theme) {
@@ -29,11 +28,9 @@ async function _highlight(code, options) {
29
28
  } else if ("themes" in options && options.themes) {
30
29
  themes = { themes: options.themes };
31
30
  }
32
- const highlighter = await getSingletonHighlighter({
31
+ const highlighter = await getHighlighter("custom", {
32
+ engine,
33
33
  langs: [lang],
34
- engine: engine ?? (defaultEngine ??= await import("shiki/engine/oniguruma").then(
35
- (res) => res.createOnigurumaEngine(import("shiki/wasm"))
36
- )),
37
34
  themes: "theme" in themes ? [themes.theme] : Object.values(themes.themes).filter((v) => v !== void 0)
38
35
  });
39
36
  return highlighter.codeToHast(code, {
@@ -53,6 +50,38 @@ function _renderHighlight(hast, options) {
53
50
  Fragment
54
51
  });
55
52
  }
53
+ async function getHighlighter(engineType, options) {
54
+ const { createHighlighter } = await import("shiki");
55
+ let highlighter = highlighters.get(engineType);
56
+ if (!highlighter) {
57
+ let engine = options.engine;
58
+ if (engineType === "js") {
59
+ engine = import("shiki/engine/javascript").then(
60
+ (res) => res.createJavaScriptRegexEngine()
61
+ );
62
+ }
63
+ if (engineType === "oniguruma" || !engine) {
64
+ engine = import("shiki/engine/oniguruma").then(
65
+ (res) => res.createOnigurumaEngine(import("shiki/wasm"))
66
+ );
67
+ }
68
+ highlighter = createHighlighter({
69
+ ...options,
70
+ engine
71
+ });
72
+ highlighters.set(engineType, highlighter);
73
+ return highlighter;
74
+ }
75
+ return highlighter.then(async (instance) => {
76
+ await Promise.all([
77
+ // @ts-expect-error unknown
78
+ instance.loadLanguage(...options.langs),
79
+ // @ts-expect-error unknown
80
+ instance.loadTheme(...options.themes)
81
+ ]);
82
+ return instance;
83
+ });
84
+ }
56
85
  async function highlight(code, options) {
57
86
  return _renderHighlight(await _highlight(code, options), options);
58
87
  }
@@ -62,5 +91,6 @@ export {
62
91
  defaultThemes,
63
92
  _highlight,
64
93
  _renderHighlight,
94
+ getHighlighter,
65
95
  highlight
66
96
  };
@@ -2,21 +2,22 @@
2
2
  function splitPath(path) {
3
3
  return path.split("/").filter((p) => p.length > 0);
4
4
  }
5
- function resolvePath(from, join) {
6
- const v1 = splitPath(from), v2 = splitPath(join);
7
- while (v2.length > 0) {
8
- switch (v2[0]) {
5
+ function joinPath(...paths) {
6
+ const out = [];
7
+ const parsed = paths.flatMap(splitPath);
8
+ while (parsed.length > 0) {
9
+ switch (parsed[0]) {
9
10
  case "..":
10
- v1.pop();
11
+ out.pop();
11
12
  break;
12
13
  case ".":
13
14
  break;
14
15
  default:
15
- v1.push(v2[0]);
16
+ out.push(parsed[0]);
16
17
  }
17
- v2.shift();
18
+ parsed.shift();
18
19
  }
19
- return v1.join("/");
20
+ return out.join("/");
20
21
  }
21
22
  function slash(path) {
22
23
  const isExtendedLengthPath = path.startsWith("\\\\?\\");
@@ -28,6 +29,6 @@ function slash(path) {
28
29
 
29
30
  export {
30
31
  splitPath,
31
- resolvePath,
32
+ joinPath,
32
33
  slash
33
34
  };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  useShiki
4
- } from "../chunk-Q73QZKHO.js";
5
- import "../chunk-E7AASGCN.js";
4
+ } from "../chunk-62S4EN7J.js";
5
+ import "../chunk-BUCUQ3WX.js";
6
6
  import "../chunk-MLKGABMK.js";
7
7
  export {
8
8
  useShiki
@@ -2,7 +2,7 @@ import "../chunk-WQMD6AUR.js";
2
2
  import {
3
3
  createStyleTransformer,
4
4
  highlight
5
- } from "../chunk-E7AASGCN.js";
5
+ } from "../chunk-BUCUQ3WX.js";
6
6
  import "../chunk-MLKGABMK.js";
7
7
  export {
8
8
  createStyleTransformer,
@@ -3,13 +3,14 @@ import {
3
3
  remarkHeading
4
4
  } from "../chunk-IYQ35KI2.js";
5
5
  import {
6
- resolvePath,
6
+ joinPath,
7
7
  slash
8
- } from "../chunk-SHGL6VBO.js";
8
+ } from "../chunk-XMCPKVJQ.js";
9
9
  import {
10
10
  createStyleTransformer,
11
- defaultThemes
12
- } from "../chunk-E7AASGCN.js";
11
+ defaultThemes,
12
+ getHighlighter
13
+ } from "../chunk-BUCUQ3WX.js";
13
14
  import "../chunk-MLKGABMK.js";
14
15
 
15
16
  // src/mdx-plugins/index.ts
@@ -25,11 +26,8 @@ import {
25
26
  transformerNotationWordHighlight
26
27
  } from "@shikijs/transformers";
27
28
  import {
28
- getSingletonHighlighter,
29
29
  bundledLanguages
30
30
  } from "shiki";
31
- import { createOnigurumaEngine } from "shiki/engine/oniguruma";
32
- import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
33
31
 
34
32
  // src/mdx-plugins/transformer-icon.ts
35
33
  var defaultShortcuts = {
@@ -248,13 +246,15 @@ function rehypeCode(_options = {}) {
248
246
  if (options.tab !== false) {
249
247
  transformers.push(transformerTab());
250
248
  }
251
- const highlighter = getSingletonHighlighter({
252
- engine: options.experimentalJSEngine ? createJavaScriptRegexEngine() : createOnigurumaEngine(() => import("shiki/wasm")),
253
- themes: "themes" in options ? Object.values(options.themes).filter(Boolean) : [options.theme],
254
- langs: options.langs ?? (options.lazy ? void 0 : Object.keys(bundledLanguages))
255
- });
249
+ const highlighter = getHighlighter(
250
+ options.experimentalJSEngine ? "js" : "oniguruma",
251
+ {
252
+ themes: "themes" in options ? Object.values(options.themes).filter(Boolean) : [options.theme],
253
+ langs: options.langs ?? (options.lazy ? [] : Object.keys(bundledLanguages))
254
+ }
255
+ );
256
256
  const transformer = highlighter.then(
257
- (instance) => rehypeShikiFromHighlighter(instance, {
257
+ (loaded) => rehypeShikiFromHighlighter(loaded, {
258
258
  ...options,
259
259
  transformers
260
260
  })
@@ -430,7 +430,7 @@ async function getImageSize(src, dir) {
430
430
  url = src;
431
431
  } else if (EXTERNAL_URL_REGEX.test(dir) && isRelative) {
432
432
  const base = new URL(dir);
433
- base.pathname = resolvePath(base.pathname, src);
433
+ base.pathname = joinPath(base.pathname, src);
434
434
  url = base.toString();
435
435
  } else {
436
436
  return imageSizeFromFile(isRelative ? path.join(dir, src) : src);
@@ -1,5 +1,4 @@
1
1
  import { TypedDocument, Orama, Language, RawData, create, SearchParams } from '@orama/orama';
2
- import { NextRequest } from 'next/server';
3
2
  import { S as StructuredData } from '../remark-structure-1kvQbrfH.js';
4
3
  import { S as SortedResult } from '../types-Ch8gnVgO.js';
5
4
  import { I as I18nConfig } from '../config-inq6kP6y.js';
@@ -70,7 +69,7 @@ interface SearchServer {
70
69
  export: () => Promise<ExportedData>;
71
70
  }
72
71
  interface SearchAPI extends SearchServer {
73
- GET: (request: NextRequest) => Promise<Response>;
72
+ GET: (request: Request) => Promise<Response>;
74
73
  /**
75
74
  * `GET` route handler that exports search indexes for static search.
76
75
  */
@@ -19,12 +19,13 @@ function createEndpoint(server) {
19
19
  return Response.json(await server.export());
20
20
  },
21
21
  async GET(request) {
22
- const query = request.nextUrl.searchParams.get("query");
22
+ const url = new URL(request.url);
23
+ const query = url.searchParams.get("query");
23
24
  if (!query) return Response.json([]);
24
25
  return Response.json(
25
26
  await search(query, {
26
- tag: request.nextUrl.searchParams.get("tag") ?? void 0,
27
- locale: request.nextUrl.searchParams.get("locale") ?? void 0
27
+ tag: url.searchParams.get("tag") ?? void 0,
28
+ locale: url.searchParams.get("locale") ?? void 0
28
29
  })
29
30
  );
30
31
  }
@@ -5,7 +5,7 @@ import "../chunk-WQMD6AUR.js";
5
5
  import {
6
6
  createStyleTransformer,
7
7
  highlight
8
- } from "../chunk-E7AASGCN.js";
8
+ } from "../chunk-BUCUQ3WX.js";
9
9
  import "../chunk-MLKGABMK.js";
10
10
 
11
11
  // src/server/get-toc.ts
@@ -4,31 +4,52 @@ import { R as Root, I as Item, F as Folder$1, S as Separator } from '../page-tre
4
4
 
5
5
  interface FileInfo {
6
6
  /**
7
- * The locale extension of file
7
+ * File path without extension
8
+ *
9
+ * @deprecated obtain it with `join(dirname, name)`
8
10
  */
9
- locale?: string;
11
+ flattenedPath: string;
10
12
  /**
11
13
  * Original path of file
12
14
  */
13
15
  path: string;
14
16
  /**
15
- * File path without extension
17
+ * File name without locale and extension
16
18
  */
17
- flattenedPath: string;
19
+ name: string;
18
20
  /**
19
- * File name without locale and extension
21
+ * file extension from the last `.`, like `.md`
22
+ *
23
+ * empty string if no file extension
24
+ */
25
+ ext: string;
26
+ dirname: string;
27
+ }
28
+ interface FolderInfo {
29
+ /**
30
+ * Original path of folder
31
+ */
32
+ path: string;
33
+ /**
34
+ * folder name
20
35
  */
21
36
  name: string;
22
37
  dirname: string;
23
38
  }
24
39
  declare function parseFilePath(path: string): FileInfo;
25
- declare function parseFolderPath(path: string): FileInfo;
40
+ declare function parseFolderPath(path: string): FolderInfo;
26
41
 
27
42
  interface LoadOptions {
28
43
  transformers?: Transformer[];
29
- rootDir?: string;
30
44
  getSlugs: (info: FileInfo) => string[];
31
45
  }
46
+ interface I18nLoadOptions extends LoadOptions {
47
+ i18n: {
48
+ parser: 'dot' | 'dir';
49
+ languages: string[];
50
+ defaultLanguage: string;
51
+ };
52
+ }
32
53
  interface VirtualFile {
33
54
  /**
34
55
  * Relative path
@@ -47,7 +68,7 @@ type Transformer = (context: {
47
68
  storage: Storage;
48
69
  options: LoadOptions;
49
70
  }) => void;
50
- declare function loadFiles(files: VirtualFile[], options: LoadOptions): Storage;
71
+ declare function loadFiles<O extends LoadOptions>(files: VirtualFile[], options: O): Storage;
51
72
 
52
73
  interface LoaderConfig {
53
74
  source: SourceConfig;
@@ -58,11 +79,6 @@ interface SourceConfig {
58
79
  metaData: MetaData;
59
80
  }
60
81
  interface LoaderOptions {
61
- /**
62
- * @deprecated It is now recommended to filter files on `source` level
63
- * @defaultValue `''`
64
- */
65
- rootDir?: string;
66
82
  baseUrl: string;
67
83
  icon?: NonNullable<BuildPageTreeOptions['resolveIcon']>;
68
84
  slugs?: LoadOptions['getSlugs'];
@@ -76,14 +92,16 @@ interface LoaderOptions {
76
92
  /**
77
93
  * Configure i18n
78
94
  */
79
- i18n?: I18nConfig;
95
+ i18n?: I18nConfig & {
96
+ parser?: I18nLoadOptions['i18n']['parser'];
97
+ };
80
98
  }
81
99
  interface Source<Config extends SourceConfig> {
82
100
  /**
83
101
  * @internal
84
102
  */
85
103
  _config?: Config;
86
- files: VirtualFile[] | ((rootDir: string) => VirtualFile[]);
104
+ files: VirtualFile[] | (() => VirtualFile[]);
87
105
  }
88
106
  interface Page<Data = PageData> {
89
107
  file: FileInfo;
@@ -105,7 +123,7 @@ interface LoaderOutput<Config extends LoaderConfig> {
105
123
  getPageTree: (locale?: string) => Root;
106
124
  getPageByHref: (href: string, options?: {
107
125
  /**
108
- * resolve relative file paths in `href` from specified directory
126
+ * resolve relative file paths in `href` from specified dirname, must be a virtual path.
109
127
  */
110
128
  dir?: string;
111
129
  }) => {
@@ -174,7 +192,7 @@ interface PageFile {
174
192
  }
175
193
  type File = MetaFile | PageFile;
176
194
  interface Folder {
177
- file: FileInfo;
195
+ file: FolderInfo;
178
196
  children: (File | Folder)[];
179
197
  }
180
198
  /**
@@ -213,7 +231,7 @@ declare namespace fileSystem {
213
231
  export { type fileSystem_File as File, type fileSystem_Folder as Folder, type fileSystem_MetaFile as MetaFile, type fileSystem_PageFile as PageFile, fileSystem_Storage as Storage };
214
232
  }
215
233
 
216
- interface BuildPageTreeOptions {
234
+ interface Options {
217
235
  /**
218
236
  * Remove references to the file path of original nodes (`$ref`)
219
237
  *
@@ -223,11 +241,14 @@ interface BuildPageTreeOptions {
223
241
  attachFile?: (node: Item, file?: PageFile) => Item;
224
242
  attachFolder?: (node: Folder$1, folder: Folder, meta?: MetaFile) => Folder$1;
225
243
  attachSeparator?: (node: Separator) => Separator;
226
- storage: Storage;
227
244
  getUrl: UrlFn;
228
245
  resolveIcon?: (icon: string | undefined) => ReactElement | undefined;
229
246
  }
230
- interface BuildPageTreeOptionsWithI18n extends BuildPageTreeOptions {
247
+ interface BuildPageTreeOptions extends Options {
248
+ storage: Storage;
249
+ }
250
+ interface BuildPageTreeOptionsWithI18n extends Options {
251
+ storages: Record<string, Storage>;
231
252
  i18n: I18nConfig;
232
253
  }
233
254
  interface PageTreeBuilder {
@@ -239,4 +260,4 @@ interface PageTreeBuilder {
239
260
  }
240
261
  declare function createPageTreeBuilder(): PageTreeBuilder;
241
262
 
242
- export { type BuildPageTreeOptions, type BuildPageTreeOptionsWithI18n, type FileInfo, fileSystem as FileSystem, type InferMetaType, type InferPageType, type LanguageEntry, type LoadOptions, type LoaderConfig, type LoaderOptions, type LoaderOutput, type Meta, type MetaData, type Page, type PageData, type PageTreeBuilder, type Source, type SourceConfig, type Transformer, type UrlFn, type VirtualFile, createGetUrl, createPageTreeBuilder, getSlugs, loadFiles, loader, parseFilePath, parseFolderPath };
263
+ export { type BuildPageTreeOptions, type BuildPageTreeOptionsWithI18n, type FileInfo, fileSystem as FileSystem, type FolderInfo, type InferMetaType, type InferPageType, type LanguageEntry, type LoadOptions, type LoaderConfig, type LoaderOptions, type LoaderOutput, type Meta, type MetaData, type Page, type PageData, type PageTreeBuilder, type Source, type SourceConfig, type Transformer, type UrlFn, type VirtualFile, createGetUrl, createPageTreeBuilder, getSlugs, loadFiles, loader, parseFilePath, parseFolderPath };
@@ -1,8 +1,8 @@
1
1
  import {
2
- resolvePath,
2
+ joinPath,
3
3
  slash,
4
4
  splitPath
5
- } from "../chunk-SHGL6VBO.js";
5
+ } from "../chunk-XMCPKVJQ.js";
6
6
  import {
7
7
  __export
8
8
  } from "../chunk-MLKGABMK.js";
@@ -24,8 +24,12 @@ function buildAll(nodes, ctx, skipIndex) {
24
24
  for (const node of [...nodes].sort(
25
25
  (a, b) => a.file.name.localeCompare(b.file.name)
26
26
  )) {
27
- if (isPageFile(node) && !node.file.locale) {
28
- const treeNode = buildFileNode(node, ctx);
27
+ if (isPageFile(node)) {
28
+ const localized = ctx.localeStorage?.read(
29
+ joinPath(node.file.dirname, node.file.name),
30
+ "page"
31
+ );
32
+ const treeNode = buildFileNode(localized ?? node, ctx);
29
33
  if (node.file.name === "index") {
30
34
  if (!skipIndex) output.unshift(treeNode);
31
35
  continue;
@@ -33,10 +37,11 @@ function buildAll(nodes, ctx, skipIndex) {
33
37
  output.push(treeNode);
34
38
  }
35
39
  if ("children" in node) {
36
- if (node.children.length === 1 && node.children[0].file.name === "index" && isPageFile(node.children[0])) {
37
- output.push(buildFileNode(node.children[0], ctx));
40
+ const folder = buildFolderNode(node, false, ctx);
41
+ if (folder.children.length === 0 && folder.index) {
42
+ output.push(folder.index);
38
43
  } else {
39
- folders.push(buildFolderNode(node, false, ctx));
44
+ folders.push(folder);
40
45
  }
41
46
  }
42
47
  }
@@ -75,8 +80,8 @@ function resolveFolderItem(folder, item, ctx, idx, addedNodePaths) {
75
80
  } else if (isExtract) {
76
81
  filename = item.slice(extractPrefix.length);
77
82
  }
78
- const path2 = resolvePath(folder.file.path, filename);
79
- const itemNode = ctx.storage.readDir(path2) ?? ctx.storage.read(path2, "page");
83
+ const path = joinPath(folder.file.path, filename);
84
+ const itemNode = ctx.storage.readDir(path) ?? ctx.localeStorage?.read(path, "page") ?? ctx.storage.read(path, "page");
80
85
  if (!itemNode) return [];
81
86
  addedNodePaths.add(itemNode.file.path);
82
87
  if (isExcept) return [];
@@ -87,10 +92,10 @@ function resolveFolderItem(folder, item, ctx, idx, addedNodePaths) {
87
92
  return [buildFileNode(itemNode, ctx)];
88
93
  }
89
94
  function buildFolderNode(folder, isGlobalRoot, ctx) {
90
- const metaPath = resolvePath(folder.file.path, "meta");
91
- const meta = findLocalizedFile(metaPath, "meta", ctx) ?? ctx.storage.read(metaPath, "meta");
95
+ const metaPath = joinPath(folder.file.path, "meta");
96
+ const meta = ctx.localeStorage?.read(metaPath, "meta") ?? ctx.storage.read(metaPath, "meta");
92
97
  const indexFile = ctx.storage.read(
93
- resolvePath(folder.file.flattenedPath, "index"),
98
+ joinPath(folder.file.path, "index"),
94
99
  "page"
95
100
  );
96
101
  const metadata = meta?.data;
@@ -137,16 +142,15 @@ function buildFolderNode(folder, isGlobalRoot, ctx) {
137
142
  return ctx.options.attachFolder?.(node, folder, meta) ?? node;
138
143
  }
139
144
  function buildFileNode(file, ctx) {
140
- const localized = findLocalizedFile(file.file.flattenedPath, "page", ctx) ?? file;
141
145
  const item = {
142
- $id: localized.file.path,
146
+ $id: file.file.path,
143
147
  type: "page",
144
- name: localized.data.data.title ?? pathToName(localized.file.name),
145
- description: localized.data.data.description,
146
- icon: ctx.options.resolveIcon?.(localized.data.data.icon),
147
- url: ctx.options.getUrl(localized.data.slugs, ctx.lang),
148
+ name: file.data.data.title ?? pathToName(file.file.name),
149
+ description: file.data.data.description,
150
+ icon: ctx.options.resolveIcon?.(file.data.data.icon),
151
+ url: ctx.options.getUrl(file.data.slugs, ctx.locale),
148
152
  $ref: !ctx.options.noRef ? {
149
- file: localized.file.path
153
+ file: file.file.path
150
154
  } : void 0
151
155
  };
152
156
  return ctx.options.attachFile?.(item, file) ?? item;
@@ -171,11 +175,11 @@ function createPageTreeBuilder() {
171
175
  buildI18n({ i18n, ...options }) {
172
176
  const entries = i18n.languages.map((lang) => {
173
177
  const tree = build({
174
- lang,
175
178
  options,
176
179
  builder: this,
177
- storage: options.storage,
178
- i18n
180
+ locale: lang,
181
+ storage: options.storages[i18n.defaultLanguage],
182
+ localeStorage: options.storages[lang]
179
183
  });
180
184
  return [lang, tree];
181
185
  });
@@ -183,10 +187,6 @@ function createPageTreeBuilder() {
183
187
  }
184
188
  };
185
189
  }
186
- function findLocalizedFile(path2, format, ctx) {
187
- if (!ctx.lang) return;
188
- return ctx.storage.read(`${path2}.${ctx.lang}`, format);
189
- }
190
190
  function pathToName(name) {
191
191
  const result = [];
192
192
  for (const c of name) {
@@ -198,44 +198,35 @@ function pathToName(name) {
198
198
  }
199
199
 
200
200
  // src/source/path.ts
201
- function parseFilePath(path2) {
202
- const segments = splitPath(slash(path2));
201
+ function parseFilePath(path) {
202
+ const segments = splitPath(slash(path));
203
203
  const dirname = segments.slice(0, -1).join("/");
204
- const base = segments.at(-1) ?? "";
205
- const dotIdx = base.lastIndexOf(".");
206
- const nameWithLocale = dotIdx !== -1 ? base.slice(0, dotIdx) : base;
207
- const flattenedPath = [dirname, nameWithLocale].filter((p) => p.length > 0).join("/");
208
- const [name, locale] = getLocale(nameWithLocale);
204
+ let name = segments.at(-1) ?? "";
205
+ let ext = "";
206
+ const dotIdx = name.lastIndexOf(".");
207
+ if (dotIdx !== -1) {
208
+ ext = name.substring(dotIdx);
209
+ name = name.substring(0, dotIdx);
210
+ }
209
211
  return {
210
212
  dirname,
211
213
  name,
212
- flattenedPath,
213
- locale,
214
- path: segments.join("/")
214
+ path: segments.join("/"),
215
+ ext,
216
+ flattenedPath: [dirname, name].filter((p) => p.length > 0).join("/")
215
217
  };
216
218
  }
217
- function parseFolderPath(path2) {
218
- const segments = splitPath(slash(path2));
219
+ function parseFolderPath(path) {
220
+ const segments = splitPath(slash(path));
219
221
  const base = segments.at(-1) ?? "";
220
- const [name, locale] = getLocale(base);
221
- const flattenedPath = segments.join("/");
222
222
  return {
223
223
  dirname: segments.slice(0, -1).join("/"),
224
- name,
225
- flattenedPath,
226
- locale,
227
- path: flattenedPath
224
+ name: base,
225
+ path: segments.join("/")
228
226
  };
229
227
  }
230
- function getLocale(name) {
231
- const sep = name.lastIndexOf(".");
232
- if (sep === -1) return [name];
233
- const locale = name.slice(sep + 1);
234
- if (/\d+/.exec(locale)) return [name];
235
- return [name.slice(0, sep), locale];
236
- }
237
- function normalizePath(path2) {
238
- const segments = splitPath(slash(path2));
228
+ function normalizePath(path) {
229
+ const segments = splitPath(slash(path));
239
230
  if (segments[0] === "." || segments[0] === "..")
240
231
  throw new Error("It must not start with './' or '../'");
241
232
  return segments.join("/");
@@ -260,30 +251,33 @@ var Storage = class {
260
251
  * @param path - flattened path
261
252
  * @param format - file format
262
253
  */
263
- read(path2, format) {
264
- return this.files.get(`${path2}.${format}`);
254
+ read(path, format) {
255
+ return this.files.get(`${path}.${format}`);
265
256
  }
266
- readDir(path2) {
267
- return this.folders.get(path2);
257
+ readDir(path) {
258
+ return this.folders.get(path);
268
259
  }
269
260
  root() {
270
261
  return this.rootFolder;
271
262
  }
272
- write(path2, format, data) {
263
+ write(path, format, data) {
273
264
  const node = {
274
265
  format,
275
- file: parseFilePath(path2),
266
+ file: parseFilePath(path),
276
267
  data
277
268
  };
278
269
  this.makeDir(node.file.dirname);
279
270
  this.readDir(node.file.dirname)?.children.push(node);
280
- this.files.set(`${node.file.flattenedPath}.${node.format}`, node);
271
+ this.files.set(
272
+ joinPath(node.file.dirname, `${node.file.name}.${node.format}`),
273
+ node
274
+ );
281
275
  }
282
276
  list() {
283
277
  return [...this.files.values()];
284
278
  }
285
- makeDir(path2) {
286
- const segments = splitPath(path2);
279
+ makeDir(path) {
280
+ const segments = splitPath(path);
287
281
  for (let i = 0; i < segments.length; i++) {
288
282
  const segment = segments.slice(0, i + 1).join("/");
289
283
  if (this.folders.has(segment)) continue;
@@ -301,20 +295,17 @@ var Storage = class {
301
295
  function loadFiles(files, options) {
302
296
  const { transformers = [] } = options;
303
297
  const storage = new Storage();
304
- const rootDir = normalizePath(options.rootDir ?? "");
305
298
  for (const file of files) {
306
- const normalizedPath = normalizePath(file.path);
307
- if (!normalizedPath.startsWith(rootDir)) continue;
308
- const relativePath = normalizedPath.slice(rootDir.length);
299
+ const parsedPath = normalizePath(file.path);
309
300
  if (file.type === "page") {
310
- const slugs = file.slugs ?? options.getSlugs(parseFilePath(relativePath));
311
- storage.write(relativePath, file.type, {
301
+ const slugs = file.slugs ?? options.getSlugs(parseFilePath(parsedPath));
302
+ storage.write(parsedPath, file.type, {
312
303
  slugs,
313
304
  data: file.data
314
305
  });
315
306
  }
316
307
  if (file.type === "meta") {
317
- storage.write(relativePath, file.type, file.data);
308
+ storage.write(parsedPath, file.type, file.data);
318
309
  }
319
310
  }
320
311
  for (const transformer of transformers) {
@@ -325,28 +316,66 @@ function loadFiles(files, options) {
325
316
  }
326
317
  return storage;
327
318
  }
319
+ function loadFilesI18n(files, options) {
320
+ const parser = options.i18n.parser === "dir" ? dirParser : dotParser;
321
+ const storages = {};
322
+ for (const lang of options.i18n.languages) {
323
+ storages[lang] = loadFiles(
324
+ files.flatMap((file) => {
325
+ const [path, locale] = parser(normalizePath(file.path));
326
+ if ((locale ?? options.i18n.defaultLanguage) === lang) {
327
+ return {
328
+ ...file,
329
+ path
330
+ };
331
+ }
332
+ return [];
333
+ }),
334
+ options
335
+ );
336
+ }
337
+ return storages;
338
+ }
339
+ function dirParser(path) {
340
+ const parsed = path.split("/");
341
+ if (parsed.length >= 2) return [parsed.slice(1).join("/"), parsed[0]];
342
+ return [path];
343
+ }
344
+ function dotParser(path) {
345
+ const segs = path.split("/");
346
+ if (segs.length === 0) return [path];
347
+ const name = segs[segs.length - 1].split(".");
348
+ if (name.length >= 3) {
349
+ const locale = name.splice(name.length - 2, 1)[0];
350
+ if (locale.length > 0 && !/\d+/.test(locale)) {
351
+ segs[segs.length - 1] = name.join(".");
352
+ return [segs.join("/"), locale];
353
+ }
354
+ }
355
+ return [path];
356
+ }
328
357
 
329
358
  // src/source/loader.ts
330
- import * as path from "node:path";
331
- function indexPages(storage, getUrl, i18n) {
359
+ function indexPages(storages, getUrl, i18n) {
332
360
  const defaultLanguage = i18n?.defaultLanguage ?? "";
333
361
  const map = /* @__PURE__ */ new Map();
334
362
  const pathToFile = /* @__PURE__ */ new Map();
335
- for (const item of storage.list()) {
363
+ for (const item of storages[defaultLanguage].list()) {
336
364
  if (item.format === "meta")
337
365
  pathToFile.set(item.file.path, fileToMeta(item));
338
366
  if (item.format === "page") {
339
- const page = fileToPage(item, getUrl, item.file.locale);
367
+ const page = fileToPage(item, getUrl, defaultLanguage);
340
368
  pathToFile.set(item.file.path, page);
341
- if (item.file.locale) continue;
342
369
  map.set(`${defaultLanguage}.${page.slugs.join("/")}`, page);
343
370
  if (!i18n) continue;
371
+ const path = joinPath(item.file.dirname, item.file.name);
344
372
  for (const lang of i18n.languages) {
345
- const localized = storage.read(
346
- `${item.file.flattenedPath}.${lang}`,
347
- "page"
373
+ if (lang === defaultLanguage) continue;
374
+ const localizedPage = fileToPage(
375
+ storages[lang].read(path, "page") ?? item,
376
+ getUrl,
377
+ lang
348
378
  );
349
- const localizedPage = fileToPage(localized ?? item, getUrl, lang);
350
379
  map.set(`${lang}.${localizedPage.slugs.join("/")}`, localizedPage);
351
380
  }
352
381
  }
@@ -387,17 +416,23 @@ function createOutput(options) {
387
416
  if (!options.url && !options.baseUrl) {
388
417
  console.warn("`loader()` now requires a `baseUrl` option to be defined.");
389
418
  }
390
- const { source, rootDir = "", slugs: slugsFn = getSlugs } = options;
419
+ const { source, slugs: slugsFn = getSlugs } = options;
391
420
  const getUrl = options.url ?? createGetUrl(options.baseUrl ?? "/", options.i18n);
392
- const storage = loadFiles(
393
- typeof source.files === "function" ? source.files(rootDir) : source.files,
394
- {
421
+ const files = typeof source.files === "function" ? source.files() : source.files;
422
+ const storages = options.i18n ? loadFilesI18n(files, {
423
+ i18n: {
424
+ ...options.i18n,
425
+ parser: options.i18n.parser ?? "dot"
426
+ },
427
+ transformers: options.transformers,
428
+ getSlugs: slugsFn
429
+ }) : {
430
+ "": loadFiles(files, {
395
431
  transformers: options.transformers,
396
- rootDir,
397
432
  getSlugs: slugsFn
398
- }
399
- );
400
- const walker = indexPages(storage, getUrl, options.i18n);
433
+ })
434
+ };
435
+ const walker = indexPages(storages, getUrl, options.i18n);
401
436
  const builder = createPageTreeBuilder();
402
437
  let pageTree;
403
438
  return {
@@ -405,7 +440,7 @@ function createOutput(options) {
405
440
  get pageTree() {
406
441
  if (options.i18n) {
407
442
  pageTree ??= builder.buildI18n({
408
- storage,
443
+ storages,
409
444
  resolveIcon: options.icon,
410
445
  getUrl,
411
446
  i18n: options.i18n,
@@ -413,7 +448,7 @@ function createOutput(options) {
413
448
  });
414
449
  } else {
415
450
  pageTree ??= builder.build({
416
- storage,
451
+ storage: storages[""],
417
452
  resolveIcon: options.icon,
418
453
  getUrl,
419
454
  ...options.pageTree
@@ -428,7 +463,7 @@ function createOutput(options) {
428
463
  const pages = Array.from(walker.pages.values());
429
464
  const [value, hash] = href.split("#", 2);
430
465
  if (value.startsWith(".") && (value.endsWith(".md") || value.endsWith(".mdx"))) {
431
- const hrefPath = path.join(dir, value);
466
+ const hrefPath = joinPath(dir, value);
432
467
  const target2 = pages.find((item) => item.file.path === hrefPath);
433
468
  if (target2)
434
469
  return {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useShiki
3
- } from "../chunk-Q73QZKHO.js";
4
- import "../chunk-E7AASGCN.js";
3
+ } from "../chunk-62S4EN7J.js";
4
+ import "../chunk-BUCUQ3WX.js";
5
5
  import "../chunk-MLKGABMK.js";
6
6
  export {
7
7
  useShiki
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "15.1.0",
3
+ "version": "15.1.2",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -113,13 +113,13 @@
113
113
  "@types/hast": "^3.0.4",
114
114
  "@types/mdast": "^4.0.3",
115
115
  "@types/negotiator": "^0.6.3",
116
- "@types/node": "22.13.10",
117
- "@types/react": "^19.0.10",
116
+ "@types/node": "22.13.11",
117
+ "@types/react": "^19.0.12",
118
118
  "@types/react-dom": "^19.0.4",
119
119
  "algoliasearch": "4.24.0",
120
120
  "mdast-util-mdx-jsx": "^3.2.0",
121
121
  "mdast-util-mdxjs-esm": "^2.0.1",
122
- "next": "^15.2.2",
122
+ "next": "^15.2.3",
123
123
  "remark-mdx": "^3.1.0",
124
124
  "remark-rehype": "^11.1.1",
125
125
  "typescript": "^5.8.2",