refactor(settings): wrap components in React.memo and improve ResetIcon

This commit is contained in:
Vlad Gerasimov 2025-08-09 07:52:28 +04:00
parent f219bd14b6
commit 6f16fdf2f4
13 changed files with 133 additions and 144 deletions

View file

@ -1,16 +1,7 @@
import React from "react";
const ResetIcon = ({ className = "" }: { className?: string }) => { const ResetIcon = () => {
return ( return (
<svg <svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><g stroke="#de689e" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"><path d="m13.5 8.5h3v-3"/><path d="m13.775 14c-.7863.7419-1.7737 1.2356-2.8389 1.4196-1.06527.1839-2.16109.0498-3.15057-.3855s-1.82875-1.1525-2.41293-2.0621c-.58419-.9095-.88739-1.9711-.87172-3.05197.01567-1.08089.34952-2.13319.95982-3.02543.61031-.89224 1.47001-1.58485 2.4717-1.99129s2.1009-.50868 3.1604-.29396c1.0595.21473 2.0322.7369 2.7965 1.50127l2.6107 2.38938"/></g></svg>
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path d="M6 6c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1s1 .45 1 1v2.05C3.82 1.6 6.71 0 10 0c5.52 0 10 4.48 10 10s-4.48 10-10 10S0 15.52 0 10c0-.55.45-1 1-1s1 .45 1 1c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8C7.47 2 5.23 3.17 3.76 5H5c.55 0 1 .45 1 1" />
</svg>
); );
}; };

View file

@ -7,7 +7,7 @@ interface AlwaysOnMicrophoneProps {
grouped?: boolean; grouped?: boolean;
} }
export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = ({ export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -26,4 +26,4 @@ export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = ({
grouped={grouped} grouped={grouped}
/> />
); );
}; });

View file

@ -7,7 +7,7 @@ interface AudioFeedbackProps {
grouped?: boolean; grouped?: boolean;
} }
export const AudioFeedback: React.FC<AudioFeedbackProps> = ({ export const AudioFeedback: React.FC<AudioFeedbackProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -26,4 +26,4 @@ export const AudioFeedback: React.FC<AudioFeedbackProps> = ({
grouped={grouped} grouped={grouped}
/> />
); );
}; });

View file

