docs-i18n 0.8.2 → 0.8.3
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.
- package/admin/dist/server/server.js +18 -18
- package/package.json +1 -1
- package/template/app/routes/$lang.$project.$version.docs.$.tsx +2 -1
- package/template/app/routes/$lang.$project.$version.docs.framework.$framework.$.tsx +2 -0
- package/template/app/routes/$lang.$project.$version.docs.tsx +2 -1
- package/template/app/routes/$lang.$project.docs.$.tsx +2 -1
- package/template/app/routes/$lang.$project.docs.tsx +2 -1
- package/template/app/routes/$lang.docs.$.tsx +2 -1
- package/template/app/routes/$lang.docs.framework.$framework.$.tsx +2 -0
- package/template/app/routes/$lang.docs.tsx +2 -1
- package/template/app/utils/content-loader.ts +12 -5
- package/template/app/utils/docs.server.ts +15 -20
- package/template/content/docs-i18n/en/architecture.md +138 -25
- package/template/content/docs-i18n/en/cli.md +13 -1
- package/template/content/docs-i18n/en/configuration.md +27 -8
- package/template/content/docs-i18n/en/deployment.md +25 -12
- package/template/content/docs-i18n/en/getting-started.md +22 -1
|
@@ -15572,9 +15572,21 @@ var manifest = {
|
|
|
15572
15572
|
functionName: "openFile_createServerFn_handler",
|
|
15573
15573
|
importer: () => import("./assets/misc-y6t3-UOP.js")
|
|
15574
15574
|
},
|
|
15575
|
-
"
|
|
15576
|
-
functionName: "
|
|
15577
|
-
importer: () => import("./assets/
|
|
15575
|
+
"421de02ce39dde6e27cf4689e837ec072cbd01e63f8cdd5c2a3f42f0bd5ca613": {
|
|
15576
|
+
functionName: "fetchJobs_createServerFn_handler",
|
|
15577
|
+
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15578
|
+
},
|
|
15579
|
+
"c08559ac758aa0d315deaca7a0d7d923a9a44d997c8cb811151417c1f221ddd6": {
|
|
15580
|
+
functionName: "createJob_createServerFn_handler",
|
|
15581
|
+
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15582
|
+
},
|
|
15583
|
+
"8a56694c9d7b29422a3e7d2f6b803be100d79d3853d92d465cb55ed572781e62": {
|
|
15584
|
+
functionName: "fetchJob_createServerFn_handler",
|
|
15585
|
+
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15586
|
+
},
|
|
15587
|
+
"88c2855c84e91504070bfecc50ddfa50339d22c305626800b6d9b05d79385d71": {
|
|
15588
|
+
functionName: "deleteJob_createServerFn_handler",
|
|
15589
|
+
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15578
15590
|
},
|
|
15579
15591
|
"4e218d79545765572808c7eab33b7663d4496209c15406d0b449366905b6b83f": {
|
|
15580
15592
|
functionName: "fetchStatus_createServerFn_handler",
|
|
@@ -15596,21 +15608,9 @@ var manifest = {
|
|
|
15596
15608
|
functionName: "rescanVersion_createServerFn_handler",
|
|
15597
15609
|
importer: () => import("./assets/status-CM7Azp4n.js")
|
|
15598
15610
|
},
|
|
15599
|
-
"
|
|
15600
|
-
functionName: "
|
|
15601
|
-
importer: () => import("./assets/
|
|
15602
|
-
},
|
|
15603
|
-
"c08559ac758aa0d315deaca7a0d7d923a9a44d997c8cb811151417c1f221ddd6": {
|
|
15604
|
-
functionName: "createJob_createServerFn_handler",
|
|
15605
|
-
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15606
|
-
},
|
|
15607
|
-
"8a56694c9d7b29422a3e7d2f6b803be100d79d3853d92d465cb55ed572781e62": {
|
|
15608
|
-
functionName: "fetchJob_createServerFn_handler",
|
|
15609
|
-
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15610
|
-
},
|
|
15611
|
-
"88c2855c84e91504070bfecc50ddfa50339d22c305626800b6d9b05d79385d71": {
|
|
15612
|
-
functionName: "deleteJob_createServerFn_handler",
|
|
15613
|
-
importer: () => import("./assets/jobs-FXffC7LH.js")
|
|
15611
|
+
"5080dc3f2f2309ec6981b94c431969637130c657e8a1dfb10400b4614eecc1ea": {
|
|
15612
|
+
functionName: "fetchModels_createServerFn_handler",
|
|
15613
|
+
importer: () => import("./assets/models-YNa3F3nn.js")
|
|
15614
15614
|
}
|
|
15615
15615
|
};
|
|
15616
15616
|
async function getServerFnById(id) {
|
package/package.json
CHANGED
|
@@ -15,7 +15,8 @@ const fetchDoc = createServerFn({ method: 'GET' })
|
|
|
15
15
|
)
|
|
16
16
|
.handler(async ({ data }) => {
|
|
17
17
|
const { loadDoc } = await import('~/utils/docs.server')
|
|
18
|
-
|
|
18
|
+
const projectConfig = findProject(data.project)
|
|
19
|
+
return loadDoc(data.project, data.version, data.lang, data.slug, projectConfig)
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
export const Route = createFileRoute('/$lang/$project/$version/docs/$')({
|
|
@@ -19,11 +19,13 @@ const fetchFrameworkDoc = createServerFn({ method: 'GET' })
|
|
|
19
19
|
)
|
|
20
20
|
.handler(async ({ data }) => {
|
|
21
21
|
const { loadDoc } = await import('~/utils/docs.server')
|
|
22
|
+
const projectConfig = findProject(data.project)
|
|
22
23
|
return loadDoc(
|
|
23
24
|
data.project,
|
|
24
25
|
data.version,
|
|
25
26
|
data.lang,
|
|
26
27
|
`framework/${data.framework}/${data.slug}`,
|
|
28
|
+
projectConfig,
|
|
27
29
|
)
|
|
28
30
|
})
|
|
29
31
|
|
|
@@ -13,7 +13,8 @@ const fetchDocsConfig = createServerFn({ method: 'GET' })
|
|
|
13
13
|
.inputValidator((data: { project: string; version: string }) => data)
|
|
14
14
|
.handler(async ({ data }) => {
|
|
15
15
|
const { loadDocsConfig } = await import('~/utils/docs.server')
|
|
16
|
-
|
|
16
|
+
const projectConfig = findProject(data.project)
|
|
17
|
+
return loadDocsConfig(data.project, data.version, projectConfig)
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
export const Route = createFileRoute('/$lang/$project/$version/docs')({
|
|
@@ -13,7 +13,8 @@ const fetchDoc = createServerFn({ method: 'GET' })
|
|
|
13
13
|
)
|
|
14
14
|
.handler(async ({ data }) => {
|
|
15
15
|
const { loadDoc } = await import('~/utils/docs.server')
|
|
16
|
-
|
|
16
|
+
const projectConfig = findProject(data.project)
|
|
17
|
+
return loadDoc(data.project, data.version, data.lang, data.slug, projectConfig)
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
export const Route = createFileRoute('/$lang/$project/docs/$')({
|
|
@@ -13,7 +13,8 @@ const fetchDocsConfig = createServerFn({ method: 'GET' })
|
|
|
13
13
|
.inputValidator((data: { project: string; version: string }) => data)
|
|
14
14
|
.handler(async ({ data }) => {
|
|
15
15
|
const { loadDocsConfig } = await import('~/utils/docs.server')
|
|
16
|
-
|
|
16
|
+
const projectConfig = findProject(data.project)
|
|
17
|
+
return loadDocsConfig(data.project, data.version, projectConfig)
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
export const Route = createFileRoute('/$lang/$project/docs')({
|
|
@@ -13,7 +13,8 @@ const fetchDoc = createServerFn({ method: 'GET' })
|
|
|
13
13
|
)
|
|
14
14
|
.handler(async ({ data }) => {
|
|
15
15
|
const { loadDoc } = await import('~/utils/docs.server')
|
|
16
|
-
|
|
16
|
+
const projectConfig = getSingleProject()
|
|
17
|
+
return loadDoc(data.project, data.version, data.lang, data.slug, projectConfig)
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
export const Route = createFileRoute('/$lang/docs/$')({
|
|
@@ -19,11 +19,13 @@ const fetchFrameworkDoc = createServerFn({ method: 'GET' })
|
|
|
19
19
|
)
|
|
20
20
|
.handler(async ({ data }) => {
|
|
21
21
|
const { loadDoc } = await import('~/utils/docs.server')
|
|
22
|
+
const projectConfig = getSingleProject()
|
|
22
23
|
return loadDoc(
|
|
23
24
|
data.project,
|
|
24
25
|
data.version,
|
|
25
26
|
data.lang,
|
|
26
27
|
`framework/${data.framework}/${data.slug}`,
|
|
28
|
+
projectConfig,
|
|
27
29
|
)
|
|
28
30
|
})
|
|
29
31
|
|
|
@@ -15,7 +15,8 @@ const fetchDocsConfig = createServerFn({ method: 'GET' })
|
|
|
15
15
|
.inputValidator((data: { project: string; version: string }) => data)
|
|
16
16
|
.handler(async ({ data }) => {
|
|
17
17
|
const { loadDocsConfig } = await import('~/utils/docs.server')
|
|
18
|
-
|
|
18
|
+
const projectConfig = getSingleProject()
|
|
19
|
+
return loadDocsConfig(data.project, data.version, projectConfig)
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
export const Route = createFileRoute('/$lang/docs')({
|
|
@@ -64,6 +64,7 @@ function getTranslationCache(projectRoot: string): TranslationCache | null {
|
|
|
64
64
|
|
|
65
65
|
export function createFsLoader(
|
|
66
66
|
projectRoot: string,
|
|
67
|
+
docsRoot = 'content',
|
|
67
68
|
urlMapper?: (filePath: string) => string,
|
|
68
69
|
): ContentLoader {
|
|
69
70
|
/** Supported markdown extensions, in priority order. */
|
|
@@ -79,14 +80,20 @@ export function createFsLoader(
|
|
|
79
80
|
lang: string,
|
|
80
81
|
slug: string,
|
|
81
82
|
): { raw: string; filePath: string } | null {
|
|
83
|
+
// Resolve content base directory using docsRoot from site config.
|
|
84
|
+
// docsRoot encodes the project-specific path (e.g. 'content/query', 'content/docs').
|
|
85
|
+
// Convention: no /en/ subdir — English source files live directly under version/.
|
|
86
|
+
// Non-English comes from .cache/ (translation assembly).
|
|
82
87
|
const baseDirs = [
|
|
83
|
-
resolve(projectRoot,
|
|
84
|
-
resolve(projectRoot,
|
|
85
|
-
|
|
86
|
-
resolve(projectRoot, 'content', lang),
|
|
87
|
-
// Flat structure (no lang subdir): content/{version}/ directly
|
|
88
|
+
resolve(projectRoot, docsRoot, version),
|
|
89
|
+
resolve(projectRoot, docsRoot),
|
|
90
|
+
// Fallback: try content/{project}/{version} pattern
|
|
88
91
|
resolve(projectRoot, 'content', project, version),
|
|
89
92
|
resolve(projectRoot, 'content', version),
|
|
93
|
+
// Legacy: with lang subdir
|
|
94
|
+
resolve(projectRoot, 'content', project, version, lang),
|
|
95
|
+
resolve(projectRoot, docsRoot, version, lang),
|
|
96
|
+
resolve(projectRoot, docsRoot, lang),
|
|
90
97
|
]
|
|
91
98
|
|
|
92
99
|
if (!urlMapper) {
|
|
@@ -62,7 +62,7 @@ export function setContentLoader(loader: ContentLoader) {
|
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Load a document with i18n fallback.
|
|
65
|
-
*
|
|
65
|
+
* Uses docsRoot from projectConfig to resolve content paths.
|
|
66
66
|
*/
|
|
67
67
|
export async function loadDoc(
|
|
68
68
|
project: string,
|
|
@@ -71,11 +71,9 @@ export async function loadDoc(
|
|
|
71
71
|
slug: string,
|
|
72
72
|
projectConfig?: ProjectConfig,
|
|
73
73
|
): Promise<LoadedDoc> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
return getLoader().loadDoc(project, version, lang, slug)
|
|
74
|
+
const docsRoot = projectConfig?.docsRoot || 'content'
|
|
75
|
+
const loader = createFsLoader(getProjectRoot(), docsRoot, projectConfig?.urlMapper)
|
|
76
|
+
return loader.loadDoc(project, version, lang, slug)
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
/**
|
|
@@ -91,11 +89,13 @@ export async function loadDocsConfig(
|
|
|
91
89
|
return loadFilesystemSidebar(project, version, projectConfig)
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
const
|
|
92
|
+
const docsRoot = projectConfig?.docsRoot || 'content'
|
|
93
|
+
const loader = createFsLoader(getProjectRoot(), docsRoot)
|
|
94
|
+
const config = await loader.loadDocsConfig(project, version)
|
|
95
95
|
if (config) return config
|
|
96
96
|
|
|
97
97
|
// Auto-scan fallback (filesystem only)
|
|
98
|
-
return autoScanDocs(getProjectRoot(), project, version)
|
|
98
|
+
return autoScanDocs(getProjectRoot(), docsRoot, project, version)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** Generate sidebar from filesystem using numeric prefix ordering. */
|
|
@@ -105,14 +105,11 @@ function loadFilesystemSidebar(
|
|
|
105
105
|
projectConfig: ProjectConfig,
|
|
106
106
|
): DocsConfig {
|
|
107
107
|
const root = getProjectRoot()
|
|
108
|
+
const docsRoot = projectConfig.docsRoot || 'content'
|
|
108
109
|
const candidates = [
|
|
109
|
-
resolve(root,
|
|
110
|
-
resolve(root,
|
|
111
|
-
resolve(root, 'content', version, 'en'),
|
|
112
|
-
resolve(root, 'content', 'en'),
|
|
113
|
-
// Flat structure (no lang subdir)
|
|
110
|
+
resolve(root, docsRoot, version),
|
|
111
|
+
resolve(root, docsRoot),
|
|
114
112
|
resolve(root, 'content', project, version),
|
|
115
|
-
resolve(root, 'content', version),
|
|
116
113
|
]
|
|
117
114
|
for (const dir of candidates) {
|
|
118
115
|
if (existsSync(dir) && statSync(dir).isDirectory()) {
|
|
@@ -124,20 +121,18 @@ function loadFilesystemSidebar(
|
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
/**
|
|
127
|
-
* Auto-generate sidebar config by scanning .md files in the content directory.
|
|
124
|
+
* Auto-generate sidebar config by scanning .md/.mdx files in the content directory.
|
|
128
125
|
*/
|
|
129
126
|
function autoScanDocs(
|
|
130
127
|
root: string,
|
|
128
|
+
docsRoot: string,
|
|
131
129
|
project: string,
|
|
132
130
|
version: string,
|
|
133
131
|
): DocsConfig {
|
|
134
132
|
const candidates = [
|
|
135
|
-
resolve(root,
|
|
136
|
-
resolve(root,
|
|
137
|
-
resolve(root, 'content', version, 'en'),
|
|
138
|
-
resolve(root, 'content', 'en'),
|
|
133
|
+
resolve(root, docsRoot, version),
|
|
134
|
+
resolve(root, docsRoot),
|
|
139
135
|
resolve(root, 'content', project, version),
|
|
140
|
-
resolve(root, 'content', version),
|
|
141
136
|
]
|
|
142
137
|
|
|
143
138
|
for (const dir of candidates) {
|
|
@@ -7,33 +7,146 @@ description: How docs-i18n works internally -- the translation pipeline, AST par
|
|
|
7
7
|
|
|
8
8
|
This document explains how docs-i18n works internally. Understanding the pipeline helps you tune configuration, debug issues, and contribute to the project.
|
|
9
9
|
|
|
10
|
+
## System Overview
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
14
|
+
│ docs-i18n ecosystem │
|
|
15
|
+
│ │
|
|
16
|
+
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ │
|
|
17
|
+
│ │ CLI │ │ Admin │ │ Template │ │ Runtime │ │
|
|
18
|
+
│ │ │ │ Dashboard │ │ (Site) │ │ (D1) │ │
|
|
19
|
+
│ │ translate│ │ (pre- │ │ TanStack │ │ CF │ │
|
|
20
|
+
│ │ rescan │ │ built) │ │ Start │ │ Workers │ │
|
|
21
|
+
│ │ assemble │ │ │ │ │ │ │ │
|
|
22
|
+
│ │ status │ │ Web UI │ │ SSR docs │ │ Edge │ │
|
|
23
|
+
│ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └────┬────┘ │
|
|
24
|
+
│ │ │ │ │ │
|
|
25
|
+
│ └────────┬───────┴───────┬───────┘ │ │
|
|
26
|
+
│ │ │ │ │
|
|
27
|
+
│ ┌──────▼──────┐ ┌─────▼──────┐ ┌─────▼─────┐ │
|
|
28
|
+
│ │ SQLite │ │ Content │ │ D1 │ │
|
|
29
|
+
│ │ .cache/ │ │ content/ │ │ (cloud) │ │
|
|
30
|
+
│ │ translations│ │ {project}/ │ │ │ │
|
|
31
|
+
│ │ .db │ │ {version}/ │ │ translate │ │
|
|
32
|
+
│ └─────────────┘ └────────────┘ │ ions │ │
|
|
33
|
+
│ └───────────┘ │
|
|
34
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Monorepo Structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
docs-i18n/
|
|
41
|
+
packages/
|
|
42
|
+
core/ ← Published as "docs-i18n" on npm
|
|
43
|
+
│ src/
|
|
44
|
+
│ cli.ts CLI entry point
|
|
45
|
+
│ core/ Parser, cache, assembler, translator
|
|
46
|
+
│ commands/ translate, rescan, assemble, status, upload
|
|
47
|
+
│ dist/ Built output
|
|
48
|
+
│
|
|
49
|
+
admin/ ← Pre-built admin dashboard
|
|
50
|
+
│ app/ TanStack Start React app
|
|
51
|
+
│ server/ Server functions (status, jobs, models)
|
|
52
|
+
│ dist/ Pre-built (zero-install runtime)
|
|
53
|
+
│ serve.mjs Node.js HTTP adapter
|
|
54
|
+
│
|
|
55
|
+
template/ ← Docs site template (built per-project)
|
|
56
|
+
app/ TanStack Start React app
|
|
57
|
+
content/ Demo content + docs-i18n's own docs
|
|
58
|
+
```
|
|
59
|
+
|
|
10
60
|
## Translation Pipeline
|
|
11
61
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
```
|
|
63
|
+
Source .md/.mdx files (English)
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
┌──────────────────────────────────────────────────┐
|
|
67
|
+
│ 1. Normalize │
|
|
68
|
+
│ Ensure JSX tags (<AppOnly> etc.) are separated │
|
|
69
|
+
│ by blank lines for correct AST parsing │
|
|
70
|
+
└──────────────────┬───────────────────────────────┘
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
┌──────────────────────────────────────────────────┐
|
|
74
|
+
│ 2. Parse (remark AST) │
|
|
75
|
+
│ .md/.mdx → flat list of typed nodes │
|
|
76
|
+
│ heading | paragraph | list | code | html | ... │
|
|
77
|
+
│ Each node: { type, rawText, needsTranslation } │
|
|
78
|
+
└──────────────────┬───────────────────────────────┘
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌──────────────────────────────────────────────────┐
|
|
82
|
+
│ 3. Hash (MD5) │
|
|
83
|
+
│ "## Installation" → "a1b2c3d4..." │
|
|
84
|
+
│ Same content = same key = deduplicated │
|
|
85
|
+
└──────────────────┬───────────────────────────────┘
|
|
86
|
+
│
|
|
87
|
+
▼
|
|
88
|
+
┌──────────────────────────────────────────────────┐
|
|
89
|
+
│ 4. Smart Chunking │
|
|
90
|
+
│ Group nodes into chunks fitting LLM context │
|
|
91
|
+
│ Input budget + output budget + language mult. │
|
|
92
|
+
│ CJK: 2.5x │ Cyrillic: 2.0x │ Chinese: 1.5x │
|
|
93
|
+
└──────────────────┬───────────────────────────────┘
|
|
94
|
+
│
|
|
95
|
+
▼
|
|
96
|
+
┌──────────────────────────────────────────────────┐
|
|
97
|
+
│ 5. LLM Translation │
|
|
98
|
+
│ Send: { nodes: [{ key, type, text }] } │
|
|
99
|
+
│ Recv: { "a1b2c3": "翻译结果" } │
|
|
100
|
+
│ JSON repair + key recovery + retry + rotation │
|
|
101
|
+
└──────────────────┬───────────────────────────────┘
|
|
102
|
+
│
|
|
103
|
+
▼
|
|
104
|
+
┌──────────────────────────────────────────────────┐
|
|
105
|
+
│ 6. Cache (SQLite) │
|
|
106
|
+
│ INSERT INTO translations (lang, key, value) │
|
|
107
|
+
│ WAL mode │ concurrent-safe │ instant writes │
|
|
108
|
+
└──────────────────┬───────────────────────────────┘
|
|
109
|
+
│
|
|
110
|
+
▼
|
|
111
|
+
┌──────────────────────────────────────────────────┐
|
|
112
|
+
│ 7. Assemble │
|
|
113
|
+
│ English source + cached translations │
|
|
114
|
+
│ → translated .md/.mdx files │
|
|
115
|
+
│ Missing translations → fallback to English │
|
|
116
|
+
└──────────────────────────────────────────────────┘
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Content Structure Convention
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
your-project/
|
|
123
|
+
content/ ← English source (no /en/ subdir)
|
|
124
|
+
{project}/{version}/ ← e.g. query/v5/ or nextjs/latest/
|
|
125
|
+
overview.md
|
|
126
|
+
guides/routing.md
|
|
127
|
+
...
|
|
128
|
+
|
|
129
|
+
.cache/
|
|
130
|
+
translations.db ← SQLite cache (all languages)
|
|
131
|
+
content/{version}/{lang}/ ← assembled translations
|
|
132
|
+
overview.md
|
|
133
|
+
guides/routing.md
|
|
134
|
+
|
|
135
|
+
docs-i18n.config.ts ← translation config
|
|
136
|
+
site.config.ts ← site template config
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The site template resolves content as:
|
|
140
|
+
```
|
|
141
|
+
Request: /zh-hans/query/v5/docs/overview
|
|
142
|
+
│ │ │
|
|
143
|
+
│ │ └─ slug
|
|
144
|
+
│ └─ version
|
|
145
|
+
└─ project
|
|
146
|
+
|
|
147
|
+
English: content/query/v5/overview.md ← direct read
|
|
148
|
+
zh-hans: .cache/ → translations.db → assemble() ← from cache
|
|
149
|
+
Fallback: English source (isFallback: true)
|
|
37
150
|
```
|
|
38
151
|
|
|
39
152
|
## Step 1: Normalization
|
|
@@ -271,6 +271,15 @@ For non-English languages, the site automatically loads translations from the `.
|
|
|
271
271
|
|
|
272
272
|
### Full translation pipeline
|
|
273
273
|
|
|
274
|
+
```
|
|
275
|
+
rescan ──→ status ──→ translate ──→ assemble ──→ site dev
|
|
276
|
+
│ │ │ │ │
|
|
277
|
+
▼ ▼ ▼ ▼ ▼
|
|
278
|
+
Index Progress Send to EN + cache Serve
|
|
279
|
+
source bars LLM, cache → output multilingual
|
|
280
|
+
files results files docs site
|
|
281
|
+
```
|
|
282
|
+
|
|
274
283
|
```bash
|
|
275
284
|
# 1. Scan source files
|
|
276
285
|
docs-i18n rescan
|
|
@@ -281,8 +290,11 @@ docs-i18n status
|
|
|
281
290
|
# 3. Translate
|
|
282
291
|
docs-i18n translate --lang zh-hans
|
|
283
292
|
|
|
284
|
-
# 4. Assemble output
|
|
293
|
+
# 4. Assemble output (optional — site does this at runtime)
|
|
285
294
|
docs-i18n assemble --lang zh-hans
|
|
295
|
+
|
|
296
|
+
# 5. Start docs site
|
|
297
|
+
docs-i18n site
|
|
286
298
|
```
|
|
287
299
|
|
|
288
300
|
### Translate a single file for review
|
|
@@ -62,9 +62,20 @@ projects: {
|
|
|
62
62
|
|
|
63
63
|
The version key (e.g., `latest`, `v1`) is used to organize translations in the cache and in assembled output. The source path points to the directory containing your English markdown/MDX files.
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
```
|
|
66
|
+
Content directory convention:
|
|
67
|
+
|
|
68
|
+
content/ ← English source (no /en/ subdir)
|
|
69
|
+
{project}/{version}/ ← source path in config
|
|
70
|
+
file.md
|
|
71
|
+
subdir/file.mdx
|
|
72
|
+
|
|
73
|
+
.cache/
|
|
74
|
+
translations.db ← all translations
|
|
75
|
+
content/{version}/{lang}/ ← assembled output
|
|
76
|
+
```
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
**Single project:**
|
|
68
79
|
|
|
69
80
|
```ts
|
|
70
81
|
projects: {
|
|
@@ -74,6 +85,12 @@ projects: {
|
|
|
74
85
|
}
|
|
75
86
|
```
|
|
76
87
|
|
|
88
|
+
```
|
|
89
|
+
content/docs/ ← source files (English)
|
|
90
|
+
getting-started.md
|
|
91
|
+
api-reference.md
|
|
92
|
+
```
|
|
93
|
+
|
|
77
94
|
**Multi-project:**
|
|
78
95
|
|
|
79
96
|
When you have multiple projects, keys become compound: `project/version` (e.g., `query/latest`, `table/v1`).
|
|
@@ -82,7 +99,7 @@ When you have multiple projects, keys become compound: `project/version` (e.g.,
|
|
|
82
99
|
projects: {
|
|
83
100
|
query: {
|
|
84
101
|
sources: {
|
|
85
|
-
|
|
102
|
+
v5: 'content/query/v5',
|
|
86
103
|
v4: 'content/query/v4',
|
|
87
104
|
},
|
|
88
105
|
},
|
|
@@ -91,14 +108,16 @@ projects: {
|
|
|
91
108
|
latest: 'content/table/latest',
|
|
92
109
|
},
|
|
93
110
|
},
|
|
94
|
-
blog: {
|
|
95
|
-
sources: {
|
|
96
|
-
latest: 'content/blog',
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
111
|
}
|
|
100
112
|
```
|
|
101
113
|
|
|
114
|
+
```
|
|
115
|
+
content/
|
|
116
|
+
query/v5/overview.md ← query project, v5
|
|
117
|
+
query/v4/overview.md ← query project, v4
|
|
118
|
+
table/latest/overview.md ← table project
|
|
119
|
+
```
|
|
120
|
+
|
|
102
121
|
### `languages`
|
|
103
122
|
|
|
104
123
|
**Required.** An array of target language codes to translate into. These codes are used as identifiers in the cache and output directories.
|
|
@@ -100,11 +100,32 @@ jobs:
|
|
|
100
100
|
- run: npx docs-i18n translate --lang zh-hans --dry-run
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Deployment Architecture
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
```
|
|
106
|
+
┌──────────────────────────────────────────────────────────┐
|
|
107
|
+
│ Development (local) │
|
|
108
|
+
│ │
|
|
109
|
+
│ docs-i18n admin ──→ Pre-built Node.js server (port 3456)│
|
|
110
|
+
│ docs-i18n site ──→ Vite dev server (port 3000) │
|
|
111
|
+
│ ↕ reads content/ + .cache/ │
|
|
112
|
+
└──────────────────────────────────────────────────────────┘
|
|
113
|
+
|
|
114
|
+
┌──────────────────────────────────────────────────────────┐
|
|
115
|
+
│ Production (Cloudflare Workers) │
|
|
116
|
+
│ │
|
|
117
|
+
│ docs-i18n site build ──→ .output/ │
|
|
118
|
+
│ docs-i18n site upload ──→ D1 (translations) │
|
|
119
|
+
│ docs-i18n site deploy ──→ CF Workers (SSR) │
|
|
120
|
+
│ │
|
|
121
|
+
│ Request → Worker → fetch EN content → D1 translate │
|
|
122
|
+
│ → SSR render → response │
|
|
123
|
+
└──────────────────────────────────────────────────────────┘
|
|
124
|
+
```
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
## Admin Dashboard
|
|
127
|
+
|
|
128
|
+
The admin dashboard is pre-built and requires no additional dependencies. It runs as a local Node.js server.
|
|
108
129
|
|
|
109
130
|
```bash
|
|
110
131
|
npx docs-i18n admin
|
|
@@ -112,15 +133,7 @@ npx docs-i18n admin
|
|
|
112
133
|
npx docs-i18n admin --port 4000
|
|
113
134
|
```
|
|
114
135
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
**Prerequisites:**
|
|
118
|
-
|
|
119
|
-
Your project must have `vite` and `@vitejs/plugin-react` installed as dev dependencies:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
npm install -D vite @vitejs/plugin-react
|
|
123
|
-
```
|
|
136
|
+
It reads your `docs-i18n.config.ts` to discover projects, versions, and languages.
|
|
124
137
|
|
|
125
138
|
## Runtime Translation with D1
|
|
126
139
|
|
|
@@ -18,11 +18,32 @@ Key characteristics:
|
|
|
18
18
|
- **Admin dashboard** -- web UI for monitoring progress, managing jobs, and previewing translations.
|
|
19
19
|
- **Runtime serve** -- optional D1-compatible translator for SSR sites on Cloudflare Workers.
|
|
20
20
|
|
|
21
|
+
## How It Works
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
25
|
+
│ Your docs │ │ docs-i18n │ │ Translated │
|
|
26
|
+
│ (English) │────→│ translate │────→│ docs (cache) │
|
|
27
|
+
│ .md / .mdx │ │ via LLM │ │ .cache/ │
|
|
28
|
+
└─────────────┘ └─────────────┘ └──────┬───────┘
|
|
29
|
+
│
|
|
30
|
+
┌─────────────┐ │
|
|
31
|
+
│ docs-i18n │◄────────────┘
|
|
32
|
+
│ site │
|
|
33
|
+
│ (dev/build) │──→ Multilingual docs site
|
|
34
|
+
└─────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Workflow:**
|
|
38
|
+
1. Write docs in English (`.md` or `.mdx`)
|
|
39
|
+
2. `docs-i18n translate` sends new/changed content to LLM, caches results in SQLite
|
|
40
|
+
3. `docs-i18n site` serves a multilingual docs site (assembles translations at runtime)
|
|
41
|
+
4. `docs-i18n admin` provides a web UI to monitor progress and manage jobs
|
|
42
|
+
|
|
21
43
|
## Requirements
|
|
22
44
|
|
|
23
45
|
- Node.js 20+ or Bun
|
|
24
46
|
- An API key for an LLM provider (OpenRouter, OpenAI, or Anthropic)
|
|
25
|
-
- For the admin dashboard: `vite` and `@vitejs/plugin-react` as dev dependencies
|
|
26
47
|
|
|
27
48
|
## Installation
|
|
28
49
|
|