| # ZuZu Grammar — self-hosted LanguageTool | |
| ZuZu Grammar uses **LanguageTool** for full sentence grammar, spelling, and punctuation. | |
| If LanguageTool is offline, the API falls back to basic local rules. | |
| ## Hugging Face Spaces (recommended) | |
| The Docker image **embeds LanguageTool** and starts it with the app (`scripts/start.sh`). | |
| 1. Redeploy the Space (rebuild the image). | |
| 2. First boot can take **1–3 minutes** while Java + LT warm up. | |
| 3. Check `GET /health` → `"languagetool": { "reachable": true }`. | |
| 4. Space secrets (optional overrides): | |
| ```env | |
| LANGUAGE_TOOL_EMBEDDED=true | |
| LANGUAGE_TOOL_URL=http://127.0.0.1:8010 | |
| LANGUAGE_TOOL_LANGUAGE=en-US | |
| LANGUAGE_TOOL_ENABLED=true | |
| LANGUAGE_TOOL_TIMEOUT=90 | |
| LANGUAGE_TOOL_CHUNK_CHARS=1800 | |
| GRAMMAR_MAX_CHARS=12000 | |
| LANGUAGETOOL_JAVA_OPTS=-Xms256m -Xmx1024m | |
| ``` | |
| Needs about **1–2 GB RAM**. If the Space OOMs, raise Space hardware or lower `-Xmx` (Java 21 is used in the image). | |
| Long drafts are **split into ~1800-character chunks** so one long paragraph does not OOM LanguageTool. Pastes over `GRAMMAR_MAX_CHARS` (default 12,000) return a clear 413 instead of a raw 500. | |
| **Do not** set `LANGUAGE_TOOL_URL` to a host that isn’t running LT (that causes `Connection refused` and the “offline” fallback message). | |
| ## Local: LanguageTool only + `python app.py` | |
| ```bash | |
| docker compose up languagetool -d | |
| ``` | |
| Wait until healthy, then in `.env`: | |
| ```env | |
| LANGUAGE_TOOL_URL=http://127.0.0.1:8010 | |
| LANGUAGE_TOOL_LANGUAGE=en-US | |
| LANGUAGE_TOOL_ENABLED=true | |
| ``` | |
| `LANGUAGE_TOOL_LANGUAGE` is the server default (en-US). The Grammar UI can override per check (`en-GB`, etc.). | |
| ```bash | |
| curl -s http://127.0.0.1:8010/v2/languages | head | |
| python app.py | |
| ``` | |
| ## Local: full Compose (app + LT sidecar) | |
| ```bash | |
| docker compose up --build | |
| ``` | |
| App: http://127.0.0.1:7860 | |
| LanguageTool: http://127.0.0.1:8010 | |
| Compose sets `LANGUAGE_TOOL_EMBEDDED=false` and `LANGUAGE_TOOL_URL=http://languagetool:8010`. | |
| ## Health | |
| ```json | |
| "languagetool": { "configured": true, "reachable": true, "default_language": "en-US" } | |
| ``` | |
| When reachable, grammar responses use `"engine": "languagetool"`. | |
| ## Troubleshooting `Connection refused` | |
| | Cause | Fix | | |
| |--------|-----| | |
| | LT container not running | `docker compose up languagetool -d` | | |
| | App URL wrong | Local app → `http://127.0.0.1:8010`; Compose app → `http://languagetool:8010`; Space embedded → `http://127.0.0.1:8010` | | |
| | Space still on old image | Rebuild/redeploy so embedded LT + `start.sh` are included | | |
| | LT still starting | Wait 1–2 min; watch logs for `LanguageTool ready` | | |
| | Long text → HTTP 500 | Redeploy latest build (sentence chunking + higher heap). Over `GRAMMAR_MAX_CHARS` now returns a clear 413. | | |
| ## Resources | |
| LanguageTool needs roughly **1–2 GB RAM**. | |