@ -7,7 +7,7 @@ interface CorrectWordsProps {
grouped?: boolean; grouped?: boolean;
} }
export const CorrectWords: React.FC<CorrectWordsProps> = ({ export const CorrectWords: React.FC<CorrectWordsProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -17,8 +17,9 @@ export const CorrectWords: React.FC<CorrectWordsProps> = ({
const handleAddWord = () => { const handleAddWord = () => {
const trimmedWord = newWord.trim(); const trimmedWord = newWord.trim();
if (trimmedWord && !trimmedWord.includes(' ') && !correctWords.includes(trimmedWord)) { const sanitizedWord = trimmedWord.replace(/[<>"'&]/g, '');
updateSetting("correct_words", [...correctWords, trimmedWord]); if (sanitizedWord && !sanitizedWord.includes(' ') && sanitizedWord.length <= 50 && !correctWords.includes(sanitizedWord)) {
updateSetting("correct_words", [...correctWords, sanitizedWord]);
setNewWord(""); setNewWord("");
} }
}; };
@ -54,7 +55,7 @@ export const CorrectWords: React.FC<CorrectWordsProps> = ({
/> />
<button <button
onClick={handleAddWord} onClick={handleAddWord}
disabled={!newWord.trim() || newWord.includes(' ') || isUpdating("correct_words")} disabled={!newWord.trim() || newWord.includes(' ') || newWord.trim().length > 50 || isUpdating("correct_words")}
className="px-3 py-1 text-xs font-medium text-white bg-logo-primary rounded hover:bg-logo-primary/90 focus:outline-none focus:ring-1 focus:ring-logo-primary disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="px-3 py-1 text-xs font-medium text-white bg-logo-primary rounded hover:bg-logo-primary/90 focus:outline-none focus:ring-1 focus:ring-logo-primary disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
Add Add
@ -81,4 +82,4 @@ export const CorrectWords: React.FC<CorrectWordsProps> = ({
)} )}
</> </>
); );
}; });

View file

@ -6,7 +6,7 @@ import {
normalizeKey, normalizeKey,
type OSType, type OSType,
} from "../../lib/utils/keyboard"; } from "../../lib/utils/keyboard";
import ResetIcon from "../icons/ResetIcon"; import { ResetButton } from "../ui/ResetButton";
import { SettingContainer } from "../ui/SettingContainer"; import { SettingContainer } from "../ui/SettingContainer";
import { useSettings } from "../../hooks/useSettings"; import { useSettings } from "../../hooks/useSettings";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@ -69,10 +69,11 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
// Only add event listeners when we're in editing mode // Only add event listeners when we're in editing mode
if (editingShortcutId === null) return; if (editingShortcutId === null) return;
console.log("keyPressed", keyPressed); let cleanup = false;
// Keyboard event listeners // Keyboard event listeners
const handleKeyDown = async (e: KeyboardEvent) => { const handleKeyDown = async (e: KeyboardEvent) => {
if (cleanup) return;
if (e.repeat) return; // ignore auto-repeat if (e.repeat) return; // ignore auto-repeat
if (e.key === "Escape") { if (e.key === "Escape") {
// Cancel recording and restore original binding // Cancel recording and restore original binding
@ -103,8 +104,6 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
const rawKey = getKeyName(e, osType); const rawKey = getKeyName(e, osType);
const key = normalizeKey(rawKey); const key = normalizeKey(rawKey);
console.log("You pressed", rawKey, "normalized to", key);
if (!keyPressed.includes(key)) { if (!keyPressed.includes(key)) {
setKeyPressed((prev) => [...prev, key]); setKeyPressed((prev) => [...prev, key]);
// Also add to recorded keys if not already there // Also add to recorded keys if not already there
@ -115,6 +114,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
}; };
const handleKeyUp = async (e: KeyboardEvent) => { const handleKeyUp = async (e: KeyboardEvent) => {
if (cleanup) return;
e.preventDefault(); e.preventDefault();
// Get the key with OS-specific naming and normalize it // Get the key with OS-specific naming and normalize it
@ -166,6 +166,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
// Add click outside handler // Add click outside handler
const handleClickOutside = async (e: MouseEvent) => { const handleClickOutside = async (e: MouseEvent) => {
if (cleanup) return;
const activeElement = shortcutRefs.current.get(editingShortcutId); const activeElement = shortcutRefs.current.get(editingShortcutId);
if (activeElement && !activeElement.contains(e.target as Node)) { if (activeElement && !activeElement.contains(e.target as Node)) {
// Cancel shortcut recording and restore original binding // Cancel shortcut recording and restore original binding
@ -196,6 +197,7 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
window.addEventListener("click", handleClickOutside); window.addEventListener("click", handleClickOutside);
return () => { return () => {
cleanup = true;
window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp); window.removeEventListener("keyup", handleKeyUp);
window.removeEventListener("click", handleClickOutside); window.removeEventListener("click", handleClickOutside);
@ -299,13 +301,10 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
{formatKeyCombination(primaryBinding.current_binding, osType)} {formatKeyCombination(primaryBinding.current_binding, osType)}
</div> </div>
)} )}
<button <ResetButton
className="px-2 py-1 hover:bg-logo-primary/30 active:bg-logo-primary/50 active:scale-95 rounded fill-text hover:cursor-pointer hover:border-logo-primary border border-transparent transition-all duration-150"
onClick={() => resetBinding(primaryId)} onClick={() => resetBinding(primaryId)}
disabled={isUpdating(`binding_${primaryId}`)} disabled={isUpdating(`binding_${primaryId}`)}
> />
<ResetIcon className="" />
</button>
</div> </div>
); );
})()} })()}

