@dominikcz/greg 0.9.27

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.
Files changed (183) hide show
  1. package/README.md +397 -0
  2. package/bin/greg.js +241 -0
  3. package/bin/init.js +351 -0
  4. package/bin/templates/docs/getting-started.md +47 -0
  5. package/bin/templates/docs/index.md +11 -0
  6. package/bin/templates/greg.config.js +39 -0
  7. package/bin/templates/greg.config.ts +38 -0
  8. package/bin/templates/index.html +16 -0
  9. package/bin/templates/src/App.svelte +5 -0
  10. package/bin/templates/src/app.css +20 -0
  11. package/bin/templates/src/main.js +9 -0
  12. package/bin/templates/svelte.config.js +1 -0
  13. package/bin/templates/tsconfig.json +21 -0
  14. package/bin/templates/vite.config.js +23 -0
  15. package/docs/__partials/markdown/examples/basic.md +4 -0
  16. package/docs/__partials/markdown/examples/diff.md +10 -0
  17. package/docs/__partials/markdown/examples/focus.md +5 -0
  18. package/docs/__partials/markdown/examples/language-title.md +3 -0
  19. package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
  20. package/docs/__partials/markdown/examples/line-numbers.md +5 -0
  21. package/docs/__partials/note.md +4 -0
  22. package/docs/guide/__shared-warning.md +4 -0
  23. package/docs/guide/asset-handling.md +88 -0
  24. package/docs/guide/deploying.md +162 -0
  25. package/docs/guide/getting-started.md +334 -0
  26. package/docs/guide/index.md +23 -0
  27. package/docs/guide/localization.md +290 -0
  28. package/docs/guide/markdown/code.md +95 -0
  29. package/docs/guide/markdown/components-and-mermaid.md +43 -0
  30. package/docs/guide/markdown/containers.md +110 -0
  31. package/docs/guide/markdown/header-anchors.md +34 -0
  32. package/docs/guide/markdown/includes.md +84 -0
  33. package/docs/guide/markdown/index.md +20 -0
  34. package/docs/guide/markdown/inline-attributes.md +21 -0
  35. package/docs/guide/markdown/links-and-toc.md +64 -0
  36. package/docs/guide/markdown/math.md +54 -0
  37. package/docs/guide/markdown/syntax-highlighting.md +75 -0
  38. package/docs/guide/routing.md +150 -0
  39. package/docs/guide/using-svelte.md +88 -0
  40. package/docs/guide/versioning.md +281 -0
  41. package/docs/incompatibilities.md +48 -0
  42. package/docs/index.md +43 -0
  43. package/docs/reference/badge.md +100 -0
  44. package/docs/reference/carbon-ads.md +46 -0
  45. package/docs/reference/code-group.md +126 -0
  46. package/docs/reference/home-page.md +232 -0
  47. package/docs/reference/index.md +18 -0
  48. package/docs/reference/markdowndocs.md +275 -0
  49. package/docs/reference/outline.md +79 -0
  50. package/docs/reference/search.md +263 -0
  51. package/docs/reference/steps.md +200 -0
  52. package/docs/reference/team-page.md +189 -0
  53. package/docs/reference/theme.md +150 -0
  54. package/fakeDocsGenerator/generate_docs.js +310 -0
  55. package/package.json +92 -0
  56. package/scripts/build-versions.js +609 -0
  57. package/scripts/generate-static.js +79 -0
  58. package/scripts/render-markdown.js +420 -0
  59. package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
  60. package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
  61. package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
  62. package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
  63. package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
  64. package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
  65. package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
  66. package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
  67. package/src/lib/MarkdownDocs/Outline.svelte +238 -0
  68. package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
  69. package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
  70. package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
  71. package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
  72. package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
  73. package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
  74. package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
  75. package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
  76. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
  77. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
  78. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
  79. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
  80. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
  81. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
  82. package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
  83. package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
  84. package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
  85. package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
  86. package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
  87. package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
  88. package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
  89. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
  90. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
  91. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
  92. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
  93. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
  94. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
  95. package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
  96. package/src/lib/MarkdownDocs/ai/characters.js +52 -0
  97. package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
  98. package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
  99. package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
  100. package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
  101. package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
  102. package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
  103. package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
  104. package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
  105. package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
  106. package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
  107. package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
  108. package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
  109. package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
  110. package/src/lib/MarkdownDocs/ai/types.ts +71 -0
  111. package/src/lib/MarkdownDocs/aiServer.js +288 -0
  112. package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
  113. package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
  114. package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
  115. package/src/lib/MarkdownDocs/common.ts +47 -0
  116. package/src/lib/MarkdownDocs/docsUtils.js +281 -0
  117. package/src/lib/MarkdownDocs/index.plugins.js +22 -0
  118. package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
  119. package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
  120. package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
  121. package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
  122. package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
  123. package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
  124. package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
  125. package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
  126. package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
  127. package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
  128. package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
  129. package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
  130. package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
  131. package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
  132. package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
  133. package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
  134. package/src/lib/MarkdownDocs/remarkImports.js +461 -0
  135. package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
  136. package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
  137. package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
  138. package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
  139. package/src/lib/MarkdownDocs/searchServer.js +263 -0
  140. package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
  141. package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
  142. package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
  143. package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
  144. package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
  145. package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
  146. package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
  147. package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
  148. package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
  149. package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
  150. package/src/lib/components/Badge.svelte +59 -0
  151. package/src/lib/components/Button.svelte +138 -0
  152. package/src/lib/components/CarbonAds.svelte +99 -0
  153. package/src/lib/components/CodeGroup.svelte +102 -0
  154. package/src/lib/components/Feature.svelte +209 -0
  155. package/src/lib/components/Features.svelte +123 -0
  156. package/src/lib/components/Hero.svelte +399 -0
  157. package/src/lib/components/Image.svelte +128 -0
  158. package/src/lib/components/Link.svelte +105 -0
  159. package/src/lib/components/SocialLink.svelte +84 -0
  160. package/src/lib/components/SocialLinks.svelte +33 -0
  161. package/src/lib/components/Steps.svelte +143 -0
  162. package/src/lib/components/TeamMember.svelte +273 -0
  163. package/src/lib/components/TeamMembers.svelte +81 -0
  164. package/src/lib/components/TeamPage.svelte +65 -0
  165. package/src/lib/components/TeamPageSection.svelte +108 -0
  166. package/src/lib/components/TeamPageTitle.svelte +89 -0
  167. package/src/lib/components/index.js +24 -0
  168. package/src/lib/portal/context.js +12 -0
  169. package/src/lib/portal/index.js +3 -0
  170. package/src/lib/portal/portal.svelte +14 -0
  171. package/src/lib/portal/slot.svelte +8 -0
  172. package/src/lib/scss/__code.scss +128 -0
  173. package/src/lib/scss/__containers.scss +99 -0
  174. package/src/lib/scss/__markdown.scss +447 -0
  175. package/src/lib/scss/__scrollbar.scss +60 -0
  176. package/src/lib/scss/__steps.scss +100 -0
  177. package/src/lib/scss/__theme.scss +238 -0
  178. package/src/lib/scss/__toc.scss +55 -0
  179. package/src/lib/scss/__utilities.scss +7 -0
  180. package/src/lib/scss/greg.scss +9 -0
  181. package/src/lib/spinner/spinner.svelte +42 -0
  182. package/svelte.config.js +146 -0
  183. package/types/index.d.ts +456 -0
