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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user