View file

@ -1,55 +1,14 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect, useMemo } from "react";
import { SettingContainer } from "../ui/SettingContainer"; import { SettingContainer } from "../ui/SettingContainer";
import ResetIcon from "../icons/ResetIcon"; import { ResetButton } from "../ui/ResetButton";
import { useSettings } from "../../hooks/useSettings"; import { useSettings } from "../../hooks/useSettings";
import { LANGUAGES } from "../../lib/constants/languages";
interface LanguageSelectorProps { interface LanguageSelectorProps {
descriptionMode?: "inline" | "tooltip"; descriptionMode?: "inline" | "tooltip";
grouped?: boolean; grouped?: boolean;
} }
const LANGUAGES = [
{ code: "auto", name: "Auto" },
{ code: "en", name: "English" },
{ code: "zh", name: "Chinese" },
{ code: "hi", name: "Hindi" },
{ code: "es", name: "Spanish" },
{ code: "fr", name: "French" },
{ code: "ar", name: "Arabic" },
{ code: "pt", name: "Portuguese" },
{ code: "ru", name: "Russian" },
{ code: "ja", name: "Japanese" },
{ code: "de", name: "German" },
{ code: "ko", name: "Korean" },
{ code: "it", name: "Italian" },
{ code: "tr", name: "Turkish" },
{ code: "vi", name: "Vietnamese" },
{ code: "pl", name: "Polish" },
{ code: "nl", name: "Dutch" },
{ code: "uk", name: "Ukrainian" },
{ code: "fa", name: "Persian" },
{ code: "th", name: "Thai" },
{ code: "ro", name: "Romanian" },
{ code: "el", name: "Greek" },
{ code: "cs", name: "Czech" },
{ code: "sv", name: "Swedish" },
{ code: "hu", name: "Hungarian" },
{ code: "he", name: "Hebrew" },
{ code: "da", name: "Danish" },
{ code: "fi", name: "Finnish" },
{ code: "no", name: "Norwegian" },
{ code: "sk", name: "Slovak" },
{ code: "bg", name: "Bulgarian" },
{ code: "hr", name: "Croatian" },
{ code: "lt", name: "Lithuanian" },
{ code: "sl", name: "Slovenian" },
{ code: "lv", name: "Latvian" },
{ code: "et", name: "Estonian" },
{ code: "sr", name: "Serbian" },
{ code: "is", name: "Icelandic" },
{ code: "id", name: "Indonesian" },
];
export const LanguageSelector: React.FC<LanguageSelectorProps> = ({ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
@ -85,12 +44,15 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({
} }
}, [isOpen]); }, [isOpen]);
const filteredLanguages = LANGUAGES.filter((language) => const filteredLanguages = useMemo(
language.name.toLowerCase().includes(searchQuery.toLowerCase()), () => LANGUAGES.filter((language) =>
language.label.toLowerCase().includes(searchQuery.toLowerCase()),
),
[searchQuery]
); );
const selectedLanguageName = const selectedLanguageName =
LANGUAGES.find((lang) => lang.code === selectedLanguage)?.name || "Auto"; LANGUAGES.find((lang) => lang.value === selectedLanguage)?.label || "Auto";
const handleLanguageSelect = async (languageCode: string) => { const handleLanguageSelect = async (languageCode: string) => {
await updateSetting("selected_language", languageCode); await updateSetting("selected_language", languageCode);
@ -114,7 +76,7 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter" && filteredLanguages.length > 0) { if (event.key === "Enter" && filteredLanguages.length > 0) {
// Select first filtered language on Enter // Select first filtered language on Enter
handleLanguageSelect(filteredLanguages[0].code); handleLanguageSelect(filteredLanguages[0].value);
} else if (event.key === "Escape") { } else if (event.key === "Escape") {
setIsOpen(false); setIsOpen(false);
setSearchQuery(""); setSearchQuery("");
@ -181,17 +143,17 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({
) : ( ) : (
filteredLanguages.map((language) => ( filteredLanguages.map((language) => (
<button <button
key={language.code} key={language.value}
type="button" type="button"
className={`w-full px-2 py-1 text-sm text-left hover:bg-logo-primary/10 transition-colors duration-150 ${ className={`w-full px-2 py-1 text-sm text-left hover:bg-logo-primary/10 transition-colors duration-150 ${
selectedLanguage === language.code selectedLanguage === language.value
? "bg-logo-primary/20 text-logo-primary font-semibold" ? "bg-logo-primary/20 text-logo-primary font-semibold"
: "" : ""
}`} }`}
onClick={() => handleLanguageSelect(language.code)} onClick={() => handleLanguageSelect(language.value)}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="truncate">{language.name}</span> <span className="truncate">{language.label}</span>
</div> </div>
</button> </button>
)) ))
@ -200,13 +162,10 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = ({
</div> </div>
)} )}
</div> </div>
<button <ResetButton
className="px-2 py-1 hover:bg-logo-primary/30 active:bg-logo-primary/50 active:scale-95 rounded fill-text hover:cursor-pointer hover:border-logo-primary border border-transparent transition-all duration-150"
onClick={handleReset} onClick={handleReset}
disabled={isUpdating("selected_language")} disabled={isUpdating("selected_language")}
> />
<ResetIcon className="" />
</button>
</div> </div>
{isUpdating("selected_language") && ( {isUpdating("selected_language") && (
<div className="absolute inset-0 bg-mid-gray/10 rounded flex items-center justify-center"> <div className="absolute inset-0 bg-mid-gray/10 rounded flex items-center justify-center">

View file

@ -1,33 +1,15 @@
import React from "react"; import React from "react";
import { Dropdown } from "../ui/Dropdown"; import { Dropdown } from "../ui/Dropdown";
import { SettingContainer } from "../ui/SettingContainer"; import { SettingContainer } from "../ui/SettingContainer";
import ResetIcon from "../icons/ResetIcon"; import { ResetButton } from "../ui/ResetButton";
import { useSettings } from "../../hooks/useSettings"; import { useSettings } from "../../hooks/useSettings";
// Simple refresh icon component
const RefreshIcon: React.FC<{ className?: string }> = ({ className = "" }) => (
<svg
className={`w-4 h-4 ${className}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
);
interface MicrophoneSelectorProps { interface MicrophoneSelectorProps {
descriptionMode?: "inline" | "tooltip"; descriptionMode?: "inline" | "tooltip";
grouped?: boolean; grouped?: boolean;
} }
export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -45,14 +27,10 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({
const handleMicrophoneSelect = async (deviceName: string) => { const handleMicrophoneSelect = async (deviceName: string) => {
await updateSetting("selected_microphone", deviceName); await updateSetting("selected_microphone", deviceName);
console.log(
`Microphone changed to: ${audioDevices.find((d) => d.name === deviceName)?.name}`,
);
}; };
const handleReset = async () => { const handleReset = async () => {
await resetSetting("selected_microphone"); await resetSetting("selected_microphone");
console.log("Microphone reset to default");
}; };
const microphoneOptions = audioDevices.map(device => ({ const microphoneOptions = audioDevices.map(device => ({
@ -76,19 +54,11 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({
disabled={isUpdating("selected_microphone") || isLoading} disabled={isUpdating("selected_microphone") || isLoading}
onRefresh={refreshAudioDevices} onRefresh={refreshAudioDevices}
/> />
<button <ResetButton
className="px-2 py-1 hover:bg-logo-primary/30 active:bg-logo-primary/50 active:scale-95 rounded fill-text hover:cursor-pointer hover:border-logo-primary border border-transparent transition-all duration-150"
onClick={handleReset} onClick={handleReset}
disabled={isUpdating("selected_microphone") || isLoading} disabled={isUpdating("selected_microphone") || isLoading}
> />
<ResetIcon className="" />
</button>
</div> </div>
{isUpdating("selected_microphone") && (
<div className="absolute inset-0 bg-mid-gray/10 rounded flex items-center justify-center">
<div className="w-4 h-4 border-2 border-logo-primary border-t-transparent rounded-full animate-spin"></div>
</div>
)}
</SettingContainer> </SettingContainer>
); );
}; });

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Dropdown } from "../ui/Dropdown"; import { Dropdown } from "../ui/Dropdown";
import { SettingContainer } from "../ui/SettingContainer"; import { SettingContainer } from "../ui/SettingContainer";
import ResetIcon from "../icons/ResetIcon"; import { ResetButton } from "../ui/ResetButton";
import { useSettings } from "../../hooks/useSettings"; import { useSettings } from "../../hooks/useSettings";
interface OutputDeviceSelectorProps { interface OutputDeviceSelectorProps {
@ -9,7 +9,7 @@ interface OutputDeviceSelectorProps {
grouped?: boolean; grouped?: boolean;
} }
export const OutputDeviceSelector: React.FC<OutputDeviceSelectorProps> = ({ export const OutputDeviceSelector: React.FC<OutputDeviceSelectorProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -28,14 +28,10 @@ export const OutputDeviceSelector: React.FC<OutputDeviceSelectorProps> = ({
const handleOutputDeviceSelect = async (deviceName: string) => { const handleOutputDeviceSelect = async (deviceName: string) => {
await updateSetting("selected_output_device", deviceName); await updateSetting("selected_output_device", deviceName);
console.log(
`Output device changed to: ${outputDevices.find((d) => d.name === deviceName)?.name}`,
);
}; };
const handleReset = async () => { const handleReset = async () => {
await resetSetting("selected_output_device"); await resetSetting("selected_output_device");
console.log("Output device reset to default");
}; };
const outputDeviceOptions = outputDevices.map(device => ({ const outputDeviceOptions = outputDevices.map(device => ({
@ -59,19 +55,11 @@ export const OutputDeviceSelector: React.FC<OutputDeviceSelectorProps> = ({
disabled={isUpdating("selected_output_device") || isLoading} disabled={isUpdating("selected_output_device") || isLoading}
onRefresh={refreshOutputDevices} onRefresh={refreshOutputDevices}
/> />
<button <ResetButton
className="px-2 py-1 hover:bg-logo-primary/30 active:bg-logo-primary/50 active:scale-95 rounded fill-text hover:cursor-pointer hover:border-logo-primary border border-transparent transition-all duration-150"
onClick={handleReset} onClick={handleReset}
disabled={isUpdating("selected_output_device") || isLoading} disabled={isUpdating("selected_output_device") || isLoading}
> />
<ResetIcon className="" />
</button>
</div> </div>
{isUpdating("selected_output_device") && (
<div className="absolute inset-0 bg-mid-gray/10 rounded flex items-center justify-center">
<div className="w-4 h-4 border-2 border-logo-primary border-t-transparent rounded-full animate-spin"></div>
</div>
)}
</SettingContainer> </SettingContainer>
); );
}; });

View file

@ -7,7 +7,7 @@ interface PushToTalkProps {
grouped?: boolean; grouped?: boolean;
} }
export const PushToTalk: React.FC<PushToTalkProps> = ({ export const PushToTalk: React.FC<PushToTalkProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -26,4 +26,4 @@ export const PushToTalk: React.FC<PushToTalkProps> = ({
grouped={grouped} grouped={grouped}
/> />
); );
}; });

View file

@ -15,7 +15,7 @@ const overlayOptions = [
{ value: "top", label: "Top" }, { value: "top", label: "Top" },
]; ];
export const ShowOverlay: React.FC<ShowOverlayProps> = ({ export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -41,4 +41,4 @@ export const ShowOverlay: React.FC<ShowOverlayProps> = ({
/> />
</SettingContainer> </SettingContainer>
); );
}; });

View file

@ -7,7 +7,7 @@ interface TranslateToEnglishProps {
grouped?: boolean; grouped?: boolean;
} }
export const TranslateToEnglish: React.FC<TranslateToEnglishProps> = ({ export const TranslateToEnglish: React.FC<TranslateToEnglishProps> = React.memo(({
descriptionMode = "tooltip", descriptionMode = "tooltip",
grouped = false, grouped = false,
}) => { }) => {
@ -26,4 +26,4 @@ export const TranslateToEnglish: React.FC<TranslateToEnglishProps> = ({
grouped={grouped} grouped={grouped}
/> />
); );
}; });

View file

@ -0,0 +1,22 @@
import React from "react";
import ResetIcon from "../icons/ResetIcon";
interface ResetButtonProps {
onClick: () => void;
disabled?: boolean;
className?: string;
}
export const ResetButton: React.FC<ResetButtonProps> = React.memo(({
onClick,
disabled = false,
className = ""
}) => (
<button
className={`p-1 hover:bg-logo-primary/30 active:bg-logo-primary/50 active:translate-y-[1px] rounded fill-text hover:cursor-pointer hover:border-logo-primary border border-transparent transition-all duration-150 ${className}`}
onClick={onClick}
disabled={disabled}
>
<ResetIcon />
</button>
));

View file

@ -0,0 +1,59 @@
export interface Language {
value: string;
label: string;
}
export const LANGUAGES: Language[] = [
{ value: "auto", label: "Auto Detect" },
{ value: "en", label: "English" },
{ value: "zh", label: "Chinese" },
{ value: "de", label: "German" },
{ value: "es", label: "Spanish" },
{ value: "ru", label: "Russian" },
{ value: "ko", label: "Korean" },
{ value: "fr", label: "French" },
{ value: "ja", label: "Japanese" },
{ value: "pt", label: "Portuguese" },
{ value: "tr", label: "Turkish" },
{ value: "pl", label: "Polish" },
{ value: "ca", label: "Catalan" },
{ value: "nl", label: "Dutch" },
{ value: "ar", label: "Arabic" },
{ value: "sv", label: "Swedish" },
{ value: "it", label: "Italian" },
{ value: "id", label: "Indonesian" },
{ value: "hi", label: "Hindi" },
{ value: "fi", label: "Finnish" },
{ value: "vi", label: "Vietnamese" },
{ value: "he", label: "Hebrew" },
{ value: "uk", label: "Ukrainian" },
{ value: "el", label: "Greek" },
{ value: "ms", label: "Malay" },
{ value: "cs", label: "Czech" },
{ value: "ro", label: "Romanian" },
{ value: "da", label: "Danish" },
{ value: "hu", label: "Hungarian" },
{ value: "ta", label: "Tamil" },
{ value: "no", label: "Norwegian" },
{ value: "th", label: "Thai" },
{ value: "ur", label: "Urdu" },
{ value: "hr", label: "Croatian" },
{ value: "bg", label: "Bulgarian" },
{ value: "lt", label: "Lithuanian" },
{ value: "la", label: "Latin" },
{ value: "mi", label: "Maori" },
{ value: "ml", label: "Malayalam" },
{ value: "cy", label: "Welsh" },
{ value: "sk", label: "Slovak" },
{ value: "te", label: "Telugu" },
{ value: "fa", label: "Persian" },
{ value: "lv", label: "Latvian" },
{ value: "bn", label: "Bengali" },
{ value: "sr", label: "Serbian" },
{ value: "az", label: "Azerbaijani" },
{ value: "sl", label: "Slovenian" },
{ value: "kn", label: "Kannada" },
{ value: "et", label: "Estonian" },
{ value: "mk", label: "Macedonian" },
{ value: "br", label: "Breton" },
];