import React from "react"; import { View, FlatList } from "react-native"; import { Text, Chip, Menu, Button } from "react-native-paper"; import API from "../API.js"; import i18n from "../i18nMessages.js"; const POLL_MS = 1800; const toLangLabel = (lang = "") => { const normalized = String(lang || "").toLowerCase(); if (!normalized || normalized === "original") return i18n.t("message.originalLanguage"); if (normalized === "en") return i18n.t("message.languageEnglish"); if (normalized === "es") return i18n.t("message.languageSpanish"); if (normalized === "fr") return i18n.t("message.languageFrench"); if (normalized === "da") return i18n.t("message.languageDanish"); return normalized.toUpperCase(); }; const CaptionRow = ({ item, selectedLang }) => { const translation = item?.translations?.[selectedLang]; const displayTranslation = translation || item?.original || ""; const isFallback = !translation && selectedLang && selectedLang !== "original" && selectedLang !== item?.sourceLang; return ( {item?.sourceLang ? `${i18n.t("message.originalLanguage")}: ${toLangLabel(item.sourceLang)}` : i18n.t("message.originalLanguage")} {item?.original || ""} {i18n.t("message.selectedTranslation")}: {toLangLabel(selectedLang)} {displayTranslation} {isFallback ? ( {i18n.t("message.translationFallback")} ) : <>} ); }; const LiveCaptions = () => { const [captions, setCaptions] = React.useState([]); const [availableLanguages, setAvailableLanguages] = React.useState([]); const [selectedLang, setSelectedLang] = React.useState((i18n?.locale || "en").toLowerCase()); const [isLoading, setIsLoading] = React.useState(true); const [langMenuVisible, setLangMenuVisible] = React.useState(false); const latestSequenceRef = React.useRef(0); const refresh = React.useCallback(async (initial = false) => { const sinceSequence = initial ? undefined : latestSequenceRef.current; const response = await API.getLiveCaptions(sinceSequence, 50); const incoming = Array.isArray(response?.captions) ? response.captions : []; const languages = Array.isArray(response?.availableLanguages) ? response.availableLanguages : []; if (initial) { setCaptions(incoming); } else if (incoming.length > 0) { setCaptions((prev) => { const bySequence = new Map(prev.map((item) => [item.sequence, item])); incoming.forEach((item) => { if (!item || !Number.isFinite(item.sequence)) return; bySequence.set(item.sequence, item); }); return Array.from(bySequence.values()).sort((a, b) => (a.sequence || 0) - (b.sequence || 0)).slice(-150); }); } if (Number.isFinite(response?.latestSequence)) { latestSequenceRef.current = response.latestSequence; } else if (incoming.length) { const maxSeq = incoming.reduce((acc, item) => Math.max(acc, Number(item?.sequence) || 0), latestSequenceRef.current); latestSequenceRef.current = maxSeq; } setAvailableLanguages(languages); setIsLoading(false); }, []); React.useEffect(() => { refresh(true); const interval = setInterval(() => { refresh(false); }, POLL_MS); return () => clearInterval(interval); }, [refresh]); const languageOptions = React.useMemo(() => { const unique = new Set(["original", ...availableLanguages]); return Array.from(unique).filter(Boolean); }, [availableLanguages]); React.useEffect(() => { if (languageOptions.includes(selectedLang)) return; if (languageOptions.length) setSelectedLang(languageOptions[0]); }, [languageOptions, selectedLang]); return ( {i18n.t("message.liveCaptions")} {i18n.t("message.liveCaptionsSubtitle")} {i18n.t("message.liveNow")} {i18n.t("message.translationLanguage")} setLangMenuVisible(false)} anchor={ } > {languageOptions.map((lang) => ( { setSelectedLang(lang); setLangMenuVisible(false); }} /> ))} {isLoading ? {i18n.t("message.loadingLiveCaptions")} : <>} {!isLoading && captions.length === 0 ? {i18n.t("message.awaitingLiveCaptions")} : <>} `${item?.sequence || "caption"}-${index}`} renderItem={({ item }) => } contentContainerStyle={{ paddingBottom: 18 }} style={{ flex: 1 }} /> ); }; export default LiveCaptions;