docs-i18n 0.8.1 → 0.8.2

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 (79) hide show
  1. package/admin/dist/server/server.js +20 -20
  2. package/package.json +1 -1
  3. package/template/app/utils/content-loader.ts +4 -0
  4. package/template/app/utils/docs.server.ts +7 -0
  5. package/template/content/blog/en/announcing-query-v5.md +110 -0
  6. package/template/content/blog/en/hello-world.md +26 -0
  7. package/template/content/blog/en/i18n-best-practices.md +57 -0
  8. package/template/content/blog/en/react-query-vs-swr.md +100 -0
  9. package/template/content/blog/en/state-management-2024.md +143 -0
  10. package/template/content/blog/en/tanstack-router-1.0.md +121 -0
  11. package/template/content/blog/ja/announcing-query-v5.md +110 -0
  12. package/template/content/blog/ja/hello-world.md +26 -0
  13. package/template/content/blog/zh-hans/announcing-query-v5.md +93 -0
  14. package/template/content/blog/zh-hans/hello-world.md +26 -0
  15. package/template/content/docs-i18n/docs.config.json +25 -0
  16. package/template/content/docs-i18n/en/architecture.md +222 -0
  17. package/template/content/docs-i18n/en/configuration.md +331 -0
  18. package/template/content/docs-i18n/en/deployment.md +209 -0
  19. package/template/content/docs-i18n/en/getting-started.md +168 -0
  20. package/template/content/docs.config.json +25 -0
  21. package/template/content/en/admin.md +151 -0
  22. package/template/content/en/architecture.md +222 -0
  23. package/template/content/en/cli.md +269 -0
  24. package/template/content/en/configuration.md +331 -0
  25. package/template/content/en/deployment.md +209 -0
  26. package/template/content/en/getting-started.md +168 -0
  27. package/template/content/form/docs.config.json +18 -0
  28. package/template/content/form/en/guides/validation.md +175 -0
  29. package/template/content/form/en/installation.md +63 -0
  30. package/template/content/form/en/overview.md +71 -0
  31. package/template/content/form/en/quick-start.md +121 -0
  32. package/template/content/form/ja/installation.md +63 -0
  33. package/template/content/form/ja/overview.md +71 -0
  34. package/template/content/form/zh-hans/installation.md +63 -0
  35. package/template/content/form/zh-hans/overview.md +71 -0
  36. package/template/content/query/docs.config.json +32 -0
  37. package/template/content/query/en/guides/mutations.md +126 -0
  38. package/template/content/query/en/guides/pagination.md +98 -0
  39. package/template/content/query/en/guides/queries.md +120 -0
  40. package/template/content/query/en/installation.md +78 -0
  41. package/template/content/query/en/overview.md +72 -0
  42. package/template/content/query/en/quick-start.md +108 -0
  43. package/template/content/query/ja/installation.md +78 -0
  44. package/template/content/query/ja/overview.md +72 -0
  45. package/template/content/query/zh-hans/guides/mutations.md +126 -0
  46. package/template/content/query/zh-hans/guides/pagination.md +98 -0
  47. package/template/content/query/zh-hans/guides/queries.md +120 -0
  48. package/template/content/query/zh-hans/installation.md +95 -0
  49. package/template/content/query/zh-hans/overview.md +72 -0
  50. package/template/content/query/zh-hans/quick-start.md +108 -0
  51. package/template/content/router/docs.config.json +18 -0
  52. package/template/content/router/en/guides/routing-concepts.md +131 -0
  53. package/template/content/router/en/installation.md +57 -0
  54. package/template/content/router/en/overview.md +74 -0
  55. package/template/content/router/en/quick-start.md +88 -0
  56. package/template/content/router/ja/installation.md +57 -0
  57. package/template/content/router/ja/overview.md +78 -0
  58. package/template/content/router/zh-hans/guides/routing-concepts.md +131 -0
  59. package/template/content/router/zh-hans/installation.md +57 -0
  60. package/template/content/router/zh-hans/overview.md +81 -0
  61. package/template/content/router/zh-hans/quick-start.md +88 -0
  62. package/template/content/table/docs.config.json +18 -0
  63. package/template/content/table/en/guides/column-definitions.md +135 -0
  64. package/template/content/table/en/installation.md +56 -0
  65. package/template/content/table/en/overview.md +79 -0
  66. package/template/content/table/en/quick-start.md +112 -0
  67. package/template/content/table/ja/installation.md +56 -0
  68. package/template/content/table/ja/overview.md +79 -0
  69. package/template/content/table/zh-hans/installation.md +56 -0
  70. package/template/content/table/zh-hans/overview.md +79 -0
  71. package/template/content/virtual/docs.config.json +18 -0
  72. package/template/content/virtual/en/guides/dynamic-sizing.md +129 -0
  73. package/template/content/virtual/en/installation.md +57 -0
  74. package/template/content/virtual/en/overview.md +74 -0
  75. package/template/content/virtual/en/quick-start.md +114 -0
  76. package/template/content/virtual/ja/installation.md +57 -0
  77. package/template/content/virtual/ja/overview.md +74 -0
  78. package/template/content/virtual/zh-hans/installation.md +57 -0
  79. package/template/content/virtual/zh-hans/overview.md +74 -0
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: "TanStack Query v5 のリリースを発表"
3
+ published: "2024-10-01"
4
+ excerpt: "TanStack Query v5 が登場しました。シンプルになった API、強化された TypeScript サポート、改善された devtools、そしてより小さなバンドルサイズを実現しています。"
5
+ authors:
6
+ - "Tanner Linsley"
7
+ - "Dominik Dorfmeister"
8
+ ---
9
+
10
+ TanStack Query v5 のリリースを発表できることを大変嬉しく思います!このリリースはコミュニティの数ヶ月にわたる作業の成果であり、全体的に大幅な改善をもたらします。
11
+
12
+ ## 新機能
13
+
14
+ ### シンプルになった API
15
+
16
+ v5 で最も目に見える変更はシンプルになった API です。オブジェクト構文のオーバーロードを廃止し、すべてのフックで単一の一貫したオブジェクト構文を採用しました:
17
+
18
+ ```tsx
19
+ // v4 — 複数のオーバーロード
20
+ useQuery(['todos'], fetchTodos)
21
+ useQuery(['todos'], fetchTodos, { staleTime: 5000 })
22
+ useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
23
+
24
+ // v5 — 単一の一貫した API
25
+ useQuery({
26
+ queryKey: ['todos'],
27
+ queryFn: fetchTodos,
28
+ staleTime: 5000,
29
+ })
30
+ ```
31
+
32
+ これにより API の学習が容易になり、TypeScript の推論も向上しました。
33
+
34
+ ### TypeScript サポートの強化
35
+
36
+ v5 には TypeScript の大幅な改善が含まれています:
37
+
38
+ - **厳密なクエリキーの型付け** — クエリキーが全体を通じて厳密に型付けされるようになりました
39
+ - **推論される戻り値の型** — `useQuery` が `queryFn` から戻り値の型を正しく推論します
40
+ - **型安全なエラーハンドリング** — 新しい `Error` ジェネリックパラメータによりエラーが適切に型付けされます
41
+
42
+ ```tsx
43
+ // エラーの型が推論され、型安全になります
44
+ const { data, error } = useQuery({
45
+ queryKey: ['user', userId],
46
+ queryFn: async () => {
47
+ const res = await fetch(`/api/users/${userId}`)
48
+ if (!res.ok) throw new ApiError(res.status, await res.text())
49
+ return res.json() as Promise<User>
50
+ },
51
+ })
52
+
53
+ // error は ApiError | null として型付けされます
54
+ if (error) {
55
+ console.log(error.status) // TypeScript はこれが存在することを知っています
56
+ }
57
+ ```
58
+
59
+ ### 新しい Devtools
60
+
61
+ Devtools は新しいデザイン、パフォーマンスの向上、新機能で完全に書き直されました:
62
+
63
+ - **クエリタイムライン** — 時間経過によるクエリフェッチの可視化
64
+ - **ミューテーションインスペクター** — ミューテーションの状態と変数を検査
65
+ - **キャッシュエクスプローラー** — クエリキャッシュ全体をブラウズ
66
+ - **オンライン/オフライン切替** — オフライン動作のテスト
67
+
68
+ ### バンドルサイズの削減
69
+
70
+ ツリーシェイキングの改善と非推奨 API の削除により、コアバンドルサイズを約 20% 削減しました。
71
+
72
+ | パッケージ | v4 | v5 | 変化 |
73
+ |---|---|---|---|
74
+ | `@tanstack/react-query` | 12.4 KB | 9.8 KB | -21% |
75
+ | `@tanstack/query-core` | 10.1 KB | 8.2 KB | -19% |
76
+
77
+ ## 破壊的変更
78
+
79
+ > [!WARNING]
80
+ > v5 にはいくつかの破壊的変更が含まれています。アップグレード前に完全な[マイグレーションガイド](/ja/query/docs/guides/migration-v5)をご確認ください。
81
+
82
+ 主な破壊的変更:
83
+
84
+ 1. **オーバーロードシグネチャの廃止** — すべてのフックがオブジェクトのみの構文を使用
85
+ 2. **`cacheTime` を `gcTime` にリネーム** — その目的(ガベージコレクション時間)をより適切に反映
86
+ 3. **`status: 'loading'` を `status: 'pending'` にリネーム** — Promise の用語に合わせて
87
+ 4. **`keepPreviousData` の廃止** — `placeholderData: keepPreviousData`(query core からインポート)に置き換え
88
+ 5. **TypeScript の最低バージョンが 4.7 に**
89
+
90
+ ## マイグレーション
91
+
92
+ マイグレーションの大部分を自動化する codemod を用意しています:
93
+
94
+ ```bash
95
+ npx @tanstack/query-codemod v5 ./src
96
+ ```
97
+
98
+ これにより最も一般的な変換が自動的に行われます。変更内容を確認し、十分にテストしてください。
99
+
100
+ ## ありがとうございます
101
+
102
+ このリリースを可能にしてくれた 100 人以上のコントリビューターに感謝します。TanStack Query v5 は、素晴らしいオープンソースコミュニティなしには実現できませんでした。
103
+
104
+ 今すぐ v5 を始めましょう:
105
+
106
+ ```bash
107
+ npm install @tanstack/react-query@latest
108
+ ```
109
+
110
+ Happy querying!
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: "Hello World:docs-i18n ブログの紹介"
3
+ published: "2024-01-15"
4
+ excerpt: "docs-i18n ブログへようこそ!プロジェクトの更新情報、チュートリアル、ドキュメント国際化のベストプラクティスを共有します。"
5
+ authors:
6
+ - "docs-i18n Team"
7
+ ---
8
+
9
+ docs-i18n ブログへようこそ!ここでは、プロジェクトの更新情報、チュートリアル、ドキュメント国際化のベストプラクティスを共有します。
10
+
11
+ ## docs-i18n とは?
12
+
13
+ docs-i18n は、多言語ドキュメントサイトを簡単に維持できるようにする汎用ドキュメント翻訳エンジンです。以下を提供します:
14
+
15
+ - **ファイルシステムベースのコンテンツ読み込み**(i18n フォールバック付き)
16
+ - **Markdown レンダリング**(シンタックスハイライトとフレームワーク対応コンテンツ)
17
+ - **管理インターフェース**(翻訳の管理用)
18
+ - **CLI ツール**(翻訳ワークフローの自動化用)
19
+
20
+ ## はじめに
21
+
22
+ [入門ガイド](/ja/getting-started)をご覧ください。docs-i18n のプロジェクトへの導入方法を学べます。
23
+
24
+ ## 今後にご期待ください
25
+
26
+ 今後のリリースでは、エキサイティングな新機能を予定しています。最新の情報はこのブログでお届けします!
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: "TanStack Query v5 正式发布"
3
+ published: "2024-10-01"
4
+ excerpt: "TanStack Query v5 带来了简化的 API、更好的 TypeScript 支持、改进的开发工具和更小的包体积。以下是你需要了解的一切。"
5
+ authors:
6
+ - "Tanner Linsley"
7
+ - "Dominik Dorfmeister"
8
+ ---
9
+
10
+ 我们非常高兴地宣布 TanStack Query v5 正式发布!这个版本凝聚了社区数月的努力,在各方面都带来了重大改进。
11
+
12
+ ## 新特性
13
+
14
+ ### 简化的 API
15
+
16
+ v5 最直观的变化是简化的 API。我们移除了多种函数签名重载,统一使用单一的对象语法:
17
+
18
+ ```tsx
19
+ // v4 — 多种重载方式
20
+ useQuery(['todos'], fetchTodos)
21
+ useQuery(['todos'], fetchTodos, { staleTime: 5000 })
22
+ useQuery({ queryKey: ['todos'], queryFn: fetchTodos })
23
+
24
+ // v5 — 统一的对象语法
25
+ useQuery({
26
+ queryKey: ['todos'],
27
+ queryFn: fetchTodos,
28
+ staleTime: 5000,
29
+ })
30
+ ```
31
+
32
+ 这使得 API 更容易学习,并提供更好的 TypeScript 类型推断。
33
+
34
+ ### 更好的 TypeScript 支持
35
+
36
+ v5 包含重大的 TypeScript 改进:
37
+
38
+ - **严格的查询键类型** — 查询键在整个流程中都经过严格类型检查
39
+ - **推断的返回类型** — `useQuery` 能从 `queryFn` 正确推断返回类型
40
+ - **类型安全的错误处理** — 通过新的 `Error` 泛型参数实现类型安全的错误处理
41
+
42
+ ```tsx
43
+ const { data, error } = useQuery({
44
+ queryKey: ['user', userId],
45
+ queryFn: async () => {
46
+ const res = await fetch(`/api/users/${userId}`)
47
+ if (!res.ok) throw new ApiError(res.status, await res.text())
48
+ return res.json() as Promise<User>
49
+ },
50
+ })
51
+
52
+ // error 的类型为 ApiError | null
53
+ if (error) {
54
+ console.log(error.status) // TypeScript 知道这个属性存在
55
+ }
56
+ ```
57
+
58
+ ### 更小的包体积
59
+
60
+ 通过 tree-shaking 优化和移除废弃的 API,我们将核心包体积减少了约 20%。
61
+
62
+ | 包 | v4 | v5 | 变化 |
63
+ |---|---|---|---|
64
+ | `@tanstack/react-query` | 12.4 KB | 9.8 KB | -21% |
65
+ | `@tanstack/query-core` | 10.1 KB | 8.2 KB | -19% |
66
+
67
+ ## 破坏性变更
68
+
69
+ > [!WARNING]
70
+ > v5 包含多项破坏性变更。请在升级前仔细阅读完整的迁移指南。
71
+
72
+ 主要破坏性变更:
73
+
74
+ 1. **移除了重载签名** — 所有 hook 现在仅使用对象语法
75
+ 2. **`cacheTime` 重命名为 `gcTime`** — 更好地反映其用途(垃圾回收时间)
76
+ 3. **`status: 'loading'` 重命名为 `status: 'pending'`** — 与 Promise 术语保持一致
77
+ 4. **最低 TypeScript 版本要求为 4.7**
78
+
79
+ ## 迁移
80
+
81
+ 我们提供了 codemod 工具来自动化大部分迁移工作:
82
+
83
+ ```bash
84
+ npx @tanstack/query-codemod v5 ./src
85
+ ```
86
+
87
+ 立即开始使用 v5:
88
+
89
+ ```bash
90
+ npm install @tanstack/react-query@latest
91
+ ```
92
+
93
+ 感谢所有让这个版本成为可能的 100 多位贡献者!
@@ -0,0 +1,26 @@
1
+ ---
2
+ title: "你好世界:docs-i18n 博客上线"
3
+ published: "2024-01-15"
4
+ excerpt: "欢迎来到 docs-i18n 博客!我们将分享更新、教程和文档国际化的最佳实践。"
5
+ authors:
6
+ - "docs-i18n Team"
7
+ ---
8
+
9
+ 欢迎来到 docs-i18n 博客!这里我们将分享项目更新、教程和文档国际化的最佳实践。
10
+
11
+ ## 什么是 docs-i18n?
12
+
13
+ docs-i18n 是一个通用的文档翻译引擎,可以轻松维护多语言文档站点。它提供:
14
+
15
+ - **基于文件系统的内容加载**,支持 i18n 回退
16
+ - **Markdown 渲染**,支持语法高亮和框架感知内容
17
+ - **管理界面**,用于管理翻译
18
+ - **CLI 工具**,用于自动化翻译工作流
19
+
20
+ ## 开始使用
21
+
22
+ 查看我们的[入门指南](/zh-hans/getting-started)来了解如何为你的项目设置 docs-i18n。
23
+
24
+ ## 敬请期待
25
+
26
+ 我们计划在即将发布的版本中推出令人兴奋的新功能。关注这个博客获取最新更新!
@@ -0,0 +1,25 @@
1
+ {
2
+ "sections": [
3
+ {
4
+ "label": "Getting Started",
5
+ "children": [
6
+ { "label": "Introduction", "to": "getting-started" }
7
+ ]
8
+ },
9
+ {
10
+ "label": "Usage",
11
+ "children": [
12
+ { "label": "CLI Commands", "to": "cli" },
13
+ { "label": "Configuration", "to": "configuration" },
14
+ { "label": "Admin Dashboard", "to": "admin" }
15
+ ]
16
+ },
17
+ {
18
+ "label": "Advanced",
19
+ "children": [
20
+ { "label": "Architecture", "to": "architecture" },
21
+ { "label": "Deployment", "to": "deployment" }
22
+ ]
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,222 @@
1
+ ---
2
+ title: Architecture
3
+ description: How docs-i18n works internally -- the translation pipeline, AST parsing, caching, and chunking strategies.
4
+ ---
5
+
6
+ # Architecture
7
+
8
+ This document explains how docs-i18n works internally. Understanding the pipeline helps you tune configuration, debug issues, and contribute to the project.
9
+
10
+ ## Translation Pipeline
11
+
12
+ The end-to-end flow is:
13
+
14
+ ```
15
+ Source files (EN)
16
+ |
17
+ v
18
+ [1. Normalize] -- Ensure JSX tags are separated by blank lines
19
+ |
20
+ v
21
+ [2. Parse] -- remark AST -> flat list of typed nodes
22
+ |
23
+ v
24
+ [3. Hash] -- MD5 of each translatable node's text
25
+ |
26
+ v
27
+ [4. Chunk] -- Group nodes into chunks that fit the LLM context window
28
+ |
29
+ v
30
+ [5. Translate] -- Send JSON to LLM, receive JSON translations
31
+ |
32
+ v
33
+ [6. Cache] -- Store translations in SQLite keyed by (lang, md5)
34
+ |
35
+ v
36
+ [7. Assemble] -- EN source + cache -> translated output files
37
+ ```
38
+
39
+ ## Step 1: Normalization
40
+
41
+ The `normalize()` function (`src/core/normalize.ts`) preprocesses MDX content to ensure that JSX tags like `<AppOnly>`, `<PagesOnly>`, `<details>`, and `<div>` are separated from surrounding content by blank lines. This ensures remark parses them as independent HTML nodes rather than merging them with adjacent text.
42
+
43
+ For example, this input:
44
+
45
+ ```
46
+ <AppOnly>
47
+ Some text here
48
+ </AppOnly>
49
+ ```
50
+
51
+ Becomes:
52
+
53
+ ```
54
+ <AppOnly>
55
+
56
+ Some text here
57
+
58
+ </AppOnly>
59
+ ```
60
+
61
+ ## Step 2: AST Parsing
62
+
63
+ The `parseMdx()` function (`src/core/parser.ts`) uses remark to parse markdown into a flat list of `ParsedNode` objects. Each node has:
64
+
65
+ - `type` -- The AST node type: `paragraph`, `heading`, `list`, `blockquote`, `code`, `html`, `thematicBreak`, or `frontmatter`.
66
+ - `rawText` -- The raw text content from the normalized source.
67
+ - `needsTranslation` -- Whether this node contains human-readable text.
68
+ - `md5` -- MD5 hash of the raw text (only for translatable nodes).
69
+ - `startOffset` / `endOffset` -- Character offsets in the normalized content.
70
+
71
+ **Translatable node types:** `paragraph`, `heading`, `list`, `blockquote`, and `html` nodes that contain non-tag text (e.g., `<summary>Examples</summary>`).
72
+
73
+ **Non-translatable:** `code` blocks, `thematicBreak`, and pure HTML/JSX tags (self-closing tags like `<Check size={18} />`, opening/closing tags like `<AppOnly></AppOnly>`).
74
+
75
+ **Frontmatter handling:** If the content starts with `---`, the parser detects YAML frontmatter and emits it as a single `frontmatter` node spanning from the opening `---` to the closing `---`. The frontmatter module (`src/core/frontmatter.ts`) then extracts only the configured translatable fields (e.g., `title`, `description`) using the `yaml` library, sends them to the LLM as plain text, and reconstructs the YAML with translated values while preserving all other fields and formatting.
76
+
77
+ ## Step 3: MD5 Hashing
78
+
79
+ Each translatable node's raw text is hashed with MD5 to produce a stable key. This key is used for:
80
+
81
+ - **Deduplication** -- identical content appearing in multiple files (or even multiple projects) shares a single translation.
82
+ - **Incremental updates** -- when source content changes, only the nodes with new MD5 hashes need translation. Unchanged nodes reuse their cached translations.
83
+ - **Heading differentiation** -- heading nodes include their level markers (`##`, `###`) in the hash, so "## Installation" and "### Installation" produce different keys.
84
+
85
+ ## Step 4: Smart Chunking
86
+
87
+ The translate command groups untranslated nodes into chunks that fit within the LLM's context window. The chunking algorithm (`src/commands/translate.ts`) accounts for:
88
+
89
+ - **Input budget** -- system prompt tokens + source text tokens. Estimated at `text.length / 4 + 80` tokens per node (accounting for JSON structure overhead).
90
+ - **Output budget** -- translated text tokens. Scaled by a per-language multiplier since different languages use tokens differently:
91
+
92
+ | Language | Multiplier | Reason |
93
+ | --- | --- | --- |
94
+ | Japanese, Korean, Hindi, Thai | 2.5x | CJK/Indic tokenization |
95
+ | Russian, Arabic, Ukrainian, Hebrew | 2.0x | Cyrillic/Arabic scripts |
96
+ | Chinese (Simplified/Traditional) | 1.5x | CJK but more concise |
97
+ | German, French, Portuguese | 1.3x | Slightly longer than English |
98
+ | Spanish, Vietnamese | 1.2x | Close to English length |
99
+ | Other languages | 2.0x | Safe default |
100
+
101
+ - **System prompt overhead** -- approximately 700 tokens for the translation prompt.
102
+ - **JSON structure overhead** -- approximately 80 tokens per key for JSON schema properties.
103
+ - **Safety margin** -- 85% of input budget and 75% of output budget are used to leave room for estimation errors.
104
+
105
+ When a chunk reaches its budget, a new chunk is started. This prevents context window overflow and output truncation.
106
+
107
+ ## Step 5: LLM Translation
108
+
109
+ The translator (`src/core/translator.ts`) uses structured JSON mode for translations:
110
+
111
+ **Input format:** A JSON object with a `nodes` array. Each node has a `key` (MD5), `type` (heading/paragraph/list/etc.), and `text` (content to translate).
112
+
113
+ ```json
114
+ {
115
+ "nodes": [
116
+ { "key": "a1b2c3...", "type": "heading", "text": "## Installation" },
117
+ { "key": "d4e5f6...", "type": "paragraph", "text": "Run the following command:" }
118
+ ]
119
+ }
120
+ ```
121
+
122
+ **Output format:** A flat JSON object mapping each key to its translation.
123
+
124
+ ```json
125
+ {
126
+ "a1b2c3...": "## \u5b89\u88c5",
127
+ "d4e5f6...": "\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a"
128
+ }
129
+ ```
130
+
131
+ **Robustness features:**
132
+
133
+ - **JSON repair** -- Handles common LLM JSON errors: unescaped newlines in strings, trailing commas, missing closing braces.
134
+ - **Thinking block stripping** -- Removes `<think>...</think>` blocks from reasoning models.
135
+ - **Unwrapping** -- If the model wraps output in `{"nodes": {...}}` or `{"translations": {...}}`, it is automatically unwrapped.
136
+ - **Key recovery** -- If the model corrupts an MD5 key (e.g., truncates it), the translator attempts fuzzy matching to recover the translation (up to 3 character differences).
137
+ - **Garbage detection** -- If more than 50% of translation values are identical, the model output is rejected.
138
+ - **Retry with backoff** -- Retries on 429 (rate limit), 503, 405, timeout, and connection errors. Uses exponential backoff starting at 2 seconds.
139
+ - **Model rotation** -- Supports a list of models to rotate through. Dead models (400/404 errors) are skipped. Rate-limited models (429) are deprioritized.
140
+ - **Truncation detection** -- If `finish_reason` is `'length'`, the output was truncated and the request is retried.
141
+
142
+ **Frontmatter translation:**
143
+
144
+ Frontmatter nodes are handled specially. Instead of sending the entire YAML block, the translator extracts individual translatable fields (e.g., `title`, `description`) and sends them as plain `paragraph` type nodes with virtual keys like `fm:<md5>:title`. After translation, the fields are reassembled into the original YAML structure using `reconstructFrontmatter()`.
145
+
146
+ ## Step 6: SQLite Cache
147
+
148
+ The `TranslationCache` class (`src/core/cache.ts`) manages all persistent state in a single SQLite database at `<cacheDir>/translations.db`.
149
+
150
+ **Schema:**
151
+
152
+ ```sql
153
+ -- EN source texts (deduplicated by MD5)
154
+ CREATE TABLE sources (
155
+ key TEXT PRIMARY KEY NOT NULL, -- MD5 hash
156
+ text TEXT NOT NULL, -- original English text
157
+ type TEXT NOT NULL DEFAULT 'paragraph'
158
+ );
159
+
160
+ -- Which files use each source node
161
+ CREATE TABLE source_files (
162
+ key TEXT NOT NULL, -- MD5 hash
163
+ file TEXT NOT NULL, -- relative file path
164
+ line INTEGER NOT NULL, -- line number
165
+ version TEXT NOT NULL DEFAULT 'latest',
166
+ PRIMARY KEY (version, key, file, line)
167
+ );
168
+
169
+ -- Translated texts per language
170
+ CREATE TABLE translations (
171
+ lang TEXT NOT NULL, -- language code
172
+ key TEXT NOT NULL, -- MD5 hash
173
+ value TEXT NOT NULL, -- translated text
174
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
175
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
176
+ PRIMARY KEY (lang, key)
177
+ );
178
+ ```
179
+
180
+ **Performance configuration:**
181
+
182
+ - **WAL mode** -- Write-Ahead Logging enables concurrent readers with a single writer. No locking issues during parallel translation and assembly.
183
+ - **busy_timeout = 5000** -- Wait up to 5 seconds for a lock before failing.
184
+ - **synchronous = NORMAL** -- Balanced between safety and performance.
185
+ - **WITHOUT ROWID** -- Tables use the primary key directly, avoiding an extra rowid column.
186
+ - **Immediate writes** -- All `set()` calls write directly to disk. No explicit save step needed.
187
+
188
+ **Key operations:**
189
+
190
+ - `get(lang, md5)` -- Look up a cached translation.
191
+ - `set(lang, md5, translation)` -- Store or update a translation (upsert).
192
+ - `untranslatedKeys(lang, version)` -- Find all source keys that have no translation for a given language.
193
+ - `fileCoverage(version, lang)` -- Get per-file translation coverage (used by the admin dashboard).
194
+ - `prune(lang, usedMd5s)` -- Remove translations whose keys are no longer referenced.
195
+ - `exportJsonl(lang, outputPath)` / `importJsonl(lang, inputPath)` -- Export/import translations in JSONL format for backup or migration.
196
+
197
+ **SQLite compatibility:**
198
+
199
+ The `openDatabase()` function (`src/core/sqlite.ts`) automatically detects the runtime environment. Under Bun, it uses `bun:sqlite`. Under Node.js, it uses `better-sqlite3`. Both expose the same interface.
200
+
201
+ ## Step 7: Assembly
202
+
203
+ The `assemble()` function (`src/core/assembler.ts`) produces a translated file from English source content and cached translations:
204
+
205
+ 1. Normalizes the source content.
206
+ 2. Parses it into AST nodes.
207
+ 3. For each node:
208
+ - **Non-translatable nodes** (code blocks, HTML tags) are kept as-is.
209
+ - **Translatable nodes with a cached translation** are replaced with the cached value.
210
+ - **Translatable nodes without a cache hit** are either wrapped in `<!-- NEEDS_TRANSLATION -->` markers (for the legacy whole-file translation mode) or fall back to the original English text (for assembled output files).
211
+ 4. Preserves all whitespace and newlines between nodes.
212
+
213
+ The `AssembleResult` includes statistics: `cachedCount`, `uncachedCount`, `totalTranslatable`, and whether all nodes were cached (`allCached`).
214
+
215
+ ## Validation
216
+
217
+ The `validate()` function (`src/core/validator.ts`) compares LLM output against the translation cache to detect and correct modifications to already-cached translations. It uses two alignment strategies:
218
+
219
+ - **Fast path** -- When the number of translatable nodes in the source and output match, nodes are aligned by index.
220
+ - **Anchor-based alignment** -- When node counts differ (the LLM merged or split paragraphs), cached translations serve as anchor points. The validator finds exact matches between output text and cached translations, then aligns nodes between anchors by type matching.
221
+
222
+ Cached translations always override LLM modifications, ensuring translation consistency across runs.