docs-i18n 0.6.3 → 0.7.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.
Files changed (169) hide show
  1. package/{src/admin/ui → admin/app}/components/JobDialog.tsx +21 -2
  2. package/{src/admin/ui → admin/app}/components/JobPanel.tsx +1 -1
  3. package/{src/admin/ui → admin/app}/components/Preview.tsx +2 -5
  4. package/{src/admin/ui → admin/app}/lib/api.ts +18 -39
  5. package/admin/app/routeTree.gen.ts +68 -0
  6. package/admin/app/router.tsx +23 -0
  7. package/admin/app/routes/__root.tsx +55 -0
  8. package/admin/app/routes/index.tsx +416 -0
  9. package/{src/admin/ui → admin/app}/styles.css +36 -3
  10. package/admin/package.json +27 -0
  11. package/admin/server/functions/jobs.ts +53 -0
  12. package/admin/server/functions/misc.ts +84 -0
  13. package/{src/admin/server/routes → admin/server/functions}/models.ts +16 -29
  14. package/admin/server/functions/status.ts +61 -0
  15. package/admin/server/index.ts +35 -0
  16. package/admin/server/init.ts +46 -0
  17. package/{src/admin → admin}/server/services/job-manager.ts +39 -10
  18. package/{src/admin → admin}/server/services/status.ts +6 -6
  19. package/admin/tsconfig.json +19 -0
  20. package/{src/admin → admin}/vite.config.ts +8 -2
  21. package/dist/{assemble-7H4QCW35.js → assemble-CP2BRYQJ.js} +6 -4
  22. package/dist/{chunk-A3YQNPKZ.js → chunk-CLYUAWZE.js} +1 -1
  23. package/dist/{chunk-YN4VJHCQ.js → chunk-JHBSHTXC.js} +1 -1
  24. package/dist/chunk-L64GJ4OB.js +32 -0
  25. package/dist/{chunk-SKKZIV3L.js → chunk-PNKVD2UK.js} +1 -29
  26. package/dist/{chunk-XEOYZUHS.js → chunk-QKIR7RKQ.js} +4 -31
  27. package/dist/chunk-TRURQFP4.js +31 -0
  28. package/dist/cli.js +108 -7
  29. package/dist/index.d.ts +41 -1
  30. package/dist/index.js +92 -3
  31. package/dist/{rescan-O5D3CYC2.js → rescan-HXMWFAOC.js} +5 -3
  32. package/dist/{status-F4MYIAAY.js → status-AGZDXOTZ.js} +4 -2
  33. package/dist/{translate-ZIVKNAC4.js → translate-A5X6MX4Y.js} +14 -7
  34. package/dist/upload-XL6KG6S2.js +132 -0
  35. package/package.json +17 -15
  36. package/template/app/components/BlogArticle.tsx +159 -0
  37. package/template/app/components/BlogList.tsx +88 -0
  38. package/template/app/components/Breadcrumbs.tsx +81 -0
  39. package/template/app/components/Card.tsx +31 -0
  40. package/template/app/components/Doc.tsx +191 -0
  41. package/template/app/components/DocBreadcrumb.tsx +60 -0
  42. package/template/app/components/DocContainer.tsx +13 -0
  43. package/template/app/components/DocTitle.tsx +11 -0
  44. package/template/app/components/DocsLayout.tsx +715 -0
  45. package/template/app/components/Dropdown.tsx +116 -0
  46. package/template/app/components/FallbackBanner.tsx +36 -0
  47. package/template/app/components/Footer.tsx +29 -0
  48. package/template/app/components/FrameworkSelect.tsx +150 -0
  49. package/template/app/components/LibraryCard.tsx +178 -0
  50. package/template/app/components/LocaleSwitcher.tsx +43 -0
  51. package/template/app/components/Navbar.tsx +430 -0
  52. package/template/app/components/PostNotFound.tsx +20 -0
  53. package/template/app/components/SearchButton.tsx +32 -0
  54. package/template/app/components/Select.tsx +103 -0
  55. package/template/app/components/Spinner.tsx +18 -0
  56. package/template/app/components/ThemeProvider.tsx +141 -0
  57. package/template/app/components/ThemeToggle.tsx +31 -0
  58. package/template/app/components/Toc.tsx +86 -0
  59. package/template/app/components/VersionSelect.tsx +118 -0
  60. package/template/app/components/icons/BSkyIcon.tsx +27 -0
  61. package/template/app/components/icons/BaseballCapIcon.tsx +25 -0
  62. package/template/app/components/icons/BrandXIcon.tsx +28 -0
  63. package/template/app/components/icons/CheckCircleIcon.tsx +28 -0
  64. package/template/app/components/icons/CogsIcon.tsx +25 -0
  65. package/template/app/components/icons/DiscordIcon.tsx +24 -0
  66. package/template/app/components/icons/GithubIcon.tsx +24 -0
  67. package/template/app/components/icons/GoogleIcon.tsx +24 -0
  68. package/template/app/components/icons/InstagramIcon.tsx +24 -0
  69. package/template/app/components/icons/NpmIcon.tsx +26 -0
  70. package/template/app/components/icons/YinYangIcon.tsx +26 -0
  71. package/template/app/components/icons/YouTubeIcon.tsx +24 -0
  72. package/template/app/components/markdown/CodeBlock.tsx +254 -0
  73. package/template/app/components/markdown/FileTabs.tsx +58 -0
  74. package/template/app/components/markdown/FrameworkContent.tsx +76 -0
  75. package/template/app/components/markdown/Markdown.tsx +216 -0
  76. package/template/app/components/markdown/MarkdownContent.tsx +89 -0
  77. package/template/app/components/markdown/MarkdownFrameworkHandler.tsx +66 -0
  78. package/template/app/components/markdown/MarkdownHeadingContext.tsx +35 -0
  79. package/template/app/components/markdown/MarkdownLink.tsx +46 -0
  80. package/template/app/components/markdown/MarkdownTabsHandler.tsx +109 -0
  81. package/template/app/components/markdown/PackageManagerTabs.tsx +95 -0
  82. package/template/app/components/markdown/Tabs.tsx +139 -0
  83. package/template/app/components/markdown/index.ts +15 -0
  84. package/template/app/components/ui/Button.tsx +141 -0
  85. package/template/app/components/ui/InlineCode.tsx +16 -0
  86. package/template/app/components/ui/MarkdownImg.tsx +21 -0
  87. package/template/app/config/frameworks.ts +93 -0
  88. package/template/app/contexts/SearchContext.tsx +36 -0
  89. package/template/app/db/index.ts +17 -0
  90. package/template/app/db/schema.ts +74 -0
  91. package/template/app/hooks/useClickOutside.ts +106 -0
  92. package/template/app/routeTree.gen.ts +584 -0
  93. package/template/app/router.tsx +29 -0
  94. package/template/app/routes/$lang.$project.$version.docs.$.tsx +128 -0
  95. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +106 -0
  96. package/template/app/routes/$lang.$project.$version.docs.framework.$framework.index.tsx +27 -0
  97. package/template/app/routes/$lang.$project.$version.docs.framework.index.tsx +44 -0
  98. package/template/app/routes/$lang.$project.$version.docs.index.tsx +27 -0
  99. package/template/app/routes/$lang.$project.$version.docs.tsx +70 -0
  100. package/template/app/routes/$lang.$project.$version.tsx +69 -0
  101. package/template/app/routes/$lang.$project.docs.$.tsx +104 -0
  102. package/template/app/routes/$lang.$project.docs.index.tsx +20 -0
  103. package/template/app/routes/$lang.$project.docs.tsx +79 -0
  104. package/template/app/routes/$lang.$project.tsx +89 -0
  105. package/template/app/routes/$lang.blog.$.tsx +82 -0
  106. package/template/app/routes/$lang.blog.index.tsx +56 -0
  107. package/template/app/routes/$lang.blog.tsx +26 -0
  108. package/template/app/routes/$lang.docs.$.tsx +100 -0
  109. package/template/app/routes/$lang.docs.framework.$framework.$.tsx +104 -0
  110. package/template/app/routes/$lang.docs.framework.$framework.index.tsx +32 -0
  111. package/template/app/routes/$lang.docs.framework.index.tsx +47 -0
  112. package/template/app/routes/$lang.docs.index.tsx +20 -0
  113. package/template/app/routes/$lang.docs.tsx +90 -0
  114. package/template/app/routes/$lang.tsx +16 -0
  115. package/template/app/routes/__root.tsx +180 -0
  116. package/template/app/routes/index.tsx +89 -0
  117. package/template/app/site.config.ts +182 -0
  118. package/template/app/styles/app.css +1029 -0
  119. package/template/app/types/index.ts +77 -0
  120. package/template/app/utils/blog.server.ts +193 -0
  121. package/template/app/utils/blog.ts +42 -0
  122. package/template/app/utils/config.ts +120 -0
  123. package/template/app/utils/content-loader.ts +400 -0
  124. package/template/app/utils/dates.ts +29 -0
  125. package/template/app/utils/docs.server.ts +150 -0
  126. package/template/app/utils/markdown/filterFrameworkContent.ts +233 -0
  127. package/template/app/utils/markdown/index.ts +2 -0
  128. package/template/app/utils/markdown/installCommand.ts +143 -0
  129. package/template/app/utils/markdown/plugins/collectHeadings.ts +104 -0
  130. package/template/app/utils/markdown/plugins/extractCodeMeta.ts +57 -0
  131. package/template/app/utils/markdown/plugins/helpers.ts +33 -0
  132. package/template/app/utils/markdown/plugins/index.ts +8 -0
  133. package/template/app/utils/markdown/plugins/parseCommentComponents.ts +103 -0
  134. package/template/app/utils/markdown/plugins/transformCommentComponents.ts +23 -0
  135. package/template/app/utils/markdown/plugins/transformFrameworkComponent.ts +217 -0
  136. package/template/app/utils/markdown/plugins/transformTabsComponent.ts +359 -0
  137. package/template/app/utils/markdown/processor.ts +75 -0
  138. package/template/app/utils/site-config.tsx +11 -0
  139. package/template/app/utils/upload.ts +232 -0
  140. package/template/app/utils/useLocalStorage.ts +65 -0
  141. package/template/app/utils/utils.ts +23 -0
  142. package/template/package.json +54 -0
  143. package/template/public/favicon.svg +1 -0
  144. package/template/public/fonts/Inter-latin-ext.woff2 +0 -0
  145. package/template/public/fonts/Inter-latin.woff2 +0 -0
  146. package/template/public/images/frameworks/angular-logo.svg +1 -0
  147. package/template/public/images/frameworks/js-logo.svg +1 -0
  148. package/template/public/images/frameworks/lit-logo.svg +1 -0
  149. package/template/public/images/frameworks/preact-logo.svg +6 -0
  150. package/template/public/images/frameworks/qwik-logo.svg +1 -0
  151. package/template/public/images/frameworks/react-logo.svg +1 -0
  152. package/template/public/images/frameworks/solid-logo.svg +1 -0
  153. package/template/public/images/frameworks/svelte-logo.svg +1 -0
  154. package/template/public/images/frameworks/vue-logo.svg +4 -0
  155. package/template/tsconfig.json +24 -0
  156. package/template/vite.config.ts +43 -0
  157. package/template/wrangler.jsonc +16 -0
  158. package/README.md +0 -161
  159. package/dist/server-73AVSOL5.js +0 -598
  160. package/src/admin/index.html +0 -13
  161. package/src/admin/server/index.ts +0 -138
  162. package/src/admin/server/routes/jobs.ts +0 -113
  163. package/src/admin/server/routes/status.ts +0 -57
  164. package/src/admin/ui/App.tsx +0 -332
  165. package/src/admin/ui/main.tsx +0 -19
  166. /package/{src/admin/ui → admin/app}/components/FileList.tsx +0 -0
  167. /package/{src/admin/ui → admin/app}/components/LangGrid.tsx +0 -0
  168. /package/{src/admin/ui → admin/app}/components/ProgressBar.tsx +0 -0
  169. /package/{src/admin/ui → admin/app}/lib/flags.ts +0 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Upload content and translation cache from local filesystem to D1.
