- Add erikvl87/languagetool service on the internal network (port 8010, English only by default — change langsToLoad to add more) - 256 MB min / 512 MB max heap; healthcheck waits up to ~3 min for first startup while Java initialises - Server LANGUAGETOOL_URL hardwired to http://languagetool:8010/v2 — no character limits, no rate limits, fully private - Remove 40 000-char cap from lint route (not needed self-hosted) - Bump fetch timeout to 30 s for cold-start requests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
55 lines
1.8 KiB
JavaScript
55 lines
1.8 KiB
JavaScript
import { Router } from 'express'
|
|
import { auth } from '../middleware/auth.js'
|
|
|
|
// Point at a self-hosted LanguageTool instance by setting LANGUAGETOOL_URL.
|
|
// Falls back to the free public API (rate-limited but good for personal use).
|
|
const LT_URL = (process.env.LANGUAGETOOL_URL || 'https://api.languagetoolplus.com/v2').replace(/\/$/, '')
|
|
|
|
const router = Router()
|
|
router.use(auth)
|
|
|
|
router.post('/check', async (req, res) => {
|
|
const { text, language = 'en-US' } = req.body || {}
|
|
if (!text || typeof text !== 'string') return res.status(400).json({ error: 'text required' })
|
|
|
|
try {
|
|
const body = new URLSearchParams({
|
|
text,
|
|
language,
|
|
// Suppress cosmetic rules that aren't useful in a creative-writing context
|
|
disabledRules: 'WHITESPACE_RULE,WORD_CONTAINS_UPPERCASE,EN_UNPAIRED_BRACKETS,DASH_RULE',
|
|
})
|
|
|
|
const r = await fetch(`${LT_URL}/check`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
body: body.toString(),
|
|
signal: AbortSignal.timeout(30000),
|
|
})
|
|
|
|
if (!r.ok) {
|
|
const msg = await r.text().catch(() => '')
|
|
throw new Error(`LanguageTool ${r.status}: ${msg.slice(0, 200)}`)
|
|
}
|
|
|
|
const data = await r.json()
|
|
|
|
// Send only what the client needs — keep the payload small
|
|
res.json({
|
|
matches: (data.matches || []).map(m => ({
|
|
message: m.message,
|
|
offset: m.offset,
|
|
length: m.length,
|
|
replacements: (m.replacements || []).slice(0, 6).map(r => r.value),
|
|
kind: m.rule?.category?.id === 'TYPOS' ? 'spelling' : 'grammar',
|
|
ruleId: m.rule?.id ?? '',
|
|
})),
|
|
})
|
|
} catch (err) {
|
|
console.error('[lint] LanguageTool error:', err.message)
|
|
res.status(502).json({ error: err.message })
|
|
}
|
|
})
|
|
|
|
export default router
|