From cb1b56f5a0eddbf77446f415f2beda57c8305f85 Mon Sep 17 00:00:00 2001
From: polwex <polwex@sortug.com>
Date: Sun, 23 Nov 2025 01:12:53 +0700
Subject: wut

---
 packages/prosody-ui/src/hooks/useLang.tsx | 184 ++++++++++++++++++++++++++++++
 1 file changed, 184 insertions(+)
 create mode 100644 packages/prosody-ui/src/hooks/useLang.tsx

(limited to 'packages/prosody-ui/src/hooks/useLang.tsx')

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>
+  );
+};
-- 
cgit v1.2.3