3
+ *
4
+ * Used by `docs-i18n site upload` CLI command to push content to production.
5
+ *
6
+ * Walks the content/ directory, reading each .md file and upserting it into
7
+ * the D1 `content` table. Also uploads the local SQLite translation cache
8
+ * into the D1 `translations` and `sources` tables.
9
+ */
10
+
11
+ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
12
+ import { resolve, relative, join } from 'node:path'
13
+
14
+ interface UploadOptions {
15
+ /** Absolute path to the project root (where content/ lives). */
16
+ projectRoot: string
17
+ /** Wrangler D1 database name to target. */
18
+ databaseName: string
19
+ /** Optional: only upload specific project. */
20
+ project?: string
21
+ /** Optional: only upload specific version. */
22
+ version?: string
23
+ }
24
+
25
+ interface ContentRow {
26
+ path: string
27
+ body: string
28
+ project: string
29
+ version: string
30
+ lang: string
31
+ updated_at: number
32
+ }
33
+
34
+ /**
35
+ * Walk the content directory and collect all .md files as content rows.
36
+ */
37
+ export function collectContentFiles(projectRoot: string): ContentRow[] {
38
+ const contentDir = resolve(projectRoot, 'content')
39
+ if (!existsSync(contentDir)) {
40
+ throw new Error(`Content directory not found: ${contentDir}`)
41
+ }
42
+
43
+ const rows: ContentRow[] = []
44
+ walkDir(contentDir, contentDir, rows)
45
+ return rows
46
+ }
47
+
48
+ function walkDir(dir: string, contentRoot: string, rows: ContentRow[]) {
49
+ const entries = readdirSync(dir, { withFileTypes: true })
50
+
51
+ for (const entry of entries) {
52
+ const fullPath = join(dir, entry.name)
53
+
54
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
55
+ walkDir(fullPath, contentRoot, rows)
56
+ } else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.json'))) {
57
+ const relativePath = relative(contentRoot, fullPath)
58
+ const body = readFileSync(fullPath, 'utf-8')
59
+
60
+ // Parse path structure: content/{project}/{version}/{lang}/... or content/{lang}/...
61
+ const parts = relativePath.split('/')
62
+ const { project, version, lang } = parseContentPath(parts)
63
+
64
+ rows.push({
65
+ path: relativePath,
66
+ body,
67
+ project,
68
+ version,
69
+ lang,
70
+ updated_at: Math.floor(Date.now() / 1000),
71
+ })
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Parse content path parts to extract project, version, and lang.
78
+ *
79
+ * Supports:
80
+ * - {project}/{version}/{lang}/slug.md
81
+ * - {project}/{lang}/slug.md (no version)
82
+ * - {lang}/slug.md (single project, no version)
83
+ */
84
+ function parseContentPath(parts: string[]): {
85
+ project: string
86
+ version: string
87
+ lang: string
88
+ } {
89
+ // If only 2 parts: lang/file.md -> single project
90
+ if (parts.length === 2) {
91
+ return { project: 'default', version: 'latest', lang: parts[0] }
92
+ }
93
+
94
+ // If 3 parts: project/lang/file.md or lang/subdir/file.md
95
+ if (parts.length === 3) {
96
+ // Heuristic: if first part looks like a lang code (2-5 chars, lowercase), it's lang/subdir/file
97
+ if (isLangCode(parts[0])) {
98
+ return { project: 'default', version: 'latest', lang: parts[0] }
99
+ }
100
+ return { project: parts[0], version: 'latest', lang: parts[1] }
101
+ }
102
+
103
+ // 4+ parts: project/version/lang/...
104
+ if (parts.length >= 4) {
105
+ return { project: parts[0], version: parts[1], lang: parts[2] }
106
+ }
107
+
108
+ return { project: 'default', version: 'latest', lang: 'en' }
109
+ }
110
+
111
+ function isLangCode(s: string): boolean {
112
+ return /^[a-z]{2}(-[a-z]{2,})?$/.test(s)
113
+ }
114
+
115
+ /**
116
+ * Generate SQL statements for batch insert/upsert into D1.
117
+ * Returns an array of SQL strings suitable for wrangler d1 execute.
118
+ */
119
+ export function generateContentSql(rows: ContentRow[]): string[] {
120
+ const statements: string[] = []
121
+
122
+ // Create table if not exists
123
+ statements.push(`
124
+ CREATE TABLE IF NOT EXISTS content (
125
+ path TEXT PRIMARY KEY NOT NULL,
126
+ body TEXT NOT NULL,
127
+ project TEXT NOT NULL,
128
+ version TEXT NOT NULL,
129
+ lang TEXT NOT NULL,
130
+ updated_at INTEGER
131
+ );
132
+ `.trim())
133
+
134
+ // Batch upsert in chunks of 50 to avoid SQL size limits
135
+ const chunkSize = 50
136
+ for (let i = 0; i < rows.length; i += chunkSize) {
137
+ const chunk = rows.slice(i, i + chunkSize)
138
+ for (const row of chunk) {
139
+ const escapedBody = row.body.replace(/'/g, "''")
140
+ const escapedPath = row.path.replace(/'/g, "''")
141
+ statements.push(
142
+ `INSERT OR REPLACE INTO content (path, body, project, version, lang, updated_at) VALUES ('${escapedPath}', '${escapedBody}', '${row.project}', '${row.version}', '${row.lang}', ${row.updated_at});`,
143
+ )
144
+ }
145
+ }
146
+
147
+ return statements
148
+ }
149
+
150
+ /**
151
+ * Collect translation cache entries from local SQLite for upload.
152
+ * Reads from the project's .docs-i18n/translations.db file.
153
+ */
154
+ export function collectTranslations(projectRoot: string): {
155
+ sources: { key: string; text: string; type: string }[]
156
+ translations: { lang: string; key: string; value: string }[]
157
+ } {
158
+ const dbPath = resolve(projectRoot, '.docs-i18n', 'translations.db')
159
+
160
+ if (!existsSync(dbPath)) {
161
+ return { sources: [], translations: [] }
162
+ }
163
+
164
+ // Dynamic import to avoid bundling better-sqlite3 in the template
165
+ // This runs in Node.js CLI context, not in the browser
166
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
167
+ const Database = require('better-sqlite3')
168
+ const db = new Database(dbPath, { readonly: true })
169
+
170
+ try {
171
+ const sources = db
172
+ .prepare('SELECT key, text, type FROM sources')
173
+ .all() as { key: string; text: string; type: string }[]
174
+
175
+ const translations = db
176
+ .prepare('SELECT lang, key, value FROM translations')
177
+ .all() as { lang: string; key: string; value: string }[]
178
+
179
+ return { sources, translations }
180
+ } finally {
181
+ db.close()
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Generate SQL for uploading translation cache to D1.
187
+ */
188
+ export function generateTranslationSql(data: {
189
+ sources: { key: string; text: string; type: string }[]
190
+ translations: { lang: string; key: string; value: string }[]
191
+ }): string[] {
192
+ const statements: string[] = []
193
+
194
+ // Create tables if not exist
195
+ statements.push(`
196
+ CREATE TABLE IF NOT EXISTS sources (
197
+ key TEXT PRIMARY KEY NOT NULL,
198
+ text TEXT NOT NULL,
199
+ type TEXT NOT NULL DEFAULT 'paragraph'
200
+ );
201
+ `.trim())
202
+
203
+ statements.push(`
204
+ CREATE TABLE IF NOT EXISTS translations (
205
+ lang TEXT NOT NULL,
206
+ key TEXT NOT NULL,
207
+ value TEXT NOT NULL,
208
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
209
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
210
+ PRIMARY KEY (lang, key)
211
+ );
212
+ CREATE INDEX IF NOT EXISTS idx_translations_lang ON translations(lang);
213
+ `.trim())
214
+
215
+ // Upsert sources
216
+ for (const s of data.sources) {
217
+ const escapedText = s.text.replace(/'/g, "''")
218
+ statements.push(
219
+ `INSERT OR REPLACE INTO sources (key, text, type) VALUES ('${s.key}', '${escapedText}', '${s.type}');`,
220
+ )
221
+ }
222
+
223
+ // Upsert translations
224
+ for (const t of data.translations) {
225
+ const escapedValue = t.value.replace(/'/g, "''")
226
+ statements.push(
227
+ `INSERT INTO translations (lang, key, value) VALUES ('${t.lang}', '${t.key}', '${escapedValue}') ON CONFLICT(lang, key) DO UPDATE SET value = excluded.value, updated_at = unixepoch();`,
228
+ )
229
+ }
230
+
231
+ return statements
232
+ }
@@ -0,0 +1,65 @@
1
+ import { useState, useEffect } from 'react'
2
+
3
+ function getWithExpiry<T>(key: string): T | undefined {
4
+ if (typeof window === 'undefined') {
5
+ return undefined
6
+ }
7
+
8
+ const itemStr = localStorage.getItem(key)
9
+ if (!itemStr) {
10
+ return undefined
11
+ }
12
+
13
+ try {
14
+ const item: { value: T; ttl: number } = JSON.parse(itemStr)
15
+
16
+ // If there is no TTL set, return the value
17
+ if (!item.ttl) {
18
+ return item.value
19
+ }
20
+
21
+ // If the item is expired, delete the item from storage
22
+ if (new Date().getTime() > item.ttl) {
23
+ localStorage.removeItem(key)
24
+ return undefined
25
+ }
26
+
27
+ return item.value
28
+ } catch {
29
+ // If JSON parsing fails, remove the corrupted item and return undefined
30
+ localStorage.removeItem(key)
31
+ return undefined
32
+ }
33
+ }
34
+
35
+ /**
36
+ * React state that persists to `localStorage` (with optional TTL).
37
+ *
38
+ * - `key`: localStorage key to read/write
39
+ * - `defaultValue`: initial value if no stored value
40
+ * - `ttl` (ms): optional time-to-live; expired values are cleared and ignored
41
+ */
42
+ export function useLocalStorage<T>(
43
+ key: string,
44
+ defaultValue: T,
45
+ ttl?: number,
46
+ ): [T, typeof setValue] {
47
+ const [value, setValue] = useState(defaultValue)
48
+
49
+ useEffect(() => {
50
+ const item = getWithExpiry<T>(key)
51
+ if (item !== undefined) setValue(item)
52
+ }, [key])
53
+
54
+ useEffect(() => {
55
+ localStorage.setItem(
56
+ key,
57
+ JSON.stringify({
58
+ value,
59
+ ttl: ttl ? new Date().getTime() + ttl : null,
60
+ }),
61
+ )
62
+ }, [key, value, ttl])
63
+
64
+ return [value, setValue]
65
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Uppercases the first character of a string and returns the result.
3
+ */
4
+ export function capitalize(str: string) {
5
+ return str.charAt(0).toUpperCase() + str.slice(1)
6
+ }
7
+
8
+ /**
9
+ * Converts a kebab-case slug (eg. "my-example") into a Title Case string.
10
+ */
11
+ export function slugToTitle(str: string) {
12
+ return str
13
+ .split('-')
14
+ .map((word) => capitalize(word))
15
+ .join(' ')
16
+ }
17
+
18
+ /**
19
+ * Returns the last element from an array.
20
+ */
21
+ export function last<T>(arr: T[]) {
22
+ return arr[arr.length - 1]
23
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@docs-i18n/template",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite dev",
7
+ "build": "vite build",
8
+ "start": "vite start"
9
+ },
10
+ "dependencies": {
11
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
12
+ "@shikijs/transformers": "^3.0.0",
13
+ "@tailwindcss/typography": "^0.5.16",
14
+ "@tailwindcss/vite": "^4.1.7",
15
+ "@tanstack/react-router": "^1.120.3",
16
+ "@tanstack/react-query": "^5.0.0",
17
+ "@tanstack/react-start": "^1.120.3",
18
+ "docs-i18n": "workspace:*",
19
+ "drizzle-orm": "^0.38.0",
20
+ "gray-matter": "^4.0.3",
21
+ "hast-util-is-element": "^3.0.0",
22
+ "hast-util-to-string": "^3.0.1",
23
+ "html-react-parser": "^5.1.10",
24
+ "lru-cache": "^11.0.0",
25
+ "lucide-react": "^0.400.0",
26
+ "mermaid": "^11.13.0",
27
+ "react": "^19.1.0",
28
+ "react-dom": "^19.1.0",
29
+ "rehype-autolink-headings": "^7.1.0",
30
+ "rehype-callouts": "^2.0.0",
31
+ "rehype-parse": "^9.0.0",
32
+ "rehype-raw": "^7.0.0",
33
+ "rehype-slug": "^6.0.0",
34
+ "rehype-stringify": "^10.0.1",
35
+ "remark-gfm": "^4.0.1",
36
+ "remark-parse": "^11.0.0",
37
+ "remark-rehype": "^11.1.2",
38
+ "shiki": "^3.0.0",
39
+ "tailwind-merge": "^2.0.0",
40
+ "tailwindcss": "^4.1.7",
41
+ "unified": "^11.0.5",
42
+ "unist-util-visit": "^5.0.0",
43
+ "vite": "^7.0.0",
44
+ "vite-tsconfig-paths": "^5.0.1",
45
+ "zustand": "^5.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.15.0",
49
+ "@types/react": "^19.1.0",
50
+ "@types/react-dom": "^19.1.0",
51
+ "drizzle-kit": "^0.30.0",
52
+ "typescript": "^5.8.0"
53
+ }
54
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#2563eb"/><text x="16" y="23" font-family="sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">D</text></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 223 236" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="49.009" x2="225.829" y1="213.75" y2="129.722"><stop offset="0" stop-color="#e40035"/><stop offset=".24" stop-color="#f60a48"/><stop offset=".352" stop-color="#f20755"/><stop offset=".494" stop-color="#dc087d"/><stop offset=".745" stop-color="#9717e7"/><stop offset="1" stop-color="#6c00f5"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="41.025" x2="156.741" y1="28.344" y2="160.344"><stop offset="0" stop-color="#ff31d9"/><stop offset="1" stop-color="#ff5be1" stop-opacity="0"/></linearGradient><clipPath id="c"><path d="m0 0h223v236h-223z"/></clipPath><g clip-path="url(#c)"><path d="m222.077 39.192-8.019 125.923-76.671-165.115zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301zm-57.933-139.342 30.357 73.803h-60.715zm-103.102 102.44-7.937-125.923 84.69-39.192z" fill="url(#a)"/><path d="m222.077 39.192-8.019 125.923-76.671-165.115zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301zm-57.933-139.342 30.357 73.803h-60.715zm-103.102 102.44-7.937-125.923 84.69-39.192z" fill="url(#b)"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 630 630" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h630v630h-630z" fill="#f7df1e"/><path d="m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" height="200" viewBox="0 0 160 200" width="160" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h160v200h-160z"/></clipPath><g clip-path="url(#a)"><path d="m40 120 20-60 90 90-30 50-40-40h-20" fill="#00e8ff"/><path d="m80 160v-80l40-40v80m-120 40 40 40 20-40-20-40h-20" fill="#283198"/><path d="m40 120v-80l40-40v80m40 120v-80l40-40v80m-160 0v-80l40 40" fill="#324fff"/><path d="m40 200v-80l40 40" fill="#0ff"/></g></svg>
@@ -0,0 +1,6 @@
1
+ <svg width="100%" height="100%" viewBox="-256 -256 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
2
+ <path d="M0,-256 221.7025033688164,-128 221.7025033688164,128 0,256 -221.7025033688164,128 -221.7025033688164,-128z" fill="#673ab8"/>
3
+ <ellipse cx="0" cy="0" stroke-width="16px" rx="75px" ry="196px" fill="none" stroke="white" transform="rotate(52.5)"/>
4
+ <ellipse cx="0" cy="0" stroke-width="16px" rx="75px" ry="196px" fill="none" stroke="white" transform="rotate(-52.5)"/>
5
+ <circle cx="0" cy="0" r="34" fill="white"/>
6
+ </svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" height="50" viewBox="0 0 47 50" width="47" xmlns="http://www.w3.org/2000/svg"><path d="m40.973 49.535-8.8869-8.8366-.1358.0194v-.0969l-18.8992-18.6614 4.6569-4.4959-2.7359-15.69651-12.98107 16.08411c-2.212022 2.2285-2.619497 5.8523-1.028395 8.5265l8.110735 13.4487c1.24183 2.0735 3.49263 3.3137 5.91813 3.2943l4.0166-.0387z" fill="#18b6f6"/><path d="m45.8232 17.5412-1.7852-3.2943-.9314-1.686-.3686-.6588-.0388.0387-4.8898-8.46836c-1.2224-2.13163-3.512-3.4493713-5.9957-3.4299928l-4.2882.1162658-12.7871.038759c-2.4254.019379-4.6374 1.298358-5.85987 3.371858l-7.78087 15.42527 13.91244-17.26622 18.2395 20.03732-3.2405 3.275 1.9404 15.6771.0194-.0387v.0387h-.0388l.0388.0388 1.5135 1.4728 7.354 7.1894c.3105.2906.815-.0582.6015-.4264l-4.5404-8.9334 7.9167-14.6308.2522-.2906c.097-.1163.194-.2326.2717-.3489 1.5523-2.1122 1.7657-4.9415.4851-7.2475z" fill="#ac7ef4"/><path d="m33.3076 21.6882-18.2977-19.9404 2.6001 15.619-4.6569 4.5152 18.9574 18.8165-1.7075-15.619z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="-11.5 -10.23 23 20.46" xmlns="http://www.w3.org/2000/svg"><circle fill="#61dafb" r="2.05"/><g fill="none" stroke="#61dafb"><ellipse rx="11" ry="4.2"/><ellipse rx="11" ry="4.2" transform="matrix(.5 .8660254 -.8660254 .5 0 0)"/><ellipse rx="11" ry="4.2" transform="matrix(-.5 .8660254 -.8660254 -.5 0 0)"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 75.8 70.7" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="11.67" x2="70.61" y1=".04" y2="28.68"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="43.99" x2="33.67" y1="14.07" y2="48.44"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="7.34" x2="66.94" y1="29.06" y2="69.58"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="34.25" x2="10.2" y1="33.91" y2="122.1"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="m75.8 15.4s-24.9-18.8-44.3-14.4l-1.7.5c-2.6.8-5 2.4-6.6 4.6-.3.4-.5.8-.7 1.2l-7.2 12.1 12.3 2.4a22 22 0 0 0 17.6 3.5l22 4.3z" fill="#76b3e1"/><path d="m75.8 15.4s-24.9-18.8-44.3-14.4l-1.7.5c-2.6.8-5 2.4-6.6 4.6-.3.4-.5.8-.7 1.2l-7.2 12.1 12.3 2.4a22 22 0 0 0 17.6 3.5l22 4.3z" fill="url(#a)" opacity=".3"/><path d="m23 15.1-1.7.5c-7.9 2.6-10.6 9.9-6.1 16.4a21.1 21.1 0 0 0 22.5 7.1l29.5-9.6c.1 0-24.8-18.8-44.2-14.4z" fill="#518ac8"/><path d="m23 15.1-1.7.5c-7.9 2.6-10.6 9.9-6.1 16.4a21.1 21.1 0 0 0 22.5 7.1l29.5-9.6c.1 0-24.8-18.8-44.2-14.4z" fill="url(#b)" opacity=".3"/><path d="m61.9 36.4c-5.4-6.8-14.3-9.6-22.6-7.1l-29.5 9.5-9.3 16.5 52.8 9 9.5-16.8c1.9-3.2 1.7-7.3-.9-11.1z" fill="url(#c)"/><path d="m52.6 52.9c-5.4-6.8-14.3-9.6-22.5-7.2l-29.6 9.6s25 18.8 44.3 14.4l1.7-.5c8-2.4 10.7-9.8 6.1-16.3z" fill="url(#d)"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 98.1 118" xmlns="http://www.w3.org/2000/svg"><path d="m91.8 15.6c-10.9-15.7-32.6-20.3-48.2-10.4l-27.5 17.6a31.25 31.25 0 0 0 -14.2 21.1c-1.3 7.3-.2 14.8 3.3 21.3-2.4 3.6-4 7.6-4.7 11.8-1.6 8.9.5 18.1 5.7 25.4 11 15.7 32.6 20.3 48.2 10.4l27.5-17.5c7.5-4.7 12.7-12.4 14.2-21.1 1.3-7.3.2-14.8-3.3-21.3 2.4-3.6 4-7.6 4.7-11.8 1.7-9-.4-18.2-5.7-25.5" fill="#ff3e00"/><path d="m40.9 103.9a21.8 21.8 0 0 1 -23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3l.6-2.6.5-1.6 1.4 1c3.3 2.4 6.9 4.2 10.8 5.4l1 .3-.1 1c-.1 1.4.3 2.9 1.1 4.1a6.62 6.62 0 0 0 8.8 2l27.4-17.5c1.4-.9 2.3-2.2 2.6-3.8s-.1-3.3-1-4.6a6.56 6.56 0 0 0 -8.8-1.9l-10.5 6.7a18.6 18.6 0 0 1 -5.6 2.4 21.8 21.8 0 0 1 -23.4-8.7 20.2 20.2 0 0 1 -3.4-15.3c.9-5.2 4.1-9.9 8.6-12.7l27.5-17.5c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0 1 23.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.2.9-.4 1.7-.7 2.6l-.5 1.6-1.4-1c-3.3-2.4-6.9-4.2-10.8-5.4l-1-.3.1-1c.1-1.4-.3-2.9-1.1-4.1a6.56 6.56 0 0 0 -8.8-1.9l-27.4 17.5c-1.4.9-2.3 2.2-2.6 3.8s.1 3.3 1 4.6a6.56 6.56 0 0 0 8.8 1.9l10.5-6.7c1.7-1.1 3.6-1.9 5.6-2.5a21.8 21.8 0 0 1 23.4 8.7c3.2 4.4 4.4 9.9 3.5 15.3-.9 5.2-4.1 9.9-8.6 12.7l-27.5 17.5c-1.7 1.1-3.6 1.9-5.6 2.5" fill="#fff"/></svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69">
2
+ <path fill="#41b883" d="m161.1 0-30.23 52.35L100.65 0H0l130.87 226.7L261.75 0z"/>
3
+ <path fill="#34495e" d="m161.1 0-30.23 52.35L100.65 0h-48.3l78.52 136.01L209.4.01z"/>
4
+ </svg>
@@ -0,0 +1,24 @@
1
+ {
2
+ "include": ["**/*.ts", "**/*.tsx"],
3
+ "exclude": ["node_modules"],
4
+ "compilerOptions": {
5
+ "strict": true,
6
+ "esModuleInterop": true,
7
+ "jsx": "react-jsx",
8
+ "module": "ESNext",
9
+ "moduleResolution": "Bundler",
10
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
11
+ "isolatedModules": true,
12
+ "resolveJsonModule": true,
13
+ "skipLibCheck": true,
14
+ "target": "ES2022",
15
+ "allowJs": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "~/*": ["./app/*"]
20
+ },
21
+ "types": ["vite/client"],
22
+ "noEmit": true
23
+ }
24
+ }
@@ -0,0 +1,43 @@
1
+ import { resolve } from 'node:path'
2
+ import { existsSync } from 'node:fs'
3
+ import { defineConfig } from 'vite'
4
+ import tsConfigPaths from 'vite-tsconfig-paths'
5
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite'
6
+ import tailwindcss from '@tailwindcss/vite'
7
+
8
+ // When launched via `docs-i18n site`, DOCS_I18N_PROJECT_ROOT points to the
9
+ // consumer project. If that project ships its own `site.config.ts` we alias
10
+ // `~/site.config` so every template import picks it up automatically.
11
+ const projectRoot = process.env.DOCS_I18N_PROJECT_ROOT
12
+ const consumerConfig = projectRoot
13
+ ? resolve(projectRoot, 'site.config.ts')
14
+ : undefined
15
+
16
+ const aliasEntries: Record<string, string> = {}
17
+ if (consumerConfig && existsSync(consumerConfig)) {
18
+ aliasEntries['~/site.config'] = consumerConfig
19
+ }
20
+
21
+ // Deduplicate React — ensure all imports resolve to the template's copy,
22
+ // preventing "Cannot read properties of null (reading 'useContext')" when the
23
+ // consumer project has its own node_modules/react.
24
+ const templateRoot = resolve(import.meta.dirname ?? __dirname, '.')
25
+
26
+ export default defineConfig({
27
+ esbuild: {
28
+ jsx: 'automatic',
29
+ },
30
+ resolve: {
31
+ alias: aliasEntries,
32
+ dedupe: ['react', 'react-dom'],
33
+ },
34
+ plugins: [
35
+ tsConfigPaths({
36
+ projects: ['./tsconfig.json'],
37
+ }),
38
+ tanstackStart({
39
+ srcDirectory: 'app',
40
+ }),
41
+ tailwindcss(),
42
+ ],
43
+ })
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "docs-i18n-site",
3
+ "compatibility_date": "2025-01-01",
4
+ "compatibility_flags": ["nodejs_compat_v2"],
5
+ "main": "dist/server/index.mjs",
6
+ "assets": {
7
+ "directory": "dist/client"
8
+ },
9
+ "d1_databases": [
10
+ {
11
+ "binding": "DB",
12
+ "database_name": "docs-i18n-db",
13
+ "database_id": "placeholder-replace-with-actual-id"
14
+ }
15
+ ]
16
+ }