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