feat: wire up mic/speaker and Google Translate backend for live translation

Frontend uses the Web Speech API for speech-to-text and text-to-speech
(no key needed). Adds POST /api/translate backed by Google Cloud
Translation API v2, gated on GOOGLE_TRANSLATE_API_KEY (placeholder
added to .env/.env.example, unset by default).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 21:28:57 -04:00
co-authored by Claude Sonnet 5
parent 616ca5f259
commit 74d5c7c7ff
5 changed files with 281 additions and 85 deletions
+2
View File
@@ -47,6 +47,7 @@ import insuranceStatusBcbsMaRoutes from "./insuranceStatusBcbsMa";
import labRxRoutes from "./lab-rx";
import seleniumSettingsRoutes from "./selenium-settings";
import typeAgentRoutes from "./type-agent";
import translateRoutes from "./translate";
const router = Router();
@@ -98,5 +99,6 @@ router.use("/fee-schedule", feeScheduleRoutes);
router.use("/license", licenseRoutes);
router.use("/lab-rx", labRxRoutes);
router.use("/selenium-settings", seleniumSettingsRoutes);
router.use("/translate", translateRoutes);
export default router;
+50
View File
@@ -0,0 +1,50 @@
import express, { Request, Response } from "express";
const router = express.Router();
// POST /api/translate
router.post("/", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const apiKey = process.env.GOOGLE_TRANSLATE_API_KEY;
if (!apiKey) {
return res.status(503).json({ message: "Translation is not configured yet. Set GOOGLE_TRANSLATE_API_KEY." });
}
const { text, targetLang, sourceLang } = req.body as { text?: string; targetLang?: string; sourceLang?: string };
if (!text?.trim() || !targetLang) {
return res.status(400).json({ message: "text and targetLang are required" });
}
const params = new URLSearchParams({
key: apiKey,
q: text,
target: targetLang,
format: "text",
});
if (sourceLang) params.set("source", sourceLang);
const googleRes = await fetch(`https://translation.googleapis.com/language/translate/v2?${params.toString()}`, {
method: "POST",
});
if (!googleRes.ok) {
const errBody = await googleRes.json().catch(() => null);
const message = errBody?.error?.message || "Translation request failed";
return res.status(googleRes.status).json({ message });
}
const data = await googleRes.json();
const translation = data?.data?.translations?.[0];
return res.status(200).json({
translatedText: translation?.translatedText ?? "",
detectedSourceLanguage: translation?.detectedSourceLanguage,
});
} catch (err) {
return res.status(500).json({ message: "Failed to translate text", details: String(err) });
}
});
export default router;