1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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>
);
};
|