diff options
Diffstat (limited to 'packages/prosody-ui/src/hooks/useLang.tsx')
| -rw-r--r-- | packages/prosody-ui/src/hooks/useLang.tsx | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/packages/prosody-ui/src/hooks/useLang.tsx b/packages/prosody-ui/src/hooks/useLang.tsx new file mode 100644 index 0000000..687c81d --- /dev/null +++ b/packages/prosody-ui/src/hooks/useLang.tsx @@ -0,0 +1,184 @@ +import { + useState, + useEffect, + createContext, + useContext, + useCallback, + type ElementType, +} from "react"; +type ScriptClass = + | "text-ipa" + | "text-rtl" + | "text-cjk" + | "text-thai" + | "text-cyrillic" + | "text-latin"; +interface ScriptContextType { + getClass: (text: string) => ScriptClass; +} +const ScriptContext = createContext({ + getClass: (text: string) => "text-latin" as ScriptClass, +}); + +const getScriptClass = (text: string): ScriptClass => { + // You can combine these with includes() if text has multiple scripts + if (/[\u0591-\u07FF\u200F\u202B]/.test(text)) return "text-rtl"; + + if (/[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff]/.test(text)) return "text-cjk"; + + if (/[\u0E00-\u0E7F]/.test(text)) return "text-thai"; + + if (/[\u0400-\u04FF]/.test(text)) return "text-cyrillic"; + + if (/[\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF]/.test(text)) + return "text-ipa"; + + return "text-latin"; // default +}; + +export const ScripProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const getClass = useCallback((text: string) => { + return getScriptClass(text); + }, []); + + return ( + <ScriptContext.Provider value={{ getClass }}> + {children} + </ScriptContext.Provider> + ); +}; + +export const useScript = () => useContext(ScriptContext); + +type TextElementProps = { + children?: React.ReactNode; + className?: string; +}; +const createTextElement = (Component: ElementType) => { + return function TextElement({ + children, + className, + ...rest + }: TextElementProps & React.ComponentPropsWithoutRef<ElementType>) { + const writingSystemClass = + typeof children === "string" ? getScriptClass(children) : "text-latin"; + + return ( + <Component + className={`${writingSystemClass} ${className || ""}`} + {...rest} + > + {children} + </Component> + ); + }; +}; + +// Create all the text elements you need +export const Span = createTextElement("span"); +export const P = createTextElement("p"); +export const H1 = createTextElement("h1"); +export const H2 = createTextElement("h2"); +export const H3 = createTextElement("h3"); +export const H4 = createTextElement("h4"); +export const H5 = createTextElement("h5"); +export const H6 = createTextElement("h6"); +export const Label = createTextElement("label"); +export const Small = createTextElement("small"); + +interface Voice { + default: boolean; + lang: string; + localService: boolean; + name: string; + voiceURI: string; +} + +export const useSpeechSynthesis = () => { + const [voices, setVoices] = useState<Voice[]>([]); + const [speaking, setSpeaking] = useState(false); + console.log({ voices }, "voices hook"); + + useEffect(() => { + // Function to get voices + const updateVoices = () => { + // Some browsers need a small delay for voices to be available + setTimeout(() => { + const availableVoices = window.speechSynthesis.getVoices(); + if (availableVoices.length > 0) { + setVoices(availableVoices); + } + }, 100); + }; + + // Get initial voices + updateVoices(); + + window.speechSynthesis.addEventListener("voiceschanged", updateVoices); + + // Cleanup + return () => { + window.speechSynthesis.removeEventListener("voiceschanged", updateVoices); + }; + }, []); + + const speak = (text: string, voiceName?: string) => { + const utterance = new SpeechSynthesisUtterance(text); + + if (voiceName) { + const voice = voices.find((v) => v.name === voiceName); + if (voice) utterance.voice = voice; + } + + utterance.onstart = () => setSpeaking(true); + utterance.onend = () => setSpeaking(false); + utterance.onerror = () => setSpeaking(false); + + window.speechSynthesis.speak(utterance); + }; + + const stop = () => { + window.speechSynthesis.cancel(); + setSpeaking(false); + }; + + return { + voices, + speaking, + speak, + stop, + }; +}; + +// Example usage in a component: +const SpeechComponent = () => { + const { voices, speaking, speak, stop } = useSpeechSynthesis(); + const [selectedVoice, setSelectedVoice] = useState<string>(""); + + return ( + <div> + <select + value={selectedVoice} + onChange={(e) => setSelectedVoice(e.target.value)} + > + <option value="">Default Voice</option> + {voices.map((voice) => ( + <option key={voice.name} value={voice.name}> + {voice.name} ({voice.lang}) + </option> + ))} + </select> + + <button + onClick={() => speak("Hello, world!", selectedVoice)} + disabled={speaking} + > + {speaking ? "Speaking..." : "Speak"} + </button> + + {speaking && <button onClick={stop}>Stop</button>} + </div> + ); +}; |
