summaryrefslogtreecommitdiff
path: root/packages/prosody-ui/src/hooks/useLang.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/prosody-ui/src/hooks/useLang.tsx')
-rw-r--r--packages/prosody-ui/src/hooks/useLang.tsx184
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>
+ );
+};