Compare commits
1 Commits
74d84470a9
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c4da84827 |
+74
-18
@@ -1,10 +1,11 @@
|
||||
import React from "react";
|
||||
import { View, FlatList } from "react-native";
|
||||
import { View } 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 CAPTION_META_KEYS = new Set(["sequence", "createdAt", "sourceLang", "original", "translations"]);
|
||||
|
||||
const toLangLabel = (lang = "") => {
|
||||
const normalized = String(lang || "").toLowerCase();
|
||||
@@ -16,22 +17,45 @@ const toLangLabel = (lang = "") => {
|
||||
return normalized.toUpperCase();
|
||||
};
|
||||
|
||||
const inferSourceLang = (item) => {
|
||||
if (!item || typeof item !== "object") return "";
|
||||
if (item.sourceLang) return String(item.sourceLang).toLowerCase();
|
||||
const original = typeof item.original === "string" ? item.original.trim() : "";
|
||||
if (!original) return "";
|
||||
const direct = Object.entries(item).find(([key, value]) => !CAPTION_META_KEYS.has(key) && typeof value === "string" && value.trim() === original);
|
||||
if (direct?.[0]) return String(direct[0]).toLowerCase();
|
||||
const nested = Object.entries(item.translations || {}).find(([_, value]) => typeof value === "string" && value.trim() === original);
|
||||
return nested?.[0] ? String(nested[0]).toLowerCase() : "";
|
||||
};
|
||||
|
||||
const getTextForLang = (item, lang) => {
|
||||
if (!item || typeof item !== "object") return "";
|
||||
const normalizedLang = String(lang || "").toLowerCase();
|
||||
if (!normalizedLang || normalizedLang === "original") return item.original || "";
|
||||
const direct = typeof item[normalizedLang] === "string" ? item[normalizedLang].trim() : "";
|
||||
if (direct) return direct;
|
||||
const nested = typeof item?.translations?.[normalizedLang] === "string" ? item.translations[normalizedLang].trim() : "";
|
||||
if (nested) return nested;
|
||||
return "";
|
||||
};
|
||||
|
||||
const CaptionRow = ({ item, selectedLang }) => {
|
||||
const translation = item?.translations?.[selectedLang];
|
||||
const translation = getTextForLang(item, selectedLang);
|
||||
const sourceLang = inferSourceLang(item);
|
||||
const displayTranslation = translation || item?.original || "";
|
||||
const isFallback = !translation && selectedLang && selectedLang !== "original" && selectedLang !== item?.sourceLang;
|
||||
const isFallback = !translation && selectedLang && selectedLang !== "original" && selectedLang !== sourceLang;
|
||||
return (
|
||||
<View style={{ marginBottom: 12, backgroundColor: "#fff", borderRadius: 10, padding: 12, borderWidth: 1, borderColor: "#e5e7eb" }}>
|
||||
<View style={{ flex: 1, backgroundColor: "#fff", borderRadius: 14, padding: 18, borderWidth: 1, borderColor: "#e5e7eb" }}>
|
||||
<Text style={{ color: "#6b7280", fontSize: 11, marginBottom: 4 }}>
|
||||
{item?.sourceLang ? `${i18n.t("message.originalLanguage")}: ${toLangLabel(item.sourceLang)}` : i18n.t("message.originalLanguage")}
|
||||
{sourceLang ? `${i18n.t("message.originalLanguage")}: ${toLangLabel(sourceLang)}` : i18n.t("message.originalLanguage")}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 18, fontWeight: "700", color: "#111827" }}>
|
||||
<Text style={{ fontSize: 28, fontWeight: "700", color: "#111827", lineHeight: 36 }}>
|
||||
{item?.original || ""}
|
||||
</Text>
|
||||
<Text style={{ color: "#6b7280", fontSize: 11, marginTop: 8, marginBottom: 4 }}>
|
||||
{i18n.t("message.selectedTranslation")}: {toLangLabel(selectedLang)}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 16, color: "#1f2937" }}>
|
||||
<Text style={{ fontSize: 24, color: "#1f2937", lineHeight: 32 }}>
|
||||
{displayTranslation}
|
||||
</Text>
|
||||
{isFallback ? (
|
||||
@@ -89,13 +113,52 @@ const LiveCaptions = () => {
|
||||
return () => clearInterval(interval);
|
||||
}, [refresh]);
|
||||
|
||||
const latestCaption = captions.length ? captions[captions.length - 1] : null;
|
||||
|
||||
const extractLangsFromCaption = React.useCallback((caption) => {
|
||||
const langs = new Set();
|
||||
if (!caption || typeof caption !== "object") return langs;
|
||||
if (caption.sourceLang) langs.add(String(caption.sourceLang).toLowerCase());
|
||||
|
||||
const map = caption.translations;
|
||||
if (map && typeof map === "object" && !Array.isArray(map)) {
|
||||
Object.keys(map).forEach((lang) => {
|
||||
if (lang) langs.add(String(lang).toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
// Backward/alternate payload support: languages at top-level (en, fr, es, etc.)
|
||||
Object.entries(caption).forEach(([key, value]) => {
|
||||
if (CAPTION_META_KEYS.has(key)) return;
|
||||
if (typeof value !== "string" || !value.trim()) return;
|
||||
const lang = String(key || "").toLowerCase().trim();
|
||||
if (!lang) return;
|
||||
langs.add(lang);
|
||||
});
|
||||
|
||||
return langs;
|
||||
}, []);
|
||||
|
||||
const languageOptions = React.useMemo(() => {
|
||||
const unique = new Set(["original", ...availableLanguages]);
|
||||
return Array.from(unique).filter(Boolean);
|
||||
}, [availableLanguages]);
|
||||
captions.forEach((caption) => {
|
||||
const langs = extractLangsFromCaption(caption);
|
||||
langs.forEach((lang) => unique.add(lang));
|
||||
});
|
||||
return Array.from(unique).filter((lang) => !!lang && lang !== "sourceLang" && lang !== "translations");
|
||||
}, [availableLanguages, captions, extractLangsFromCaption]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (languageOptions.includes(selectedLang)) return;
|
||||
const appLang = (i18n?.locale || "en").toLowerCase();
|
||||
if (languageOptions.includes(appLang)) {
|
||||
setSelectedLang(appLang);
|
||||
return;
|
||||
}
|
||||
if (languageOptions.includes("en")) {
|
||||
setSelectedLang("en");
|
||||
return;
|
||||
}
|
||||
if (languageOptions.length) setSelectedLang(languageOptions[0]);
|
||||
}, [languageOptions, selectedLang]);
|
||||
|
||||
@@ -136,15 +199,8 @@ const LiveCaptions = () => {
|
||||
</View>
|
||||
|
||||
{isLoading ? <Text>{i18n.t("message.loadingLiveCaptions")}</Text> : <></>}
|
||||
{!isLoading && captions.length === 0 ? <Text>{i18n.t("message.awaitingLiveCaptions")}</Text> : <></>}
|
||||
|
||||
<FlatList
|
||||
data={captions}
|
||||
keyExtractor={(item, index) => `${item?.sequence || "caption"}-${index}`}
|
||||
renderItem={({ item }) => <CaptionRow item={item} selectedLang={selectedLang} />}
|
||||
contentContainerStyle={{ paddingBottom: 18 }}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
{!isLoading && !latestCaption ? <Text>{i18n.t("message.awaitingLiveCaptions")}</Text> : <></>}
|
||||
{!isLoading && latestCaption ? <CaptionRow item={latestCaption} selectedLang={selectedLang} /> : <></>}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user