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,209 @@
|
|
|
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
|
+
## Admin Dashboard Deployment
|
|
104
|
+
|
|
105
|
+
The admin dashboard is a TanStack Start application that runs locally. It is designed for development use, not production deployment.
|
|
106
|
+
|
|
107
|
+
To start it:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx docs-i18n admin
|
|
111
|
+
# or
|
|
112
|
+
npx docs-i18n admin --port 4000
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The dashboard starts a Vite dev server pointed at the `src/admin` directory within the docs-i18n package. It reads your `docs-i18n.config.ts` to discover projects, versions, and languages.
|
|
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
|
+
```
|
|
124
|
+
|
|
125
|
+
## Runtime Translation with D1
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
### How it works
|
|
130
|
+
|
|
131
|
+
The `createTranslator` function from `docs-i18n/serve` creates a translator instance. When `translate()` is called:
|
|
132
|
+
|
|
133
|
+
1. The markdown is parsed into AST nodes using the same parser as the CLI.
|
|
134
|
+
2. Translatable nodes' MD5 keys are collected.
|
|
135
|
+
3. A batch query is sent to D1 to look up all translations for the given language.
|
|
136
|
+
4. The content is reassembled: translated nodes use the D1 cache value, untranslated nodes fall back to the original English text.
|
|
137
|
+
|
|
138
|
+
### Setup
|
|
139
|
+
|
|
140
|
+
#### 1. Export translations to D1
|
|
141
|
+
|
|
142
|
+
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:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Create a D1 database
|
|
146
|
+
wrangler d1 create docs-translations
|
|
147
|
+
|
|
148
|
+
# Export the translations table from your local SQLite
|
|
149
|
+
sqlite3 .cache/translations.db ".dump translations" > translations.sql
|
|
150
|
+
|
|
151
|
+
# Import into D1
|
|
152
|
+
wrangler d1 execute docs-translations --file=translations.sql
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Make sure the D1 database has the same `translations` table schema:
|
|
156
|
+
|
|
157
|
+
```sql
|
|
158
|
+
CREATE TABLE IF NOT EXISTS translations (
|
|
159
|
+
lang TEXT NOT NULL,
|
|
160
|
+
key TEXT NOT NULL,
|
|
161
|
+
value TEXT NOT NULL,
|
|
162
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
163
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
164
|
+
PRIMARY KEY (lang, key)
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### 2. Use the translator in your Worker
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { createTranslator } from 'docs-i18n/serve';
|
|
172
|
+
|
|
173
|
+
const translator = createTranslator();
|
|
174
|
+
|
|
175
|
+
// In your request handler (e.g., TanStack Start server function):
|
|
176
|
+
export async function getTranslatedDoc(
|
|
177
|
+
repo: string,
|
|
178
|
+
branch: string,
|
|
179
|
+
filePath: string,
|
|
180
|
+
lang: string,
|
|
181
|
+
env: { DB: D1Database },
|
|
182
|
+
) {
|
|
183
|
+
// Fetch the English markdown from GitHub
|
|
184
|
+
const enMarkdown = await fetchFromGitHub(repo, branch, filePath);
|
|
185
|
+
|
|
186
|
+
// If the requested language is English, return as-is
|
|
187
|
+
if (lang === 'en') return enMarkdown;
|
|
188
|
+
|
|
189
|
+
// Translate using D1 cache
|
|
190
|
+
const translated = await translator.translate(enMarkdown, lang, env.DB);
|
|
191
|
+
return translated;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### 3. Bind D1 in wrangler.toml
|
|
196
|
+
|
|
197
|
+
```toml
|
|
198
|
+
[[d1_databases]]
|
|
199
|
+
binding = "DB"
|
|
200
|
+
database_name = "docs-translations"
|
|
201
|
+
database_id = "your-database-id"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Performance considerations
|
|
205
|
+
|
|
206
|
+
- 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).
|
|
207
|
+
- English content (`lang === 'en'`) short-circuits immediately without parsing or querying.
|
|
208
|
+
- 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.
|
|
209
|
+
- Untranslated nodes gracefully fall back to English, so partial translations work without errors.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
description: Install docs-i18n and translate your first documentation file in minutes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Getting Started
|
|
7
|
+
|
|
8
|
+
## What is docs-i18n?
|
|
9
|
+
|
|
10
|
+
docs-i18n is a universal documentation translation engine. It parses markdown and MDX files into translatable AST nodes, translates them via LLM, caches results in SQLite, and assembles translated output files. It is framework-agnostic and works with any documentation site built on Next.js, Astro, TanStack Start, or similar tools.
|
|
11
|
+
|
|
12
|
+
Key characteristics:
|
|
13
|
+
|
|
14
|
+
- **Incremental** -- only changed or new content is sent to the LLM.
|
|
15
|
+
- **AST-based** -- uses remark to parse markdown into nodes, producing stable MD5 keys for deduplication.
|
|
16
|
+
- **SQLite-backed** -- concurrent-safe cache with WAL mode. No manual save steps.
|
|
17
|
+
- **Multi-project** -- supports multiple documentation projects and versions in a single config.
|
|
18
|
+
- **Admin dashboard** -- web UI for monitoring progress, managing jobs, and previewing translations.
|
|
19
|
+
- **Runtime serve** -- optional D1-compatible translator for SSR sites on Cloudflare Workers.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Node.js 20+ or Bun
|
|
24
|
+
- 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
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install docs-i18n
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or with Bun:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bun add docs-i18n
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### 1. Create a configuration file
|
|
42
|
+
|
|
43
|
+
Create `docs-i18n.config.ts` in your project root:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { defineConfig } from 'docs-i18n';
|
|
47
|
+
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
projects: {
|
|
50
|
+
mydocs: {
|
|
51
|
+
sources: {
|
|
52
|
+
latest: 'content/docs',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
languages: ['zh-hans', 'ja', 'es'],
|
|
57
|
+
llm: {
|
|
58
|
+
provider: 'openrouter',
|
|
59
|
+
model: 'deepseek/deepseek-chat-v3-0324:free',
|
|
60
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The `projects` object maps project names to their source directories. Each project can have multiple versions (e.g., `latest`, `v1`, `v2`). The `languages` array lists the target language codes to translate into.
|
|
66
|
+
|
|
67
|
+
### 2. Scan your source files
|
|
68
|
+
|
|
69
|
+
Before translating, scan your English source files to build the source index:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx docs-i18n rescan
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This parses every markdown/MDX file in your source directories, extracts translatable nodes, and stores them in the SQLite cache.
|
|
76
|
+
|
|
77
|
+
### 3. Check translation status
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx docs-i18n status
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This displays a progress bar for each language showing how many nodes have been translated.
|
|
84
|
+
|
|
85
|
+
### 4. Run your first translation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx docs-i18n translate --lang zh-hans
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This sends untranslated nodes to your configured LLM, receives translations, and stores them in the cache. Only new or changed content is translated -- cached translations are reused.
|
|
92
|
+
|
|
93
|
+
### 5. Assemble output files
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx docs-i18n assemble
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This produces translated markdown files by combining your English sources with cached translations. Output is written to `.cache/content/<version>/<lang>/` by default.
|
|
100
|
+
|
|
101
|
+
## Minimal Working Example
|
|
102
|
+
|
|
103
|
+
Given this project structure:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
my-docs/
|
|
107
|
+
content/
|
|
108
|
+
docs/
|
|
109
|
+
getting-started.md
|
|
110
|
+
api-reference.md
|
|
111
|
+
docs-i18n.config.ts
|
|
112
|
+
package.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With this config:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { defineConfig } from 'docs-i18n';
|
|
119
|
+
|
|
120
|
+
export default defineConfig({
|
|
121
|
+
projects: {
|
|
122
|
+
docs: {
|
|
123
|
+
sources: { latest: 'content/docs' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
languages: ['zh-hans'],
|
|
127
|
+
llm: {
|
|
128
|
+
provider: 'openrouter',
|
|
129
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
130
|
+
model: 'deepseek/deepseek-chat-v3-0324:free',
|
|
131
|
+
contextLength: 32768,
|
|
132
|
+
maxTokens: 16384,
|
|
133
|
+
},
|
|
134
|
+
context: 'MyProject is a TypeScript library for building web apps.',
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Run these commands:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx docs-i18n rescan
|
|
142
|
+
npx docs-i18n translate --lang zh-hans
|
|
143
|
+
npx docs-i18n assemble --lang zh-hans
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The translated files will appear at:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
.cache/content/latest/zh-hans/getting-started.md
|
|
150
|
+
.cache/content/latest/zh-hans/api-reference.md
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Adding npm scripts
|
|
154
|
+
|
|
155
|
+
Add these convenience scripts to your `package.json`:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"scripts": {
|
|
160
|
+
"i18n": "docs-i18n",
|
|
161
|
+
"i18n:status": "docs-i18n status",
|
|
162
|
+
"i18n:translate": "docs-i18n translate",
|
|
163
|
+
"i18n:assemble": "docs-i18n assemble",
|
|
164
|
+
"i18n:rescan": "docs-i18n rescan",
|
|
165
|
+
"i18n:admin": "docs-i18n admin"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sections": [
|
|
3
|
+
{
|
|
4
|
+
"label": "Getting Started",
|
|
5
|
+
"children": [
|
|
6
|
+
{ "label": "Overview", "to": "overview" },
|
|
7
|
+
{ "label": "Installation", "to": "installation" },
|
|
8
|
+
{ "label": "Quick Start", "to": "quick-start" }
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "Guides",
|
|
13
|
+
"children": [
|
|
14
|
+
{ "label": "Validation", "to": "guides/validation" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Validation
|
|
3
|
+
description: Learn how to validate form fields with TanStack Form
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Validation
|
|
7
|
+
|
|
8
|
+
TanStack Form provides powerful, flexible validation that works at both the field level and form level. Validation can run on change, on blur, on submit, or asynchronously.
|
|
9
|
+
|
|
10
|
+
## Field-Level Validation
|
|
11
|
+
|
|
12
|
+
The most common approach is to validate individual fields. You can add validators directly to each field:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { useForm } from '@tanstack/react-form'
|
|
16
|
+
|
|
17
|
+
function SignupForm() {
|
|
18
|
+
const form = useForm({
|
|
19
|
+
defaultValues: {
|
|
20
|
+
username: '',
|
|
21
|
+
email: '',
|
|
22
|
+
password: '',
|
|
23
|
+
},
|
|
24
|
+
onSubmit: async ({ value }) => {
|
|
25
|
+
console.log('Form submitted:', value)
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<form
|
|
31
|
+
onSubmit={(e) => {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
form.handleSubmit()
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<form.Field
|
|
37
|
+
name="username"
|
|
38
|
+
validators={{
|
|
39
|
+
onChange: ({ value }) => {
|
|
40
|
+
if (value.length < 3) {
|
|
41
|
+
return 'Username must be at least 3 characters'
|
|
42
|
+
}
|
|
43
|
+
return undefined
|
|
44
|
+
},
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{(field) => (
|
|
48
|
+
<div>
|
|
49
|
+
<label htmlFor={field.name}>Username</label>
|
|
50
|
+
<input
|
|
51
|
+
id={field.name}
|
|
52
|
+
value={field.state.value}
|
|
53
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
54
|
+
onBlur={field.handleBlur}
|
|
55
|
+
/>
|
|
56
|
+
{field.state.meta.errors.length > 0 && (
|
|
57
|
+
<p className="text-red-500">{field.state.meta.errors.join(', ')}</p>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</form.Field>
|
|
62
|
+
|
|
63
|
+
<button type="submit">Sign Up</button>
|
|
64
|
+
</form>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Validation Timing
|
|
70
|
+
|
|
71
|
+
TanStack Form supports multiple validation timings:
|
|
72
|
+
|
|
73
|
+
| Timing | When it runs | Use case |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `onChange` | Every keystroke | Real-time feedback |
|
|
76
|
+
| `onBlur` | When field loses focus | Less intrusive UX |
|
|
77
|
+
| `onSubmit` | When form is submitted | Final validation only |
|
|
78
|
+
| `onChangeAsync` | Debounced on change | API-backed validation |
|
|
79
|
+
| `onBlurAsync` | Async on blur | Server-side checks |
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<form.Field
|
|
83
|
+
name="email"
|
|
84
|
+
validators={{
|
|
85
|
+
onChange: ({ value }) => {
|
|
86
|
+
if (!value.includes('@')) return 'Invalid email format'
|
|
87
|
+
},
|
|
88
|
+
onChangeAsyncDebounceMs: 500,
|
|
89
|
+
onChangeAsync: async ({ value }) => {
|
|
90
|
+
const available = await checkEmailAvailable(value)
|
|
91
|
+
if (!available) return 'Email already taken'
|
|
92
|
+
},
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{(field) => <input {...field} />}
|
|
96
|
+
</form.Field>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Schema Validation with Adapters
|
|
100
|
+
|
|
101
|
+
TanStack Form integrates with popular schema validation libraries through adapters:
|
|
102
|
+
|
|
103
|
+
### Zod Adapter
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install @tanstack/zod-form-adapter zod
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { zodValidator } from '@tanstack/zod-form-adapter'
|
|
111
|
+
import { z } from 'zod'
|
|
112
|
+
|
|
113
|
+
const form = useForm({
|
|
114
|
+
defaultValues: { email: '' },
|
|
115
|
+
onSubmit: async ({ value }) => console.log(value),
|
|
116
|
+
validatorAdapter: zodValidator(),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Then in fields:
|
|
120
|
+
<form.Field
|
|
121
|
+
name="email"
|
|
122
|
+
validators={{
|
|
123
|
+
onChange: z.string().email('Invalid email'),
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{(field) => <input />}
|
|
127
|
+
</form.Field>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Valibot Adapter
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install @tanstack/valibot-form-adapter valibot
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { valibotValidator } from '@tanstack/valibot-form-adapter'
|
|
138
|
+
import * as v from 'valibot'
|
|
139
|
+
|
|
140
|
+
const form = useForm({
|
|
141
|
+
defaultValues: { email: '' },
|
|
142
|
+
onSubmit: async ({ value }) => console.log(value),
|
|
143
|
+
validatorAdapter: valibotValidator(),
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> [!NOTE]
|
|
148
|
+
> Schema adapters let you use the same schema definitions for both form validation and API validation, keeping your validation logic DRY.
|
|
149
|
+
|
|
150
|
+
## Form-Level Validation
|
|
151
|
+
|
|
152
|
+
You can also validate the entire form at once, useful for cross-field validation:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const form = useForm({
|
|
156
|
+
defaultValues: {
|
|
157
|
+
password: '',
|
|
158
|
+
confirmPassword: '',
|
|
159
|
+
},
|
|
160
|
+
validators: {
|
|
161
|
+
onChange: ({ value }) => {
|
|
162
|
+
if (value.password !== value.confirmPassword) {
|
|
163
|
+
return 'Passwords do not match'
|
|
164
|
+
}
|
|
165
|
+
return undefined
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
onSubmit: async ({ value }) => {
|
|
169
|
+
// handle submit
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> [!WARNING]
|
|
175
|
+
> Form-level validators run on every field change by default. For expensive validation logic, use `onBlur` or `onSubmit` timing instead.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Installation
|
|
3
|
+
description: How to install TanStack Form in your project
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Installation
|
|
7
|
+
|
|
8
|
+
## React
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @tanstack/react-form
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @tanstack/react-form
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
yarn add @tanstack/react-form
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bun add @tanstack/react-form
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Vue
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @tanstack/vue-form
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Angular
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @tanstack/angular-form
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Validation Adapters
|
|
39
|
+
|
|
40
|
+
TanStack Form supports popular validation libraries via adapters:
|
|
41
|
+
|
|
42
|
+
### Zod
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @tanstack/zod-form-adapter zod
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Valibot
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @tanstack/valibot-form-adapter valibot
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Yup
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @tanstack/yup-form-adapter yup
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- TypeScript 5.0+
|
|
63
|
+
- React 18+ / Vue 3+ / Angular 17+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TanStack Form Overview
|
|
3
|
+
description: Headless, performant, and type-safe form state management
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TanStack Form
|
|
7
|
+
|
|
8
|
+
TanStack Form is a headless, performant, and type-safe form state management library for TS/JS, React, Vue, and Angular.
|
|
9
|
+
|
|
10
|
+
## Why TanStack Form?
|
|
11
|
+
|
|
12
|
+
Most form libraries either sacrifice type safety for convenience or are tightly coupled to a specific framework. TanStack Form gives you both — full type safety from form values to validation errors, with adapters for React, Vue, and Angular.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Type-Safe** — Full TypeScript inference for form values, field names, and validation
|
|
17
|
+
- **Framework Agnostic** — Works with React, Vue, Angular, and more
|
|
18
|
+
- **Headless** — No UI opinions, bring your own components
|
|
19
|
+
- **Performant** — Field-level subscriptions, no unnecessary re-renders
|
|
20
|
+
- **Validation** — Built-in sync and async validation with adapter support for Zod, Valibot, and Yup
|
|
21
|
+
- **Arrays & Dynamic Fields** — First-class support for array fields
|
|
22
|
+
|
|
23
|
+
## Basic Example
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { useForm } from '@tanstack/react-form'
|
|
27
|
+
|
|
28
|
+
function SignupForm() {
|
|
29
|
+
const form = useForm({
|
|
30
|
+
defaultValues: {
|
|
31
|
+
firstName: '',
|
|
32
|
+
lastName: '',
|
|
33
|
+
email: '',
|
|
34
|
+
},
|
|
35
|
+
onSubmit: async ({ value }) => {
|
|
36
|
+
console.log(value)
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<form
|
|
42
|
+
onSubmit={(e) => {
|
|
43
|
+
e.preventDefault()
|
|
44
|
+
form.handleSubmit()
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<form.Field
|
|
48
|
+
name="firstName"
|
|
49
|
+
validators={{
|
|
50
|
+
onChange: ({ value }) =>
|
|
51
|
+
!value ? 'First name is required' : undefined,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{(field) => (
|
|
55
|
+
<div>
|
|
56
|
+
<label>First Name</label>
|
|
57
|
+
<input
|
|
58
|
+
value={field.state.value}
|
|
59
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
60
|
+
/>
|
|
61
|
+
{field.state.meta.errors.length > 0 && (
|
|
62
|
+
<span>{field.state.meta.errors.join(', ')}</span>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</form.Field>
|
|
67
|
+
<button type="submit">Submit</button>
|
|
68
|
+
</form>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|