docs-i18n 0.6.2 → 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 -23
  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
@@ -1,598 +0,0 @@
1
- import {
2
- init_parser,
3
- parseMdx
4
- } from "./chunk-YN4VJHCQ.js";
5
- import {
6
- TranslationCache
7
- } from "./chunk-XEOYZUHS.js";
8
- import {
9
- flattenSources
10
- } from "./chunk-SKKZIV3L.js";
11
-
12
- // src/admin/server/index.ts
13
- import { spawn as nodeSpawn } from "child_process";
14
- import { createServer } from "http";
15
- import { readFileSync as readFileSync2 } from "fs";
16
- import { resolve as resolve2, dirname } from "path";
17
- import { fileURLToPath } from "url";
18
- import { Hono as Hono4 } from "hono";
19
-
20
- // src/admin/server/services/status.ts
21
- import { existsSync, readdirSync, readFileSync } from "fs";
22
- import { join, resolve } from "path";
23
- init_parser();
24
- var _config;
25
- var _cache = null;
26
- function initStatus(config) {
27
- _config = config;
28
- _cache = null;
29
- }
30
- function getVersions() {
31
- return flattenSources(_config).map((s) => s.versionKey);
32
- }
33
- function getLangs() {
34
- return _config.languages;
35
- }
36
- function getCache() {
37
- if (!_cache) {
38
- _cache = new TranslationCache(resolve(_config.cacheDir ?? ".cache"));
39
- }
40
- return _cache;
41
- }
42
- function getSourcePath(versionKey) {
43
- const source = flattenSources(_config).find((s) => s.versionKey === versionKey);
44
- return source ? resolve(source.sourcePath) : null;
45
- }
46
- function walkFiles(dir) {
47
- const results = [];
48
- if (!existsSync(dir)) return results;
49
- const exts = (_config.include ?? ["**/*.mdx", "**/*.md"]).map((p) => p.replace("**/*", ""));
50
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
51
- const fullPath = join(dir, entry.name);
52
- if (entry.isDirectory()) results.push(...walkFiles(fullPath));
53
- else if (exts.some((ext) => entry.name.endsWith(ext))) results.push(fullPath);
54
- }
55
- return results;
56
- }
57
- function ensureScanned(version) {
58
- const cache = getCache();
59
- const enDir = getSourcePath(version);
60
- if (!enDir || !existsSync(enDir)) return;
61
- const files = walkFiles(enDir);
62
- const currentCount = cache.sourceCount(version);
63
- if (currentCount >= files.length) return;
64
- cache.clearSources("", version);
65
- for (const file of files) {
66
- const relPath = file.slice(enDir.length + 1);
67
- const content = readFileSync(file, "utf8");
68
- const nodes = parseMdx(content);
69
- for (const node of nodes) {
70
- if (node.needsTranslation && node.md5) {
71
- const line = content.substring(0, node.startOffset).split("\n").length;
72
- cache.setSource(node.md5, node.rawText, node.type);
73
- cache.updateSource("", node.md5, relPath, line, version);
74
- }
75
- }
76
- }
77
- }
78
- function rescan(version) {
79
- const cache = getCache();
80
- const enDir = getSourcePath(version);
81
- if (!enDir || !existsSync(enDir)) return 0;
82
- const files = walkFiles(enDir);
83
- cache.clearSources("", version);
84
- for (const file of files) {
85
- const relPath = file.slice(enDir.length + 1);
86
- const content = readFileSync(file, "utf8");
87
- const nodes = parseMdx(content);
88
- for (const node of nodes) {
89
- if (node.needsTranslation && node.md5) {
90
- const line = content.substring(0, node.startOffset).split("\n").length;
91
- cache.setSource(node.md5, node.rawText, node.type);
92
- cache.updateSource("", node.md5, relPath, line, version);
93
- }
94
- }
95
- }
96
- return files.length;
97
- }
98
- function getOverview() {
99
- const cache = getCache();
100
- const versions = getVersions();
101
- const langs = getLangs();
102
- const result = {};
103
- for (const version of versions) {
104
- ensureScanned(version);
105
- const enDir = getSourcePath(version);
106
- const enFileCount = enDir && existsSync(enDir) ? walkFiles(enDir).length : 0;
107
- const langStats = {};
108
- for (const lang of langs) {
109
- const sections = cache.sectionStats(version, lang);
110
- let totalFiles = 0, translatedFiles = 0, totalNodes = 0, translatedNodes = 0;
111
- const sectionMap = {};
112
- for (const s of sections) {
113
- sectionMap[s.section] = s;
114
- totalFiles += s.totalFiles;
115
- translatedFiles += s.translatedFiles;
116
- totalNodes += s.totalNodes;
117
- translatedNodes += s.translatedNodes;
118
- }
119
- langStats[lang] = { sections: sectionMap, totalFiles, translatedFiles, totalNodes, translatedNodes };
120
- }
121
- result[version] = { enFileCount, langs: langStats };
122
- }
123
- return result;
124
- }
125
- function getFileCoverage(version, lang) {
126
- ensureScanned(version);
127
- return getCache().fileCoverage(version, lang);
128
- }
129
- function getFileBlocks(version, lang, file) {
130
- const enDir = getSourcePath(version);
131
- if (!enDir) return null;
132
- const enPath = join(enDir, file);
133
- if (!existsSync(enPath)) return null;
134
- const cache = getCache();
135
- const content = readFileSync(enPath, "utf8");
136
- const nodes = parseMdx(content);
137
- const blocks = [];
138
- let lastEnd = 0;
139
- for (const node of nodes) {
140
- if (node.startOffset > lastEnd) {
141
- const gap = content.substring(lastEnd, node.startOffset);
142
- blocks.push({ md5: null, type: "gap", source: gap, translation: null });
143
- }
144
- if (node.needsTranslation && node.md5) {
145
- const translation = lang === "en" ? null : cache.get(lang, node.md5) ?? null;
146
- blocks.push({ md5: node.md5, type: node.type, source: node.rawText, translation });
147
- } else {
148
- blocks.push({ md5: null, type: node.type, source: node.rawText, translation: null });
149
- }
150
- lastEnd = node.endOffset;
151
- }
152
- if (lastEnd < content.length) {
153
- const tail = content.substring(lastEnd);
154
- if (tail.trim()) blocks.push({ md5: null, type: "gap", source: tail, translation: null });
155
- }
156
- return blocks;
157
- }
158
-
159
- // src/admin/server/routes/jobs.ts
160
- import { Hono } from "hono";
161
- import { streamSSE } from "hono/streaming";
162
-
163
- // src/admin/server/services/job-manager.ts
164
- import { spawn } from "child_process";
165
- var PROJECT_ROOT = process.cwd();
166
- var CLI_BIN = process.argv[1] ?? "docs-i18n";
167
- var _config2;
168
- function setConfig(config) {
169
- _config2 = config;
170
- }
171
- var JobManager = class {
172
- jobs = /* @__PURE__ */ new Map();
173
- processes = /* @__PURE__ */ new Map();
174
- subscribers = /* @__PURE__ */ new Map();
175
- nextId = 1;
176
- start(opts) {
177
- for (const [, job2] of this.jobs) {
178
- if (job2.lang === opts.lang && job2.version === opts.version && job2.status === "running") {
179
- throw new Error(`Job already running for ${opts.lang}/${opts.version}`);
180
- }
181
- }
182
- const id = `job-${this.nextId++}`;
183
- const job = {
184
- id,
185
- lang: opts.lang,
186
- version: opts.version,
187
- project: opts.project ?? "",
188
- status: "running",
189
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
190
- logLines: [],
191
- translatedFiles: 0,
192
- totalFiles: 0,
193
- toTranslate: 0,
194
- errorFiles: 0
195
- };
196
- this.jobs.set(id, job);
197
- const args = [
198
- "translate",
199
- "--lang",
200
- opts.lang,
201
- "--version",
202
- opts.version,
203
- "--max",
204
- String(opts.max ?? 999),
205
- "--concurrency",
206
- String(opts.concurrency ?? 3)
207
- ];
208
- if (opts.project) args.push("--project", opts.project);
209
- if (opts.model) args.push("--model", opts.model);
210
- if (opts.files?.length) args.push("--files", opts.files.join(","));
211
- const apiKey = _config2?.llm?.apiKey || process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY || "";
212
- if (apiKey) args.push("--api-key", apiKey);
213
- const proc = spawn(CLI_BIN, args, {
214
- cwd: PROJECT_ROOT,
215
- stdio: ["ignore", "pipe", "pipe"],
216
- env: { ...process.env, NO_TTY: "1", FORCE_COLOR: "0" }
217
- });
218
- this.processes.set(id, proc);
219
- proc.stdout?.on("data", (d) => {
220
- for (const line of d.toString().split("\n").filter(Boolean)) {
221
- this.addLog(id, line);
222
- this.parseProgress(job, line);
223
- }
224
- });
225
- proc.stderr?.on("data", (d) => {
226
- for (const line of d.toString().split("\n").filter(Boolean)) {
227
- this.addLog(id, line);
228
- }
229
- });
230
- proc.on("exit", (code) => {
231
- job.status = code === 0 ? "completed" : "failed";
232
- job.exitCode = code;
233
- job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
234
- job.currentFile = void 0;
235
- this.addLog(id, `Process exited with code ${code}`);
236
- this.processes.delete(id);
237
- this.emit(id, { type: "exit", code });
238
- });
239
- proc.on("error", (err) => {
240
- job.status = "failed";
241
- job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
242
- this.addLog(id, `Process error: ${err.message}`);
243
- this.processes.delete(id);
244
- this.emit(id, { type: "exit", code: -1 });
245
- });
246
- return job;
247
- }
248
- cancel(id) {
249
- const proc = this.processes.get(id);
250
- const job = this.jobs.get(id);
251
- if (!proc || !job) return false;
252
- proc.kill("SIGTERM");
253
- job.status = "cancelled";
254
- job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
255
- this.processes.delete(id);
256
- this.emit(id, { type: "exit", code: null });
257
- return true;
258
- }
259
- list() {
260
- return [...this.jobs.values()].sort(
261
- (a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
262
- );
263
- }
264
- get(id) {
265
- return this.jobs.get(id);
266
- }
267
- remove(id) {
268
- const job = this.jobs.get(id);
269
- if (!job || job.status === "running") return false;
270
- this.jobs.delete(id);
271
- this.subscribers.delete(id);
272
- return true;
273
- }
274
- subscribe(id, callback) {
275
- if (!this.subscribers.has(id)) this.subscribers.set(id, /* @__PURE__ */ new Set());
276
- this.subscribers.get(id)?.add(callback);
277
- return () => {
278
- this.subscribers.get(id)?.delete(callback);
279
- };
280
- }
281
- addLog(id, line) {
282
- const job = this.jobs.get(id);
283
- if (!job) return;
284
- const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
285
- const entry = `[${ts}] ${line}`;
286
- job.logLines = job.logLines ?? [];
287
- job.logLines.push(entry);
288
- if (job.logLines.length > 500) job.logLines.shift();
289
- this.emit(id, { type: "log", data: entry });
290
- }
291
- emit(id, event) {
292
- this.subscribers.get(id)?.forEach((cb) => cb(event));
293
- }
294
- parseProgress(job, line) {
295
- const cached = line.match(/\+(\d+) cached/);
296
- if (cached) job.translatedFiles += Number.parseInt(cached[1], 10);
297
- const untranslated = line.match(/(\d+) untranslated keys/);
298
- if (untranslated) job.toTranslate = Number.parseInt(untranslated[1], 10);
299
- const chunk = line.match(/chunk \d+\/(\d+)/);
300
- if (chunk) job.totalFiles = Number.parseInt(chunk[1], 10);
301
- if (line.includes("\u23F3")) job.currentFile = line.replace(/^⏳\s*/, "");
302
- }
303
- };
304
- var jobManager = new JobManager();
305
-
306
- // src/admin/server/routes/jobs.ts
307
- var app = new Hono();
308
- app.get("/", (c) => {
309
- return c.json(
310
- jobManager.list().map((j) => ({
311
- ...j,
312
- logLines: (j.logLines ?? []).slice(-20)
313
- // Last 20 lines in list view
314
- }))
315
- );
316
- });
317
- app.post("/", async (c) => {
318
- const body = await c.req.json();
319
- if (!body.lang || !body.version) {
320
- return c.json({ error: "Missing lang or version" }, 400);
321
- }
322
- try {
323
- const job = jobManager.start(body);
324
- return c.json(job, 201);
325
- } catch (err) {
326
- return c.json(
327
- { error: err instanceof Error ? err.message : "Unknown error" },
328
- 409
329
- );
330
- }
331
- });
332
- app.get("/:id", (c) => {
333
- const job = jobManager.get(c.req.param("id"));
334
- if (!job) return c.json({ error: "Job not found" }, 404);
335
- return c.json(job);
336
- });
337
- app.delete("/:id", (c) => {
338
- const id = c.req.param("id");
339
- const job = jobManager.get(id);
340
- if (!job) return c.json({ error: "Job not found" }, 404);
341
- if (job.status === "running") {
342
- jobManager.cancel(id);
343
- } else {
344
- jobManager.remove(id);
345
- }
346
- return c.json({ ok: true });
347
- });
348
- app.get("/:id/stream", (c) => {
349
- const id = c.req.param("id");
350
- const job = jobManager.get(id);
351
- if (!job) return c.json({ error: "Job not found" }, 404);
352
- return streamSSE(c, async (stream) => {
353
- await stream.writeSSE({
354
- data: JSON.stringify({
355
- type: "state",
356
- data: { ...job, logLines: (job.logLines ?? []).slice(-50) }
357
- }),
358
- event: "message"
359
- });
360
- const unsubscribe = jobManager.subscribe(id, async (event) => {
361
- try {
362
- await stream.writeSSE({
363
- data: JSON.stringify(event),
364
- event: "message"
365
- });
366
- } catch {
367
- unsubscribe();
368
- }
369
- });
370
- while (true) {
371
- const currentJob = jobManager.get(id);
372
- if (!currentJob || currentJob.status !== "running") {
373
- await stream.writeSSE({
374
- data: JSON.stringify({ type: "done", data: currentJob }),
375
- event: "message"
376
- });
377
- break;
378
- }
379
- await stream.sleep(1e3);
380
- }
381
- unsubscribe();
382
- });
383
- });
384
- var jobs_default = app;
385
-
386
- // src/admin/server/routes/models.ts
387
- import { Hono as Hono2 } from "hono";
388
- var app2 = new Hono2();
389
- var cachedResult = null;
390
- var cacheTime = 0;
391
- var CACHE_TTL = 5 * 60 * 1e3;
392
- app2.get("/", async (c) => {
393
- const now = Date.now();
394
- if (cachedResult && now - cacheTime < CACHE_TTL) {
395
- return c.json(cachedResult);
396
- }
397
- try {
398
- const res = await fetch("https://openrouter.ai/api/v1/models");
399
- if (!res.ok) {
400
- return c.json({ error: `OpenRouter API error: ${res.status}` }, 502);
401
- }
402
- const { data } = await res.json();
403
- cachedResult = formatModels(data);
404
- cacheTime = now;
405
- return c.json(cachedResult);
406
- } catch (err) {
407
- return c.json(
408
- { error: err instanceof Error ? err.message : "Failed to fetch models" },
409
- 500
410
- );
411
- }
412
- });
413
- function formatModels(models) {
414
- return models.filter((m) => {
415
- if (!m.pricing) return false;
416
- const pp = Number.parseFloat(m.pricing.prompt);
417
- const cp = Number.parseFloat(m.pricing.completion);
418
- if (pp < 0 || cp < 0) return false;
419
- if (!m.architecture?.modality?.startsWith("text")) return false;
420
- if (!m.architecture.output_modalities?.includes("text")) return false;
421
- return true;
422
- }).map((m) => {
423
- const pp = Number.parseFloat(m.pricing.prompt) * 1e6;
424
- const cp = Number.parseFloat(m.pricing.completion) * 1e6;
425
- return {
426
- id: m.id,
427
- name: m.name,
428
- promptPrice: pp,
429
- completionPrice: cp,
430
- contextLength: m.context_length,
431
- maxOutput: m.top_provider?.max_completion_tokens ?? 0,
432
- isFree: pp === 0 && cp === 0,
433
- supportsJson: m.supported_parameters?.includes("response_format") || m.supported_parameters?.includes("structured_outputs"),
434
- supportsTools: m.supported_parameters?.includes("tools"),
435
- provider: m.id.split("/")[0]
436
- };
437
- }).sort((a, b) => a.promptPrice - b.promptPrice);
438
- }
439
- var models_default = app2;
440
-
441
- // src/admin/server/routes/status.ts
442
- import { Hono as Hono3 } from "hono";
443
- var app3 = new Hono3();
444
- app3.get("/", (c) => {
445
- return c.json({
446
- versions: getVersions(),
447
- langs: ["en", ...getLangs()],
448
- data: getOverview()
449
- });
450
- });
451
- app3.get("/:version/:lang", (c) => {
452
- const { version, lang } = c.req.param();
453
- if (lang === "en") {
454
- const anyLang = getLangs()[0];
455
- const coverage = getFileCoverage(version, anyLang);
456
- return c.json(coverage.map((f) => ({ file: f.file, total: f.total, translated: f.total })));
457
- }
458
- return c.json(getFileCoverage(version, lang));
459
- });
460
- app3.get("/:version/:lang/blocks", (c) => {
461
- const { version, lang } = c.req.param();
462
- const filePath = c.req.query("path");
463
- if (!filePath) return c.json({ error: "Missing path query param" }, 400);
464
- const blocks = getFileBlocks(version, lang, filePath);
465
- if (!blocks) return c.json({ error: "File not found" }, 404);
466
- return c.json({ file: filePath, lang, version, blocks });
467
- });
468
- app3.delete("/:version/:lang/cache", (c) => {
469
- const { lang } = c.req.param();
470
- const key = c.req.query("key");
471
- if (!key) return c.json({ error: "Missing key query param" }, 400);
472
- const cache = getCache();
473
- cache.db.prepare("DELETE FROM translations WHERE lang = ? AND key = ?").run(lang, key);
474
- return c.json({ deleted: key, lang });
475
- });
476
- app3.post("/:version/rescan", (c) => {
477
- const { version } = c.req.param();
478
- const count = rescan(version);
479
- return c.json({ version, files: count });
480
- });
481
- var status_default = app3;
482
-
483
- // src/admin/server/index.ts
484
- function loadVersion() {
485
- try {
486
- let dir = dirname(fileURLToPath(import.meta.url));
487
- for (let i = 0; i < 5; i++) {
488
- try {
489
- const pkg = JSON.parse(readFileSync2(resolve2(dir, "package.json"), "utf-8"));
490
- if (pkg.name === "docs-i18n") return pkg.version;
491
- } catch {
492
- }
493
- dir = dirname(dir);
494
- }
495
- } catch {
496
- }
497
- return "unknown";
498
- }
499
- var PKG_VERSION = loadVersion();
500
- async function startAdmin(config, port = 3456) {
501
- initStatus(config);
502
- setConfig(config);
503
- const app4 = new Hono4();
504
- app4.get("/api/version", (c) => c.json({ version: PKG_VERSION }));
505
- app4.route("/api/status", status_default);
506
- app4.route("/api/jobs", jobs_default);
507
- app4.route("/api/models", models_default);
508
- app4.get("/api/health", (c) => c.json({ ok: true }));
509
- app4.get("/api/config", (c) => c.json({ projectRoot: process.cwd() }));
510
- app4.post("/api/open-file", async (c) => {
511
- const { file } = await c.req.json();
512
- if (!file) return c.json({ error: "Missing file" }, 400);
513
- const fullPath = resolve2(process.cwd(), file);
514
- if (!fullPath.startsWith(process.cwd())) return c.json({ error: "Invalid path" }, 400);
515
- const candidates = process.env.EDITOR_CMD ? [process.env.EDITOR_CMD] : ["code", "cursor", "zed"];
516
- for (const cmd of candidates) {
517
- const found = await new Promise((r) => {
518
- const p = nodeSpawn("which", [cmd], { stdio: "ignore" });
519
- p.on("exit", (code) => r(code === 0));
520
- p.on("error", () => r(false));
521
- });
522
- if (found) {
523
- nodeSpawn(cmd, [fullPath], { stdio: "ignore", detached: true }).unref();
524
- return c.json({ opened: fullPath, editor: cmd });
525
- }
526
- }
527
- const fallback = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
528
- nodeSpawn(fallback, [fullPath], { stdio: "ignore", detached: true }).unref();
529
- return c.json({ opened: fullPath, editor: fallback });
530
- });
531
- const thisFile = new URL(import.meta.url).pathname;
532
- const pkgRoot = resolve2(thisFile, "..", "..");
533
- const adminRoot = resolve2(pkgRoot, "src", "admin");
534
- try {
535
- const { createServer: createViteServer } = await import("vite");
536
- const vite = await createViteServer({
537
- root: adminRoot,
538
- server: { middlewareMode: true },
539
- appType: "spa"
540
- });
541
- const server = createServer(async (req, res) => {
542
- const url = req.url ?? "/";
543
- if (url.startsWith("/api")) {
544
- const headers = new Headers();
545
- for (const [k, v] of Object.entries(req.headers)) {
546
- if (v) headers.set(k, Array.isArray(v) ? v.join(", ") : v);
547
- }
548
- const body = req.method !== "GET" && req.method !== "HEAD" ? await new Promise((r) => {
549
- let data = "";
550
- req.on("data", (c) => {
551
- data += c.toString();
552
- });
553
- req.on("end", () => r(data));
554
- }) : void 0;
555
- const webReq = new Request(`http://localhost:${port}${url}`, {
556
- method: req.method,
557
- headers,
558
- body
559
- });
560
- const webRes = await app4.fetch(webReq);
561
- res.writeHead(webRes.status, Object.fromEntries(webRes.headers.entries()));
562
- res.end(Buffer.from(await webRes.arrayBuffer()));
563
- return;
564
- }
565
- vite.middlewares(req, res);
566
- });
567
- server.listen(port, () => {
568
- console.log(`\u{1F310} docs-i18n admin \u2192 http://localhost:${port}`);
569
- });
570
- } catch (err) {
571
- console.error("Failed to start admin UI with Vite:", err.message);
572
- console.log("Starting API-only mode...");
573
- const server = createServer(async (req, res) => {
574
- const url = req.url ?? "/";
575
- const headers = new Headers();
576
- for (const [k, v] of Object.entries(req.headers)) {
577
- if (v) headers.set(k, Array.isArray(v) ? v.join(", ") : v);
578
- }
579
- const body = req.method !== "GET" && req.method !== "HEAD" ? await new Promise((r) => {
580
- let data = "";
581
- req.on("data", (c) => {
582
- data += c.toString();
583
- });
584
- req.on("end", () => r(data));
585
- }) : void 0;
586
- const webReq = new Request(`http://localhost:${port}${url}`, { method: req.method, headers, body });
587
- const webRes = await app4.fetch(webReq);
588
- res.writeHead(webRes.status, Object.fromEntries(webRes.headers.entries()));
589
- res.end(Buffer.from(await webRes.arrayBuffer()));
590
- });
591
- server.listen(port, () => {
592
- console.log(`\u{1F310} docs-i18n admin (API only) \u2192 http://localhost:${port}`);
593
- });
594
- }
595
- }
596
- export {
597
- startAdmin
598
- };
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Translation Admin</title>
7
- </head>
8
- <body>
9
- <script>document.documentElement.dataset.theme=localStorage.getItem('theme')||'dark'</script>
10
- <div id="root"></div>
11
- <script type="module" src="/ui/main.tsx"></script>
12
- </body>
13
- </html>