docs-i18n 0.8.1 → 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 +32 -32
- 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 +13 -2
- package/template/app/utils/docs.server.ts +17 -15
- package/template/content/blog/en/announcing-query-v5.md +110 -0
- package/template/content/blog/en/hello-world.md +26 -0
- package/template/content/blog/en/i18n-best-practices.md +57 -0
- package/template/content/blog/en/react-query-vs-swr.md +100 -0
- package/template/content/blog/en/state-management-2024.md +143 -0
- package/template/content/blog/en/tanstack-router-1.0.md +121 -0
- package/template/content/blog/ja/announcing-query-v5.md +110 -0
- package/template/content/blog/ja/hello-world.md +26 -0
- package/template/content/blog/zh-hans/announcing-query-v5.md +93 -0
- package/template/content/blog/zh-hans/hello-world.md +26 -0
- package/template/content/docs-i18n/docs.config.json +25 -0
- package/template/content/docs-i18n/en/architecture.md +335 -0
- package/template/content/docs-i18n/en/cli.md +13 -1
- package/template/content/docs-i18n/en/configuration.md +350 -0
- package/template/content/docs-i18n/en/deployment.md +222 -0
- package/template/content/docs-i18n/en/getting-started.md +189 -0
- package/template/content/docs.config.json +25 -0
- package/template/content/en/admin.md +151 -0
- package/template/content/en/architecture.md +222 -0
- package/template/content/en/cli.md +269 -0
- package/template/content/en/configuration.md +331 -0
- package/template/content/en/deployment.md +209 -0
- package/template/content/en/getting-started.md +168 -0
- package/template/content/form/docs.config.json +18 -0
- package/template/content/form/en/guides/validation.md +175 -0
- package/template/content/form/en/installation.md +63 -0
- package/template/content/form/en/overview.md +71 -0
- package/template/content/form/en/quick-start.md +121 -0
- package/template/content/form/ja/installation.md +63 -0
- package/template/content/form/ja/overview.md +71 -0
- package/template/content/form/zh-hans/installation.md +63 -0
- package/template/content/form/zh-hans/overview.md +71 -0
- package/template/content/query/docs.config.json +32 -0
- package/template/content/query/en/guides/mutations.md +126 -0
- package/template/content/query/en/guides/pagination.md +98 -0
- package/template/content/query/en/guides/queries.md +120 -0
- package/template/content/query/en/installation.md +78 -0
- package/template/content/query/en/overview.md +72 -0
- package/template/content/query/en/quick-start.md +108 -0
- package/template/content/query/ja/installation.md +78 -0
- package/template/content/query/ja/overview.md +72 -0
- package/template/content/query/zh-hans/guides/mutations.md +126 -0
- package/template/content/query/zh-hans/guides/pagination.md +98 -0
- package/template/content/query/zh-hans/guides/queries.md +120 -0
- package/template/content/query/zh-hans/installation.md +95 -0
- package/template/content/query/zh-hans/overview.md +72 -0
- package/template/content/query/zh-hans/quick-start.md +108 -0
- package/template/content/router/docs.config.json +18 -0
- package/template/content/router/en/guides/routing-concepts.md +131 -0
- package/template/content/router/en/installation.md +57 -0
- package/template/content/router/en/overview.md +74 -0
- package/template/content/router/en/quick-start.md +88 -0
- package/template/content/router/ja/installation.md +57 -0
- package/template/content/router/ja/overview.md +78 -0
- package/template/content/router/zh-hans/guides/routing-concepts.md +131 -0
- package/template/content/router/zh-hans/installation.md +57 -0
- package/template/content/router/zh-hans/overview.md +81 -0
- package/template/content/router/zh-hans/quick-start.md +88 -0
- package/template/content/table/docs.config.json +18 -0
- package/template/content/table/en/guides/column-definitions.md +135 -0
- package/template/content/table/en/installation.md +56 -0
- package/template/content/table/en/overview.md +79 -0
- package/template/content/table/en/quick-start.md +112 -0
- package/template/content/table/ja/installation.md +56 -0
- package/template/content/table/ja/overview.md +79 -0
- package/template/content/table/zh-hans/installation.md +56 -0
- package/template/content/table/zh-hans/overview.md +79 -0
- package/template/content/virtual/docs.config.json +18 -0
- package/template/content/virtual/en/guides/dynamic-sizing.md +129 -0
- package/template/content/virtual/en/installation.md +57 -0
- package/template/content/virtual/en/overview.md +74 -0
- package/template/content/virtual/en/quick-start.md +114 -0
- package/template/content/virtual/ja/installation.md +57 -0
- package/template/content/virtual/ja/overview.md +74 -0
- package/template/content/virtual/zh-hans/installation.md +57 -0
- package/template/content/virtual/zh-hans/overview.md +74 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Configuration
|
|
3
|
+
description: Complete reference for the docs-i18n configuration file and all available options.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configuration
|
|
7
|
+
|
|
8
|
+
docs-i18n is configured via a `docs-i18n.config.ts` file in your project root. Use the `defineConfig` helper for type safety.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { defineConfig } from 'docs-i18n';
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
// ... options
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Full Config Reference
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
interface DocsI18nConfig {
|
|
22
|
+
projects: Record<string, ProjectConfig>;
|
|
23
|
+
languages: string[];
|
|
24
|
+
cacheDir?: string;
|
|
25
|
+
translatableFields?: string[];
|
|
26
|
+
include?: string[];
|
|
27
|
+
context?: string;
|
|
28
|
+
llm?: {
|
|
29
|
+
provider?: 'openrouter' | 'openai' | 'anthropic';
|
|
30
|
+
model?: string;
|
|
31
|
+
apiKey?: string;
|
|
32
|
+
contextLength?: number;
|
|
33
|
+
maxTokens?: number;
|
|
34
|
+
};
|
|
35
|
+
onTranslated?: (info: {
|
|
36
|
+
project: string;
|
|
37
|
+
version: string;
|
|
38
|
+
lang: string;
|
|
39
|
+
}) => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ProjectConfig {
|
|
43
|
+
sources: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
### `projects`
|
|
50
|
+
|
|
51
|
+
**Required.** A map of project IDs to their configuration. Each project has a `sources` object mapping version names to source directories (relative to project root).
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
projects: {
|
|
55
|
+
mydocs: {
|
|
56
|
+
sources: {
|
|
57
|
+
latest: 'content/docs',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
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
|
+
|
|
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
|
+
```
|
|
77
|
+
|
|
78
|
+
**Single project:**
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
projects: {
|
|
82
|
+
docs: {
|
|
83
|
+
sources: { latest: 'content/docs' },
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
content/docs/ ← source files (English)
|
|
90
|
+
getting-started.md
|
|
91
|
+
api-reference.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Multi-project:**
|
|
95
|
+
|
|
96
|
+
When you have multiple projects, keys become compound: `project/version` (e.g., `query/latest`, `table/v1`).
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
projects: {
|
|
100
|
+
query: {
|
|
101
|
+
sources: {
|
|
102
|
+
v5: 'content/query/v5',
|
|
103
|
+
v4: 'content/query/v4',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
table: {
|
|
107
|
+
sources: {
|
|
108
|
+
latest: 'content/table/latest',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
```
|
|
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
|
+
|
|
121
|
+
### `languages`
|
|
122
|
+
|
|
123
|
+
**Required.** An array of target language codes to translate into. These codes are used as identifiers in the cache and output directories.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
languages: ['zh-hans', 'ja', 'es', 'ko', 'fr', 'de']
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Common language codes:
|
|
130
|
+
|
|
131
|
+
| Code | Language |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `zh-hans` | Simplified Chinese |
|
|
134
|
+
| `zh-hant` | Traditional Chinese |
|
|
135
|
+
| `ja` | Japanese |
|
|
136
|
+
| `ko` | Korean |
|
|
137
|
+
| `es` | Spanish |
|
|
138
|
+
| `fr` | French |
|
|
139
|
+
| `de` | German |
|
|
140
|
+
| `pt` | Portuguese |
|
|
141
|
+
| `ru` | Russian |
|
|
142
|
+
| `ar` | Arabic |
|
|
143
|
+
| `vi` | Vietnamese |
|
|
144
|
+
| `hi` | Hindi |
|
|
145
|
+
| `th` | Thai |
|
|
146
|
+
|
|
147
|
+
### `cacheDir`
|
|
148
|
+
|
|
149
|
+
**Optional.** Directory for the SQLite cache database and assembled output. Default: `'.cache'`.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
cacheDir: '.cache'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The cache database is stored at `<cacheDir>/translations.db`. Assembled output files are written to `<cacheDir>/content/<version>/<lang>/`.
|
|
156
|
+
|
|
157
|
+
### `translatableFields`
|
|
158
|
+
|
|
159
|
+
**Optional.** Frontmatter YAML fields to translate. Supports dot notation for nested fields. Default: `['title', 'description', 'nav_title']`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
translatableFields: ['title', 'description', 'nav_title', 'related.title', 'related.description']
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
When a frontmatter block is detected, only these fields are extracted and sent to the LLM as plain text. All other frontmatter fields are preserved unchanged. After translation, the frontmatter is reconstructed with translated values while maintaining original YAML formatting.
|
|
166
|
+
|
|
167
|
+
### `include`
|
|
168
|
+
|
|
169
|
+
**Optional.** Glob patterns for files to include. Default: `['**/*.mdx', '**/*.md']`.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
include: ['**/*.mdx', '**/*.md']
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Files not matching these patterns are ignored during scanning, translation, and assembly.
|
|
176
|
+
|
|
177
|
+
### `context`
|
|
178
|
+
|
|
179
|
+
**Optional.** Project context injected into the LLM system prompt. Use this to provide domain-specific information that helps the LLM produce better translations.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
context: 'TanStack Query is a data-fetching and state management library for React, Vue, Solid, and Angular. It was previously known as React Query.'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This text is appended to the system prompt under a "CONTEXT" section, giving the LLM background about your project, terminology preferences, or naming conventions.
|
|
186
|
+
|
|
187
|
+
### `llm`
|
|
188
|
+
|
|
189
|
+
**Optional.** LLM provider configuration.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
llm: {
|
|
193
|
+
provider: 'openrouter',
|
|
194
|
+
model: 'qwen/qwen3.5-flash-02-23',
|
|
195
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
196
|
+
contextLength: 1_000_000,
|
|
197
|
+
maxTokens: 65536,
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `llm.provider`
|
|
202
|
+
|
|
203
|
+
The API provider. Default: `'openrouter'`.
|
|
204
|
+
|
|
205
|
+
| Provider | Base URL | API Key Env Var |
|
|
206
|
+
| --- | --- | --- |
|
|
207
|
+
| `'openrouter'` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` |
|
|
208
|
+
| `'openai'` | `https://api.deepseek.com` | `OPENAI_API_KEY` |
|
|
209
|
+
| `'anthropic'` | `https://api.anthropic.com/v1` | `ANTHROPIC_API_KEY` |
|
|
210
|
+
|
|
211
|
+
All providers use the OpenAI-compatible chat completions API.
|
|
212
|
+
|
|
213
|
+
#### `llm.model`
|
|
214
|
+
|
|
215
|
+
The model identifier. Defaults vary by provider:
|
|
216
|
+
|
|
217
|
+
- `openrouter`: `'deepseek/deepseek-chat-v3-0324:free'`
|
|
218
|
+
- `openai`: `'deepseek-chat'`
|
|
219
|
+
- `anthropic`: `'claude-sonnet-4-20250514'`
|
|
220
|
+
|
|
221
|
+
You can also override the model per command with `--model`.
|
|
222
|
+
|
|
223
|
+
#### `llm.apiKey`
|
|
224
|
+
|
|
225
|
+
API key for the provider. Falls back to the corresponding environment variable (`OPENROUTER_API_KEY`, `OPENAI_API_KEY`, or `ANTHROPIC_API_KEY`). Can also be overridden per command with `--api-key`.
|
|
226
|
+
|
|
227
|
+
#### `llm.contextLength`
|
|
228
|
+
|
|
229
|
+
Total context window of the model in tokens. Default: `32768`. Used to calculate how many translation nodes fit in a single API call. Set this to match your model's actual context window for optimal chunking.
|
|
230
|
+
|
|
231
|
+
#### `llm.maxTokens`
|
|
232
|
+
|
|
233
|
+
Maximum output tokens per API call. Default: `16384`. The actual output budget is calculated as `min(maxTokens, contextLength - 4096)`. The translator detects truncated output (when `finish_reason` is `'length'`) and retries.
|
|
234
|
+
|
|
235
|
+
### `onTranslated`
|
|
236
|
+
|
|
237
|
+
**Optional.** Async callback invoked after translation completes for a project/version/language combination.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
onTranslated: async ({ project, version, lang }) => {
|
|
241
|
+
console.log(`Finished translating ${project}/${version} to ${lang}`);
|
|
242
|
+
// Trigger a build, send a notification, etc.
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Example Configurations
|
|
247
|
+
|
|
248
|
+
### Simple single-project setup
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { defineConfig } from 'docs-i18n';
|
|
252
|
+
|
|
253
|
+
export default defineConfig({
|
|
254
|
+
projects: {
|
|
255
|
+
docs: {
|
|
256
|
+
sources: { latest: 'docs' },
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
languages: ['zh-hans'],
|
|
260
|
+
llm: {
|
|
261
|
+
provider: 'openrouter',
|
|
262
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
263
|
+
model: 'deepseek/deepseek-chat-v3-0324:free',
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Multi-project monorepo (e.g., TanStack)
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { defineConfig } from 'docs-i18n';
|
|
272
|
+
|
|
273
|
+
export default defineConfig({
|
|
274
|
+
projects: {
|
|
275
|
+
query: {
|
|
276
|
+
sources: {
|
|
277
|
+
latest: 'content/query/latest',
|
|
278
|
+
v4: 'content/query/v4',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
table: {
|
|
282
|
+
sources: { latest: 'content/table/latest' },
|
|
283
|
+
},
|
|
284
|
+
router: {
|
|
285
|
+
sources: { latest: 'content/router/latest' },
|
|
286
|
+
},
|
|
287
|
+
blog: {
|
|
288
|
+
sources: { latest: 'content/blog' },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
languages: ['zh-hans', 'ja', 'ko', 'es'],
|
|
292
|
+
cacheDir: '.cache',
|
|
293
|
+
translatableFields: ['title', 'description', 'nav_title'],
|
|
294
|
+
include: ['**/*.md'],
|
|
295
|
+
context: 'TanStack provides high-quality open-source libraries for web development including Query, Router, Table, and Form.',
|
|
296
|
+
llm: {
|
|
297
|
+
provider: 'openrouter',
|
|
298
|
+
model: 'qwen/qwen3.5-flash-02-23',
|
|
299
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
300
|
+
contextLength: 1_000_000,
|
|
301
|
+
maxTokens: 65536,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Using Anthropic
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { defineConfig } from 'docs-i18n';
|
|
310
|
+
|
|
311
|
+
export default defineConfig({
|
|
312
|
+
projects: {
|
|
313
|
+
docs: {
|
|
314
|
+
sources: { latest: 'content/docs' },
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
languages: ['ja', 'fr'],
|
|
318
|
+
llm: {
|
|
319
|
+
provider: 'anthropic',
|
|
320
|
+
model: 'claude-sonnet-4-20250514',
|
|
321
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
322
|
+
contextLength: 200000,
|
|
323
|
+
maxTokens: 16384,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### With post-translation hook
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { defineConfig } from 'docs-i18n';
|
|
332
|
+
import { exec } from 'node:child_process';
|
|
333
|
+
|
|
334
|
+
export default defineConfig({
|
|
335
|
+
projects: {
|
|
336
|
+
docs: {
|
|
337
|
+
sources: { latest: 'content/docs' },
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
languages: ['zh-hans'],
|
|
341
|
+
llm: {
|
|
342
|
+
provider: 'openrouter',
|
|
343
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
344
|
+
},
|
|
345
|
+
onTranslated: async ({ project, version, lang }) => {
|
|
346
|
+
// Rebuild the site after translation
|
|
347
|
+
exec('npm run build');
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deployment
|
|
3
|
+
description: Using docs-i18n in CI/CD pipelines, deploying the admin dashboard, and runtime translation with Cloudflare D1.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deployment
|
|
7
|
+
|
|
8
|
+
## CI/CD with GitHub Actions
|
|
9
|
+
|
|
10
|
+
docs-i18n works well in CI/CD pipelines. Since translations are cached in SQLite, you only pay for API calls on new or changed content.
|
|
11
|
+
|
|
12
|
+
### Basic translation workflow
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
name: Translate Docs
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
paths:
|
|
20
|
+
- 'content/**'
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
translate:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- uses: actions/setup-node@v4
|
|
29
|
+
with:
|
|
30
|
+
node-version: 20
|
|
31
|
+
|
|
32
|
+
- run: npm ci
|
|
33
|
+
|
|
34
|
+
# Restore the SQLite cache from a previous run
|
|
35
|
+
- uses: actions/cache@v4
|
|
36
|
+
with:
|
|
37
|
+
path: .cache
|
|
38
|
+
key: i18n-cache-${{ hashFiles('content/**') }}
|
|
39
|
+
restore-keys: |
|
|
40
|
+
i18n-cache-
|
|
41
|
+
|
|
42
|
+
# Rescan source files to detect changes
|
|
43
|
+
- run: npx docs-i18n rescan
|
|
44
|
+
|
|
45
|
+
# Translate all configured languages
|
|
46
|
+
- run: npx docs-i18n translate --lang zh-hans
|
|
47
|
+
env:
|
|
48
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
49
|
+
|
|
50
|
+
- run: npx docs-i18n translate --lang ja
|
|
51
|
+
env:
|
|
52
|
+
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
53
|
+
|
|
54
|
+
# Assemble output files
|
|
55
|
+
- run: npx docs-i18n assemble
|
|
56
|
+
|
|
57
|
+
# Commit translated files (or use them in a build step)
|
|
58
|
+
- uses: stefanzweifel/git-auto-commit-action@v5
|
|
59
|
+
with:
|
|
60
|
+
commit_message: 'chore: update translations'
|
|
61
|
+
file_pattern: '.cache/content/**'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Tips for CI/CD
|
|
65
|
+
|
|
66
|
+
- **Cache the SQLite database.** The `.cache/translations.db` file contains all cached translations. Restoring it between runs avoids re-translating unchanged content. Use `actions/cache` with a key based on your content files.
|
|
67
|
+
- **Run `rescan` before `translate`.** This detects new, changed, and deleted source files, and cleans up orphaned translations.
|
|
68
|
+
- **Use `--dry-run` in PR checks.** Add a CI step that runs `docs-i18n translate --lang zh-hans --dry-run` to report how many nodes need translation without spending API credits.
|
|
69
|
+
- **Limit concurrency.** In CI, you may want `--concurrency 1` to avoid rate limiting from your LLM provider.
|
|
70
|
+
- **Set `--max` for budgeting.** Use `--max 10` to cap the number of API calls per run if you want to translate incrementally across multiple CI runs.
|
|
71
|
+
|
|
72
|
+
### PR status check
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
name: Translation Status
|
|
76
|
+
on:
|
|
77
|
+
pull_request:
|
|
78
|
+
paths:
|
|
79
|
+
- 'content/**'
|
|
80
|
+
|
|
81
|
+
jobs:
|
|
82
|
+
check:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
- uses: actions/setup-node@v4
|
|
87
|
+
with:
|
|
88
|
+
node-version: 20
|
|
89
|
+
- run: npm ci
|
|
90
|
+
|
|
91
|
+
- uses: actions/cache@v4
|
|
92
|
+
with:
|
|
93
|
+
path: .cache
|
|
94
|
+
key: i18n-cache-${{ hashFiles('content/**') }}
|
|
95
|
+
restore-keys: |
|
|
96
|
+
i18n-cache-
|
|
97
|
+
|
|
98
|
+
- run: npx docs-i18n rescan
|
|
99
|
+
- run: npx docs-i18n status
|
|
100
|
+
- run: npx docs-i18n translate --lang zh-hans --dry-run
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Deployment Architecture
|
|
104
|
+
|
|
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
|
+
```
|
|
125
|
+
|
|
126
|
+
## Admin Dashboard
|
|
127
|
+
|
|
128
|
+
The admin dashboard is pre-built and requires no additional dependencies. It runs as a local Node.js server.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx docs-i18n admin
|
|
132
|
+
# or
|
|
133
|
+
npx docs-i18n admin --port 4000
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
It reads your `docs-i18n.config.ts` to discover projects, versions, and languages.
|
|
137
|
+
|
|
138
|
+
## Runtime Translation with D1
|
|
139
|
+
|
|
140
|
+
For SSR sites that fetch markdown at runtime (e.g., TanStack Start on Cloudflare Workers), docs-i18n provides a lightweight runtime translator that queries Cloudflare D1 for cached translations.
|
|
141
|
+
|
|
142
|
+
### How it works
|
|
143
|
+
|
|
144
|
+
The `createTranslator` function from `docs-i18n/serve` creates a translator instance. When `translate()` is called:
|
|
145
|
+
|
|
146
|
+
1. The markdown is parsed into AST nodes using the same parser as the CLI.
|
|
147
|
+
2. Translatable nodes' MD5 keys are collected.
|
|
148
|
+
3. A batch query is sent to D1 to look up all translations for the given language.
|
|
149
|
+
4. The content is reassembled: translated nodes use the D1 cache value, untranslated nodes fall back to the original English text.
|
|
150
|
+
|
|
151
|
+
### Setup
|
|
152
|
+
|
|
153
|
+
#### 1. Export translations to D1
|
|
154
|
+
|
|
155
|
+
First, translate your content using the CLI as normal. Then export the SQLite cache to D1. You can use Wrangler to create a D1 database and import the translations:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Create a D1 database
|
|
159
|
+
wrangler d1 create docs-translations
|
|
160
|
+
|
|
161
|
+
# Export the translations table from your local SQLite
|
|
162
|
+
sqlite3 .cache/translations.db ".dump translations" > translations.sql
|
|
163
|
+
|
|
164
|
+
# Import into D1
|
|
165
|
+
wrangler d1 execute docs-translations --file=translations.sql
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Make sure the D1 database has the same `translations` table schema:
|
|
169
|
+
|
|
170
|
+
```sql
|
|
171
|
+
CREATE TABLE IF NOT EXISTS translations (
|
|
172
|
+
lang TEXT NOT NULL,
|
|
173
|
+
key TEXT NOT NULL,
|
|
174
|
+
value TEXT NOT NULL,
|
|
175
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
176
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
177
|
+
PRIMARY KEY (lang, key)
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### 2. Use the translator in your Worker
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { createTranslator } from 'docs-i18n/serve';
|
|
185
|
+
|
|
186
|
+
const translator = createTranslator();
|
|
187
|
+
|
|
188
|
+
// In your request handler (e.g., TanStack Start server function):
|
|
189
|
+
export async function getTranslatedDoc(
|
|
190
|
+
repo: string,
|
|
191
|
+
branch: string,
|
|
192
|
+
filePath: string,
|
|
193
|
+
lang: string,
|
|
194
|
+
env: { DB: D1Database },
|
|
195
|
+
) {
|
|
196
|
+
// Fetch the English markdown from GitHub
|
|
197
|
+
const enMarkdown = await fetchFromGitHub(repo, branch, filePath);
|
|
198
|
+
|
|
199
|
+
// If the requested language is English, return as-is
|
|
200
|
+
if (lang === 'en') return enMarkdown;
|
|
201
|
+
|
|
202
|
+
// Translate using D1 cache
|
|
203
|
+
const translated = await translator.translate(enMarkdown, lang, env.DB);
|
|
204
|
+
return translated;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### 3. Bind D1 in wrangler.toml
|
|
209
|
+
|
|
210
|
+
```toml
|
|
211
|
+
[[d1_databases]]
|
|
212
|
+
binding = "DB"
|
|
213
|
+
database_name = "docs-translations"
|
|
214
|
+
database_id = "your-database-id"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Performance considerations
|
|
218
|
+
|
|
219
|
+
- The translator uses a single batch query to D1, so latency scales with the number of translatable nodes (typically under 50ms for a standard documentation page).
|
|
220
|
+
- English content (`lang === 'en'`) short-circuits immediately without parsing or querying.
|
|
221
|
+
- The parser runs on every request since the English source is fetched at runtime. For heavy traffic, consider caching the translated output at the edge.
|
|
222
|
+
- Untranslated nodes gracefully fall back to English, so partial translations work without errors.
|