From e9134e02298cf6ff1257c27675b05954ea684941 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Mon, 9 Feb 2026 23:46:42 -0800 Subject: [PATCH] feat(web): show detected language with flag indicator in live demo (#169) - Add language mapping with flag emojis for 40+ locales - Display detected browser language in header with flag badge - Show Listening in Language indicator when active - Responsive design: language name hidden on mobile, flag always visible Improves UX by making users aware of which language the speech recognition is configured for. --- web/next-env.d.ts | 1 + web/src/components/live-demo.tsx | 127 ++++++++++++++++++++++++++++--- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/web/next-env.d.ts b/web/next-env.d.ts index 1b3be08..d39ca30 100644 --- a/web/next-env.d.ts +++ b/web/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/web/src/components/live-demo.tsx b/web/src/components/live-demo.tsx index 00117fa..97f5d16 100644 --- a/web/src/components/live-demo.tsx +++ b/web/src/components/live-demo.tsx @@ -35,6 +35,85 @@ declare global { } } +// Language code to display name and flag mapping +const LANGUAGE_MAP: Record = { + "en": { name: "English", flag: "🇬🇧" }, + "en-US": { name: "English (US)", flag: "🇺🇸" }, + "en-GB": { name: "English (UK)", flag: "🇬🇧" }, + "en-CA": { name: "English (Canada)", flag: "🇨🇦" }, + "en-AU": { name: "English (Australia)", flag: "🇦🇺" }, + "fr": { name: "French", flag: "🇫🇷" }, + "fr-FR": { name: "French (France)", flag: "🇫🇷" }, + "fr-CA": { name: "French (Canada)", flag: "🇨🇦" }, + "de": { name: "German", flag: "🇩🇪" }, + "de-DE": { name: "German (Germany)", flag: "🇩🇪" }, + "es": { name: "Spanish", flag: "🇪🇸" }, + "es-ES": { name: "Spanish (Spain)", flag: "🇪🇸" }, + "es-MX": { name: "Spanish (Mexico)", flag: "🇲🇽" }, + "es-AR": { name: "Spanish (Argentina)", flag: "🇦🇷" }, + "it": { name: "Italian", flag: "🇮🇹" }, + "it-IT": { name: "Italian (Italy)", flag: "🇮🇹" }, + "pt": { name: "Portuguese", flag: "🇵🇹" }, + "pt-BR": { name: "Portuguese (Brazil)", flag: "🇧🇷" }, + "pt-PT": { name: "Portuguese (Portugal)", flag: "🇵🇹" }, + "nl": { name: "Dutch", flag: "🇳🇱" }, + "nl-NL": { name: "Dutch (Netherlands)", flag: "🇳🇱" }, + "ja": { name: "Japanese", flag: "🇯🇵" }, + "ja-JP": { name: "Japanese (Japan)", flag: "🇯🇵" }, + "ko": { name: "Korean", flag: "🇰🇷" }, + "ko-KR": { name: "Korean (Korea)", flag: "🇰🇷" }, + "zh": { name: "Chinese", flag: "🇨🇳" }, + "zh-CN": { name: "Chinese (Simplified)", flag: "🇨🇳" }, + "zh-TW": { name: "Chinese (Traditional)", flag: "🇹🇼" }, + "ru": { name: "Russian", flag: "🇷🇺" }, + "ru-RU": { name: "Russian (Russia)", flag: "🇷🇺" }, + "ar": { name: "Arabic", flag: "🇸🇦" }, + "ar-SA": { name: "Arabic (Saudi Arabia)", flag: "🇸🇦" }, + "hi": { name: "Hindi", flag: "🇮🇳" }, + "hi-IN": { name: "Hindi (India)", flag: "🇮🇳" }, + "pl": { name: "Polish", flag: "🇵🇱" }, + "pl-PL": { name: "Polish (Poland)", flag: "🇵🇱" }, + "tr": { name: "Turkish", flag: "🇹🇷" }, + "tr-TR": { name: "Turkish (Turkey)", flag: "🇹🇷" }, + "sv": { name: "Swedish", flag: "🇸🇪" }, + "sv-SE": { name: "Swedish (Sweden)", flag: "🇸🇪" }, + "da": { name: "Danish", flag: "🇩🇰" }, + "da-DK": { name: "Danish (Denmark)", flag: "🇩🇰" }, + "fi": { name: "Finnish", flag: "🇫🇮" }, + "fi-FI": { name: "Finnish (Finland)", flag: "🇫🇮" }, + "no": { name: "Norwegian", flag: "🇳🇴" }, + "no-NO": { name: "Norwegian (Norway)", flag: "🇳🇴" }, + "cs": { name: "Czech", flag: "🇨🇿" }, + "cs-CZ": { name: "Czech (Czech Republic)", flag: "🇨🇿" }, + "el": { name: "Greek", flag: "🇬🇷" }, + "el-GR": { name: "Greek (Greece)", flag: "🇬🇷" }, + "he": { name: "Hebrew", flag: "🇮🇱" }, + "he-IL": { name: "Hebrew (Israel)", flag: "🇮🇱" }, + "th": { name: "Thai", flag: "🇹🇭" }, + "th-TH": { name: "Thai (Thailand)", flag: "🇹🇭" }, + "vi": { name: "Vietnamese", flag: "🇻🇳" }, + "vi-VN": { name: "Vietnamese (Vietnam)", flag: "🇻🇳" }, + "id": { name: "Indonesian", flag: "🇮🇩" }, + "id-ID": { name: "Indonesian (Indonesia)", flag: "🇮🇩" }, + "ms": { name: "Malay", flag: "🇲🇾" }, + "ms-MY": { name: "Malay (Malaysia)", flag: "🇲🇾" }, +}; + +// Helper function to get language display info +function getLanguageInfo(locale: string): { name: string; flag: string } { + // Try exact match first + if (LANGUAGE_MAP[locale]) { + return LANGUAGE_MAP[locale]; + } + // Try base language code (e.g., "en-US" -> "en") + const baseCode = locale.split("-")[0]; + if (LANGUAGE_MAP[baseCode]) { + return LANGUAGE_MAP[baseCode]; + } + // Fallback to locale code itself + return { name: locale, flag: "🌐" }; +} + export function LiveDemo() { const [isSupported, setIsSupported] = useState(null); const [isListening, setIsListening] = useState(false); @@ -44,6 +123,7 @@ export function LiveDemo() { const [hasPermission, setHasPermission] = useState(null); const [lastCtrlPress, setLastCtrlPress] = useState(null); const [showCtrlHint, setShowCtrlHint] = useState(false); + const [detectedLanguage, setDetectedLanguage] = useState("en-US"); const recognitionRef = useRef(null); const isListeningRef = useRef(false); @@ -52,10 +132,14 @@ export function LiveDemo() { isListeningRef.current = isListening; }, [isListening]); - // Check for browser support + // Check for browser support and detect language useEffect(() => { const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition; setIsSupported(!!SpeechRecognitionAPI); + + // Detect browser language + const browserLang = navigator.language || "en-US"; + setDetectedLanguage(browserLang); }, []); // Initialize recognition @@ -245,6 +329,18 @@ export function LiveDemo() { Web Speech API + {(() => { + const langInfo = getLanguageInfo(detectedLanguage); + return ( + + {langInfo.flag} + {langInfo.name} + + ); + })()} {transcript && (