From 730ea4e1809caf8e0420d86cf73b0001b8aa03ae Mon Sep 17 00:00:00 2001 From: CJY Date: Sun, 25 Aug 2024 23:26:31 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=8C=EC=84=B1=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=BD=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/speak/SpeechTale.tsx | 64 ++++++++++++++++++++++ src/components/tales/readTale/ReadTale.tsx | 39 +++++++++---- src/utils/speechUtil.ts | 53 ++++++++++++++++++ 3 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 src/components/common/speak/SpeechTale.tsx create mode 100644 src/utils/speechUtil.ts diff --git a/src/components/common/speak/SpeechTale.tsx b/src/components/common/speak/SpeechTale.tsx new file mode 100644 index 0000000..e065af7 --- /dev/null +++ b/src/components/common/speak/SpeechTale.tsx @@ -0,0 +1,64 @@ +import { franc } from "franc"; +import { iso6393 } from "iso-639-3"; +import styled from "styled-components"; + +interface SpeakPracticeProps { + text1: string; +} + +const getLangCode = (isoCode: string): string => { + const lang = iso6393.find((lang) => lang.iso6393 === isoCode); + if (lang && lang.iso6393 === "cmn") { + return "zh"; + } + return lang ? lang.iso6391 || "en" : "en"; +}; + +const SpeechTale = ({ text1 }: SpeakPracticeProps) => { + const handleBtn = () => { + const detectedLang = franc(text1); + const langCode = getLangCode(detectedLang); + + const utterance = new SpeechSynthesisUtterance(text1); + utterance.lang = langCode; + utterance.rate = 0.6; + + window.speechSynthesis.speak(utterance); + }; + + return ( + + + + {text1} + + + ); +}; + +export default SpeechTale; + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + gap: 2rem; + width: 100%; + display: none; +`; + +const SpeakBox = styled.div` + display: flex; + align-items: center; + gap: 1.3rem; + height: fit-content; +`; + +const SpeakBtn = styled.img` + cursor: pointer; + width: 2.2rem; +`; + +const WordBox = styled.div` + display: none; +`; diff --git a/src/components/tales/readTale/ReadTale.tsx b/src/components/tales/readTale/ReadTale.tsx index 343b2c4..b018e5b 100644 --- a/src/components/tales/readTale/ReadTale.tsx +++ b/src/components/tales/readTale/ReadTale.tsx @@ -1,23 +1,25 @@ +import { useEffect, useState } from "react"; import Header from "@components/common/header/Header"; import * as S from "./ReadTale.styled"; import Dropdown from "@components/common/dropDown/Dropdown"; -import { useEffect, useState } from "react"; import NextBtn from "@components/common/NextBtn"; import { getTale } from "@apis/createTales"; import { useLocation } from "react-router-dom"; import { nationElements } from "@utils/defaultData"; import { ResponseTaleData } from "@type/createTale"; +import { toggleSpeech } from "@utils/speechUtil"; const ReadTale = () => { const location = useLocation(); - // const navigate = useNavigate(); const { response } = location.state || {}; const [language, setLanguage] = useState(2); const [data, setData] = useState(); + const [selectedIndex, setSelectedIndex] = useState(null); + const [isSpeaking, setIsSpeaking] = useState(false); - const onClick = () => { - console.log(data); + const handleDivClick = (index: number) => { + setSelectedIndex((prevIndex) => (prevIndex === index ? null : index)); }; useEffect(() => { @@ -25,10 +27,16 @@ const ReadTale = () => { const tale = await getTale(Number(language) || 2, response); setData(tale); }; - fetchData(); }, [language, response]); + const handleSpeechButtonClick = () => { + if (data) { + const textToSpeak = data.story.replace(/\n/g, " "); + toggleSpeech(textToSpeak, isSpeaking, setIsSpeaking); + } + }; + return ( <>
@@ -47,22 +55,33 @@ const ReadTale = () => { /> - {data.story.split("\n").map((line, index) => ( -
{line}
+ {data.story.split("\n").map((line, idx) => ( +
handleDivClick(idx)} + style={{ + backgroundColor: + selectedIndex === idx ? "#FFF4B3" : "transparent", + padding: "5px", + cursor: "pointer", + }} + > + {line} +
))}
{}} /> diff --git a/src/utils/speechUtil.ts b/src/utils/speechUtil.ts new file mode 100644 index 0000000..16ad5f8 --- /dev/null +++ b/src/utils/speechUtil.ts @@ -0,0 +1,53 @@ +import { franc } from "franc"; +import { iso6393 } from "iso-639-3"; + +export const getLangCode = (isoCode: string): string => { + const lang = iso6393.find((lang) => lang.iso6393 === isoCode); + if (lang && lang.iso6393 === "cmn") { + return "zh"; + } + return lang ? lang.iso6391 || "en" : "en"; +}; + +export const speakText = ( + text: string, + onEnd?: () => void, + onPause?: () => void +) => { + const detectedLang = franc(text); + const langCode = getLangCode(detectedLang); + + const utterance = new SpeechSynthesisUtterance(text.replace(/\n/g, " ")); + utterance.lang = langCode; + utterance.rate = 0.6; + + if (onEnd) { + utterance.onend = () => onEnd(); + } + if (onPause) { + utterance.onpause = () => onPause(); + } + + window.speechSynthesis.speak(utterance); +}; + +export const toggleSpeech = ( + text: string, + isSpeaking: boolean, + setIsSpeaking: (isSpeaking: boolean) => void +) => { + if (isSpeaking) { + window.speechSynthesis.pause(); + setIsSpeaking(false); + } else { + new SpeechSynthesisUtterance(text); + window.speechSynthesis.cancel(); + + speakText( + text, + () => setIsSpeaking(false), + () => setIsSpeaking(false) + ); + setIsSpeaking(true); + } +};