@@ -0,0 +1,682 @@
1
+ type TopNavItem = {
2
+ text: string;
3
+ link?: string;
4
+ target?: string;
5
+ items?: TopNavItem[];
6
+ };
7
+
8
+ type ThemeableImage =
9
+ | string
10
+ | { src: string; alt?: string }
11
+ | { light: string; dark: string; alt?: string };
12
+
13
+ type SocialLinkItem = {
14
+ icon: string | { svg: string };
15
+ link: string;
16
+ ariaLabel?: string;
17
+ };
18
+
19
+ type OutlineLevel = false | number | [number, number] | "deep";
20
+ type OutlineOption = OutlineLevel | { level?: OutlineLevel; label?: string };
21
+
22
+ type BadgeSpec = string | { text: string; type?: string };
23
+
24
+ type SidebarItem = {
25
+ text: string;
26
+ link?: string;
27
+ items?: SidebarItem[];
28
+ auto?: string;
29
+ badge?: BadgeSpec;
30
+ };
31
+
32
+ export type LocaleThemeConfig = {
33
+ nav?: TopNavItem[];
34
+ sidebar?: "auto" | SidebarItem[];
35
+ outline?: OutlineOption | boolean;
36
+ lastUpdatedText?: string;
37
+ langMenuLabel?: string;
38
+ sidebarMenuLabel?: string;
39
+ skipToContentLabel?: string;
40
+ returnToTopLabel?: string;
41
+ darkModeSwitchLabel?: string;
42
+ lightModeSwitchTitle?: string;
43
+ darkModeSwitchTitle?: string;
44
+ docFooter?: {
45
+ prev?: string | false;
46
+ next?: string | false;
47
+ };
48
+ siteTitle?: string | false;
49
+ logo?: ThemeableImage;
50
+ socialLinks?: SocialLinkItem[];
51
+ editLink?: {
52
+ pattern: string;
53
+ text?: string;
54
+ };
55
+ footer?: {
56
+ message?: string;
57
+ copyright?: string;
58
+ };
59
+ aside?: boolean | "left";
60
+ lastUpdated?: {
61
+ text?: string;
62
+ formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean };
63
+ };
64
+ externalLinkIcon?: boolean;
65
+ search?: {
66
+ locales?: Record<
67
+ string,
68
+ {
69
+ button?: {
70
+ buttonText?: string;
71
+ buttonAriaLabel?: string;
72
+ };
73
+ modal?: {
74
+ displayDetails?: string;
75
+ resetButtonTitle?: string;
76
+ backButtonTitle?: string;
77
+ noResultsText?: string;
78
+ footer?: {
79
+ selectText?: string;
80
+ navigateText?: string;
81
+ closeText?: string;
82
+ };
83
+ searchBox?: {
84
+ placeholder?: string;
85
+ resetButtonTitle?: string;
86
+ resetButtonAriaLabel?: string;
87
+ cancelButtonText?: string;
88
+ cancelButtonAriaLabel?: string;
89
+ };
90
+ startScreen?: {
91
+ recentSearchesTitle?: string;
92
+ noRecentSearchesText?: string;
93
+ saveRecentSearchButtonTitle?: string;
94
+ removeRecentSearchButtonTitle?: string;
95
+ favoriteSearchesTitle?: string;
96
+ removeFavoriteSearchButtonTitle?: string;
97
+ };
98
+ errorScreen?: {
99
+ titleText?: string;
100
+ helpText?: string;
101
+ };
102
+ loadingScreen?: {
103
+ loadingText?: string;
104
+ };
105
+ ai?: {
106
+ tabLabel?: string;
107
+ placeholder?: string;
108
+ startText?: string;
109
+ loadingText?: string;
110
+ errorText?: string;
111
+ sourcesLabel?: string;
112
+ };
113
+ };
114
+ }
115
+ >;
116
+ };
117
+ };
118
+
119
+ export type LocaleConfig = {
120
+ lang?: string;
121
+ dir?: "ltr" | "rtl";
122
+ title?: string;
123
+ description?: string;
124
+ label?: string;
125
+ link?: string;
126
+ themeConfig?: LocaleThemeConfig;
127
+ [key: string]: unknown;
128
+ };
129
+
130
+ export type LocaleEntry = {
131
+ key: string;
132
+ segment: string;
133
+ srcDir: string;
134
+ config: LocaleConfig;
135
+ };
136
+
137
+ type ResolveDefaults = {
138
+ mainTitle: string;
139
+ nav: TopNavItem[];
140
+ sidebar: "auto" | SidebarItem[];
141
+ outline: OutlineOption | boolean;
142
+ langMenuLabel?: string;
143
+ sidebarMenuLabel?: string;
144
+ skipToContentLabel?: string;
145
+ returnToTopLabel?: string;
146
+ darkModeSwitchLabel?: string;
147
+ lightModeSwitchTitle?: string;
148
+ darkModeSwitchTitle?: string;
149
+ docFooter?: {
150
+ prev?: string | false;
151
+ next?: string | false;
152
+ };
153
+ siteTitle?: string | false;
154
+ logo?: ThemeableImage;
155
+ socialLinks?: SocialLinkItem[];
156
+ editLink?: {
157
+ pattern: string;
158
+ text?: string;
159
+ };
160
+ footer?: {
161
+ message?: string;
162
+ copyright?: string;
163
+ };
164
+ aside?: boolean | "left";
165
+ lastUpdated?: {
166
+ text?: string;
167
+ formatOptions?: Intl.DateTimeFormatOptions & { forceLocale?: boolean };
168
+ };
169
+ externalLinkIcon?: boolean;
170
+ searchButtonLabel?: string;
171
+ searchModalLabel?: string;
172
+ searchPlaceholder?: string;
173
+ searchLoadingText?: string;
174
+ searchErrorText?: string;
175
+ searchSearchingText?: string;
176
+ searchNoResultsText?: string;
177
+ searchStartText?: string;
178
+ searchResultsAriaLabel?: string;
179
+ searchNavigateText?: string;
180
+ searchSelectText?: string;
181
+ searchCloseText?: string;
182
+ aiTabLabel?: string;
183
+ aiPlaceholder?: string;
184
+ aiLoadingText?: string;
185
+ aiErrorText?: string;
186
+ aiStartText?: string;
187
+ aiSourcesLabel?: string;
188
+ aiClearChatLabel?: string;
189
+ aiSendLabel?: string;
190
+ };
191
+
192
+ const EXTERNAL_LINK_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
193
+
194
+ // ── Built-in locale strings ──────────────────────────────────────────────────
195
+ // Provides default UI translations per language tag so users don't need to
196
+ // repeat common strings in their greg.config.js.
197
+ // Keys: lowercase BCP-47 language tag (full e.g. "en-us" or base e.g. "en").
198
+ // Lookup order: full tag → base tag → empty (falls through to defaults).
199
+ //
200
+ // Structure mirrors LocaleThemeConfig. The `search` key holds the per-locale
201
+ // search config (button + modal) without the `locales[key]` wrapping — that
202
+ // wrapping is injected dynamically in getBuiltInThemeConfig() using the actual
203
+ // locale key, so the data stays key-agnostic.
204
+
205
+ type SearchLocaleModal = {
206
+ noResultsText?: string;
207
+ footer?: {
208
+ selectText?: string;
209
+ navigateText?: string;
210
+ closeText?: string;
211
+ };
212
+ searchBox?: { placeholder?: string };
213
+ startScreen?: { noRecentSearchesText?: string };
214
+ errorScreen?: { titleText?: string };
215
+ loadingScreen?: { loadingText?: string };
216
+ /** Greg-specific: "Searching…" status text. */
217
+ searchingText?: string;
218
+ /** Greg-specific: aria-label for the results list. */
219
+ resultsAriaLabel?: string;
220
+ ai?: {
221
+ tabLabel?: string;
222
+ placeholder?: string;
223
+ startText?: string;
224
+ loadingText?: string;
225
+ errorText?: string;
226
+ sourcesLabel?: string;
227
+ clearChatLabel?: string;
228
+ sendLabel?: string;
229
+ };
230
+ };
231
+
232
+ type SearchLocaleEntry = {
233
+ button?: { buttonText?: string; buttonAriaLabel?: string };
234
+ modal?: SearchLocaleModal;
235
+ };
236
+
237
+ /** LocaleThemeConfig shape, but with `search` stored one level shallower
238
+ * (the locale key is unknown at definition time and injected on retrieval). */
239
+ type BuiltInLocaleThemeConfig = Omit<LocaleThemeConfig, "search"> & {
240
+ search?: SearchLocaleEntry;
241
+ };
242
+
243
+ const BUILT_IN_LOCALE_STRINGS: Record<string, BuiltInLocaleThemeConfig> = {
244
+ en: {
245
+ langMenuLabel: "Change language",
246
+ sidebarMenuLabel: "Menu",
247
+ skipToContentLabel: "Skip to content",
248
+ returnToTopLabel: "Back to top",
249
+ darkModeSwitchLabel: "Appearance",
250
+ lightModeSwitchTitle: "Switch to light theme",
251
+ darkModeSwitchTitle: "Switch to dark theme",
252
+ lastUpdatedText: "Last updated:",
253
+ docFooter: { prev: "Previous page", next: "Next page" },
254
+ search: {
255
+ button: { buttonText: "Search...", buttonAriaLabel: "Search" },
256
+ modal: {
257
+ searchBox: { placeholder: "Search docs..." },
258
+ loadingScreen: { loadingText: "Loading index..." },
259
+ errorScreen: { titleText: "Failed to load search index." },
260
+ noResultsText: "No results for",
261
+ startScreen: { noRecentSearchesText: "Start typing to search across all documentation." },
262
+ footer: { navigateText: "navigate", selectText: "open", closeText: "close" },
263
+ searchingText: "Searching...",
264
+ resultsAriaLabel: "Search results",
265
+ ai: {
266
+ tabLabel: "Ask AI",
267
+ placeholder: "Ask a question about the docs\u2026",
268
+ loadingText: "Thinking\u2026",
269
+ errorText: "Something went wrong. Please try again.",
270
+ startText: "Ask me anything about this documentation. My answers are based exclusively on the docs.",
271
+ sourcesLabel: "Sources",
272
+ clearChatLabel: "Clear chat",
273
+ sendLabel: "Send",
274
+ },
275
+ },
276
+ },
277
+ },
278
+ pl: {
279
+ langMenuLabel: "Zmień język",
280
+ sidebarMenuLabel: "Menu",
281
+ skipToContentLabel: "Przejdź do treści",
282
+ returnToTopLabel: "Wróć do góry",
283
+ darkModeSwitchLabel: "Wygląd",
284
+ lightModeSwitchTitle: "Przełącz na jasny motyw",
285
+ darkModeSwitchTitle: "Przełącz na ciemny motyw",
286
+ lastUpdatedText: "Ostatnia aktualizacja:",
287
+ docFooter: { prev: "Poprzednia strona", next: "Następna strona" },
288
+ search: {
289
+ button: { buttonText: "Szukaj...", buttonAriaLabel: "Wyszukiwarka" },
290
+ modal: {
291
+ searchBox: { placeholder: "Szukaj w dokumentacji..." },
292
+ loadingScreen: { loadingText: "Wczytywanie indeksu..." },
293
+ errorScreen: { titleText: "Nie udało się wczytać indeksu wyszukiwania." },
294
+ noResultsText: "Brak wyników dla",
295
+ startScreen: { noRecentSearchesText: "Zacznij pisać, aby przeszukać całą dokumentację." },
296
+ footer: { navigateText: "nawiguj", selectText: "otwórz", closeText: "zamknij" },
297
+ searchingText: "Szukam...",
298
+ resultsAriaLabel: "Wyniki wyszukiwania",
299
+ ai: {
300
+ tabLabel: "Zapytaj AI",
301
+ placeholder: "Zadaj pytanie o dokumentację\u2026",
302
+ loadingText: "Myślę\u2026",
303
+ errorText: "Coś poszło nie tak. Spróbuj ponownie.",
304
+ startText: "Zapytaj mnie o cokolwiek z tej dokumentacji. Moje odpowiedzi bazują wyłącznie na dokumentacji.",
305
+ sourcesLabel: "Źródła",
306
+ clearChatLabel: "Wyczyść czat",
307
+ sendLabel: "Wyślij",
308
+ },
309
+ },
310
+ },
311
+ },
312
+ };
313
+
314
+ /** Returns built-in locale strings as a proper LocaleThemeConfig, with the
315
+ * search locale entry nested under the given localeKey. */
316
+ function getBuiltInThemeConfig(lang?: string, localeKey = "/"): LocaleThemeConfig {
317
+ if (!lang) return {};
318
+ const normalized = lang.toLowerCase();
319
+ const base = normalized.split("-")[0];
320
+ const data = BUILT_IN_LOCALE_STRINGS[normalized] ?? BUILT_IN_LOCALE_STRINGS[base];
321
+ if (!data) return {};
322
+ const { search, ...rest } = data;
323
+ if (!search) return rest;
324
+ return { ...rest, search: { locales: { [localeKey]: search } } };
325
+ }
326
+
327
+ /** Deep-merges `override` into `base` (explicit values win). Arrays are
328
+ * replaced wholesale; plain objects are merged recursively. */
329
+ function deepMerge<T>(base: T, override: T): T {
330
+ if (override === undefined || override === null) return base;
331
+ if (base === undefined || base === null) return override;
332
+ if (typeof override !== "object" || typeof base !== "object") return override;
333
+ if (Array.isArray(override) || Array.isArray(base)) {
334
+ return (Array.isArray(override) ? override : base) as T;
335
+ }
336
+ const result: Record<string, unknown> = { ...(base as Record<string, unknown>) };
337
+ for (const key of Object.keys(override as Record<string, unknown>)) {
338
+ const ov = (override as Record<string, unknown>)[key];
339
+ if (ov === undefined) continue;
340
+ result[key] = deepMerge(result[key], ov);
341
+ }
342
+ return result as T;
343
+ }
344
+
345
+ function splitPathAndSuffix(raw: string): { path: string; suffix: string } {
346
+ const value = String(raw || "").trim();
347
+ const hashIndex = value.indexOf("#");
348
+ const queryIndex = value.indexOf("?");
349
+ const firstSuffixIndex =
350
+ hashIndex === -1
351
+ ? queryIndex
352
+ : queryIndex === -1
353
+ ? hashIndex
354
+ : Math.min(hashIndex, queryIndex);
355
+
356
+ if (firstSuffixIndex === -1) {
357
+ return { path: value, suffix: "" };
358
+ }
359
+
360
+ return {
361
+ path: value.slice(0, firstSuffixIndex),
362
+ suffix: value.slice(firstSuffixIndex),
363
+ };
364
+ }
365
+
366
+ function resolveThemeLink(
367
+ rawLink: string,
368
+ args: {
369
+ baseSrcDir: string;
370
+ localeSrcDir: string;
371
+ localeSegment: string;
372
+ },
373
+ ): string {
374
+ const value = String(rawLink || "").trim();
375
+ if (!value) return value;
376
+ if (EXTERNAL_LINK_RE.test(value)) return value;
377
+ if (value.startsWith("#") || value.startsWith("?")) return value;
378
+
379
+ const { path, suffix } = splitPathAndSuffix(value);
380
+ if (!path) return suffix || value;
381
+
382
+ const baseSrcDir = normalizeSrcDir(args.baseSrcDir);
383
+ const localeSrcDir = normalizeSrcDir(args.localeSrcDir);
384
+ const localeSegment = args.localeSegment
385
+ ? normalizeSrcDir(args.localeSegment)
386
+ : "";
387
+
388
+ if (path === "/") return `${localeSrcDir}${suffix}`;
389
+
390
+ if (path.startsWith(baseSrcDir + "/") || path === baseSrcDir) {
391
+ return `${normalizeSrcDir(path)}${suffix}`;
392
+ }
393
+
394
+ if (
395
+ localeSegment &&
396
+ (path === localeSegment || path.startsWith(localeSegment + "/"))
397
+ ) {
398
+ return `${normalizeSrcDir(baseSrcDir + path)}${suffix}`;
399
+ }
400
+
401
+ if (path.startsWith("/")) {
402
+ return `${normalizeSrcDir(localeSrcDir + path)}${suffix}`;
403
+ }
404
+
405
+ return `${normalizeSrcDir(`${localeSrcDir}/${path}`)}${suffix}`;
406
+ }
407
+
408
+ function normalizeTopNavLinks(
409
+ nav: TopNavItem[],
410
+ args: {
411
+ baseSrcDir: string;
412
+ localeSrcDir: string;
413
+ localeSegment: string;
414
+ },
415
+ ): TopNavItem[] {
416
+ return (nav ?? []).map((item) => ({
417
+ ...item,
418
+ link:
419
+ typeof item.link === "string"
420
+ ? resolveThemeLink(item.link, args)
421
+ : item.link,
422
+ items: item.items ? normalizeTopNavLinks(item.items, args) : item.items,
423
+ }));
424
+ }
425
+
426
+ function normalizeSidebarLinks(
427
+ sidebar: SidebarItem[],
428
+ args: {
429
+ baseSrcDir: string;
430
+ localeSrcDir: string;
431
+ localeSegment: string;
432
+ },
433
+ ): SidebarItem[] {
434
+ return (sidebar ?? []).map((item) => ({
435
+ ...item,
436
+ link:
437
+ typeof item.link === "string"
438
+ ? resolveThemeLink(item.link, args)
439
+ : item.link,
440
+ items: item.items
441
+ ? normalizeSidebarLinks(item.items, args)
442
+ : item.items,
443
+ }));
444
+ }
445
+
446
+ function resolveSearchLocaleConfig(
447
+ themeConfig: LocaleThemeConfig,
448
+ localeKey: string,
449
+ ) {
450
+ const localesMap = themeConfig.search?.locales ?? {};
451
+ const normalized = normalizeLocaleKey(localeKey);
452
+ const trimmed = normalized.replace(/^\/+|\/+$/g, "");
453
+ const candidateKeys =
454
+ normalized === "/"
455
+ ? ["/", "root"]
456
+ : [normalized, normalized.replace(/\/$/, ""), trimmed];
457
+
458
+ for (const key of candidateKeys) {
459
+ const value = localesMap[key];
460
+ if (value) return value;
461
+ }
462
+
463
+ return undefined;
464
+ }
465
+
466
+ export function normalizeSrcDir(path: string): string {
467
+ const value = String(path || "").trim();
468
+ if (!value || value === "/") return "/";
469
+ return "/" + value.replace(/^\/+|\/+$/g, "");
470
+ }
471
+
472
+ export function normalizeLocaleKey(key: string): string {
473
+ const value = String(key || "").trim();
474
+ if (!value || value === "root" || value === "/") return "/";
475
+ const withSlashes = `/${value.replace(/^\/+|\/+$/g, "")}/`;
476
+ return withSlashes === "//" ? "/" : withSlashes;
477
+ }
478
+
479
+ export function keyToLocaleSegment(key: string): string {
480
+ const normalized = normalizeLocaleKey(key);
481
+ if (normalized === "/") return "";
482
+ return normalized.replace(/\/$/, "");
483
+ }
484
+
485
+ export function getLocaleEntries(
486
+ baseSrcDir: string,
487
+ locales: Record<string, LocaleConfig>,
488
+ ): LocaleEntry[] {
489
+ const base = normalizeSrcDir(baseSrcDir);
490
+ const rawEntries = Object.entries(locales ?? {});
491
+ if (!rawEntries.length) {
492
+ return [{ key: "/", segment: "", srcDir: base, config: {} }];
493
+ }
494
+
495
+ return rawEntries.map(([key, config]) => {
496
+ const segment = keyToLocaleSegment(key);
497
+ const srcDir = normalizeSrcDir(segment ? `${base}${segment}` : base);
498
+ return { key: normalizeLocaleKey(key), segment, srcDir, config };
499
+ });
500
+ }
501
+
502
+ export function resolveLocaleForPath(
503
+ activePath: string,
504
+ baseSrcDir: string,
505
+ locales: Record<string, LocaleConfig>,
506
+ defaults: ResolveDefaults,
507
+ ) {
508
+ const cleanPath = normalizeSrcDir(activePath || "/");
509
+ const normalizedBaseSrcDir = normalizeSrcDir(baseSrcDir);
510
+ const entries = getLocaleEntries(baseSrcDir, locales);
511
+ const matched =
512
+ [...entries]
513
+ .sort((a, b) => b.segment.length - a.segment.length)
514
+ .find(
515
+ (entry) =>
516
+ cleanPath === entry.srcDir ||
517
+ cleanPath.startsWith(entry.srcDir + "/") ||
518
+ (entry.segment && (
519
+ cleanPath === entry.segment ||
520
+ cleanPath.startsWith(entry.segment + "/")
521
+ )),
522
+ ) ?? entries[0];
523
+
524
+ const themeConfig = matched.config.themeConfig ?? {};
525
+ const normalizedNav = normalizeTopNavLinks(themeConfig.nav ?? defaults.nav, {
526
+ baseSrcDir: normalizedBaseSrcDir,
527
+ localeSrcDir: matched.srcDir,
528
+ localeSegment: matched.segment,
529
+ });
530
+ const normalizedSidebar = Array.isArray(themeConfig.sidebar ?? defaults.sidebar)
531
+ ? normalizeSidebarLinks(
532
+ (themeConfig.sidebar ?? defaults.sidebar) as SidebarItem[],
533
+ {
534
+ baseSrcDir: normalizedBaseSrcDir,
535
+ localeSrcDir: matched.srcDir,
536
+ localeSegment: matched.segment,
537
+ },
538
+ )
539
+ : (themeConfig.sidebar ?? defaults.sidebar);
540
+ // Deep-merge built-in defaults under explicit themeConfig — explicit always wins.
541
+ const builtInThemeConfig = getBuiltInThemeConfig(matched.config.lang, matched.key);
542
+ const mergedThemeConfig = deepMerge(builtInThemeConfig, themeConfig);
543
+ const searchLocale = resolveSearchLocaleConfig(mergedThemeConfig, matched.key);
544
+ // Narrow to SearchLocaleModal so we can access Greg-specific extensions
545
+ // (searchingText, resultsAriaLabel) without type errors.
546
+ const modal = searchLocale?.modal as (SearchLocaleModal | undefined);
547
+ return {
548
+ key: matched.key,
549
+ lang: matched.config.lang,
550
+ dir: matched.config.dir,
551
+ srcDir: matched.srcDir,
552
+ allSrcDirs: entries.map((entry) => entry.srcDir),
553
+ mainTitle: matched.config.title ?? defaults.mainTitle,
554
+ nav: normalizedNav,
555
+ sidebar: normalizedSidebar,
556
+ outline: mergedThemeConfig.outline ?? defaults.outline,
557
+ lastUpdatedText: mergedThemeConfig.lastUpdatedText,
558
+ langMenuLabel: mergedThemeConfig.langMenuLabel ?? defaults.langMenuLabel,
559
+ sidebarMenuLabel: mergedThemeConfig.sidebarMenuLabel ?? defaults.sidebarMenuLabel,
560
+ skipToContentLabel: mergedThemeConfig.skipToContentLabel ?? defaults.skipToContentLabel,
561
+ returnToTopLabel: mergedThemeConfig.returnToTopLabel ?? defaults.returnToTopLabel,
562
+ darkModeSwitchLabel: mergedThemeConfig.darkModeSwitchLabel ?? defaults.darkModeSwitchLabel,
563
+ lightModeSwitchTitle: mergedThemeConfig.lightModeSwitchTitle ?? defaults.lightModeSwitchTitle,
564
+ darkModeSwitchTitle: mergedThemeConfig.darkModeSwitchTitle ?? defaults.darkModeSwitchTitle,
565
+ docFooter: mergedThemeConfig.docFooter ?? defaults.docFooter,
566
+ siteTitle: mergedThemeConfig.siteTitle ?? defaults.siteTitle,
567
+ logo: mergedThemeConfig.logo ?? defaults.logo,
568
+ socialLinks: mergedThemeConfig.socialLinks ?? defaults.socialLinks,
569
+ editLink: mergedThemeConfig.editLink ?? defaults.editLink,
570
+ footer: mergedThemeConfig.footer ?? defaults.footer,
571
+ aside: mergedThemeConfig.aside ?? defaults.aside,
572
+ lastUpdated: mergedThemeConfig.lastUpdated ?? defaults.lastUpdated,
573
+ externalLinkIcon: mergedThemeConfig.externalLinkIcon ?? defaults.externalLinkIcon,
574
+ searchButtonLabel: searchLocale?.button?.buttonText ?? defaults.searchButtonLabel,
575
+ searchModalLabel: searchLocale?.button?.buttonAriaLabel ?? defaults.searchModalLabel,
576
+ searchPlaceholder: modal?.searchBox?.placeholder ?? defaults.searchPlaceholder,
577
+ searchLoadingText: modal?.loadingScreen?.loadingText ?? defaults.searchLoadingText,
578
+ searchErrorText: modal?.errorScreen?.titleText ?? defaults.searchErrorText,
579
+ searchSearchingText: modal?.searchingText ?? defaults.searchSearchingText,
580
+ searchNoResultsText: modal?.noResultsText ?? defaults.searchNoResultsText,
581
+ searchStartText: modal?.startScreen?.noRecentSearchesText ?? defaults.searchStartText,
582
+ searchResultsAriaLabel: modal?.resultsAriaLabel ?? defaults.searchResultsAriaLabel,
583
+ searchNavigateText: modal?.footer?.navigateText ?? defaults.searchNavigateText,
584
+ searchSelectText: modal?.footer?.selectText ?? defaults.searchSelectText,
585
+ searchCloseText: modal?.footer?.closeText ?? defaults.searchCloseText,
586
+ aiTabLabel: modal?.ai?.tabLabel ?? defaults.aiTabLabel,
587
+ aiPlaceholder: modal?.ai?.placeholder ?? defaults.aiPlaceholder,
588
+ aiLoadingText: modal?.ai?.loadingText ?? defaults.aiLoadingText,
589
+ aiErrorText: modal?.ai?.errorText ?? defaults.aiErrorText,
590
+ aiStartText: modal?.ai?.startText ?? defaults.aiStartText,
591
+ aiSourcesLabel: modal?.ai?.sourcesLabel ?? defaults.aiSourcesLabel,
592
+ aiClearChatLabel: modal?.ai?.clearChatLabel ?? defaults.aiClearChatLabel,
593
+ aiSendLabel: modal?.ai?.sendLabel ?? defaults.aiSendLabel,
594
+ entries,
595
+ };
596
+ }
597
+
598
+ function hasMarkdownForPath(
599
+ frontmatters: Record<string, unknown>,
600
+ srcDir: string,
601
+ routePath: string,
602
+ ): boolean {
603
+ const rel = routePath.replace(srcDir, "").replace(/^\//, "");
604
+ const candidates = rel
605
+ ? (srcDir === "/"
606
+ ? [`/${rel}.md`, `/${rel}/index.md`]
607
+ : [`${srcDir}/${rel}.md`, `${srcDir}/${rel}/index.md`])
608
+ : (srcDir === "/"
609
+ ? [`/index.md`]
610
+ : [`${srcDir}/index.md`, `${srcDir}index.md`]);
611
+ return candidates.some((candidate) => candidate in frontmatters);
612
+ }
613
+
614
+ function getLocaleLabel(entry: LocaleEntry): string {
615
+ if (entry.config.label) return entry.config.label;
616
+ if (entry.config.lang) return entry.config.lang;
617
+ if (entry.key === "/") return "Default";
618
+ return entry.key.replace(/^\/+|\/+$/g, "").toUpperCase();
619
+ }
620
+
621
+ export function getLocaleSwitchItems(args: {
622
+ entries: LocaleEntry[];
623
+ activePath: string;
624
+ activeSrcDir: string;
625
+ activeLocaleKey: string;
626
+ frontmatters: Record<string, unknown>;
627
+ preservePath?: boolean;
628
+ }) {
629
+ const {
630
+ entries,
631
+ activePath,
632
+ activeSrcDir,
633
+ activeLocaleKey,
634
+ frontmatters,
635
+ preservePath = true,
636
+ } = args;
637
+ if (entries.length <= 1) return [];
638
+
639
+ const EXTERNAL_LINK_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
640
+
641
+ const relPath =
642
+ preservePath &&
643
+ (activePath === activeSrcDir ||
644
+ (activeSrcDir === "/"
645
+ ? activePath.startsWith("/")
646
+ : activePath.startsWith(activeSrcDir + "/")))
647
+ ? (activeSrcDir === "/"
648
+ ? (activePath === "/" ? "" : activePath)
649
+ : activePath.slice(activeSrcDir.length))
650
+ : "";
651
+
652
+ return entries.map((entry) => {
653
+ const mappedPath = normalizeSrcDir(entry.srcDir + relPath);
654
+ const localeLink =
655
+ typeof entry.config.link === "string"
656
+ ? entry.config.link.trim()
657
+ : "";
658
+
659
+ let link: string = localeLink
660
+ ? EXTERNAL_LINK_RE.test(localeLink)
661
+ ? localeLink
662
+ : normalizeSrcDir(localeLink)
663
+ : hasMarkdownForPath(frontmatters, entry.srcDir, mappedPath)
664
+ ? mappedPath
665
+ : entry.srcDir;
666
+ // For non-root locales without an explicit link, strip the docsBase prefix
667
+ // so the language switcher navigates to the locale-segment root (e.g. '/pl')
668
+ // rather than the docsBase-prefixed path (e.g. '/documentation/pl').
669
+ if (!localeLink && entry.segment) {
670
+ const base = entry.srcDir.slice(0, entry.srcDir.length - entry.segment.length);
671
+ if (base && (link === base || link.startsWith(base + "/"))) {
672
+ link = link.slice(base.length) || "/";
673
+ }
674
+ }
675
+ return {
676
+ key: entry.key,
677
+ label: getLocaleLabel(entry),
678
+ link,
679
+ active: entry.key === activeLocaleKey,
680
+ };
681
+ });
682
+ }