@@ -22,13 +22,117 @@ const PYPI_JSON = `https://pypi.org/pypi/${PYPI_PROJECT}/json`;
2222export const defaultLightThemeOption = EditorView . theme ( { '&' : { backgroundColor : 'whitesmoke' } } , { dark : false } ) ;
2323import styles from './index.module.css' ;
2424
25- // languages (kept short here; extend as needed)
25+ // Array of Languages on right side of code editor
2626const languages = [
27- { id : 'EN' , code3 : 'eng' , code2 : 'en' , name : 'English' , i18nName : 'English' , fontFamily : "Hack, 'Courier New', monospace" } ,
28- { id : 'HI' , default : true , code3 : 'hin' , code2 : 'hi' , name : 'Hindi' , i18nName : 'Hindi' , fontFamily : "Hack, 'Courier New', monospace" } ,
29- { id : 'UR' , code3 : 'urd' , code2 : 'ur' , name : 'Urdu' , i18nName : 'اردو' , direction : 'rtl' , fontFamily : "Hack, 'Courier New', monospace" } ,
30- { id : 'FR' , code3 : 'fra' , code2 : 'fr' , name : 'French' , i18nName : 'Français' , fontFamily : "Hack, 'Courier New', monospace" } ,
31- ] ;
27+ {
28+ id : "CS" ,
29+ code3 : "ces" ,
30+ code2 : "cs" ,
31+ name : "Czech" ,
32+ i18nName : "Čeština" ,
33+ fontFamily : "'Roboto Mono'" ,
34+ toEnglishDict : "'languages/cs/default.yaml'" ,
35+ } ,
36+ {
37+ id : "DE" ,
38+ code3 : "deu" ,
39+ code2 : "de" ,
40+ name : "German" ,
41+ i18nName : "Deutsch" ,
42+ fontFamily : "'Roboto Mono'" ,
43+ toEnglishDict : "'languages/de/default.yaml'" ,
44+ } ,
45+ {
46+ id : "UR" ,
47+ code3 : "urd" ,
48+ code2 : "ur" ,
49+ name : "Urdu" ,
50+ i18nName : "اردو" ,
51+ direction : "rtl" ,
52+ fontFamily : "'Roboto Mono'" ,
53+ toEnglishDict : "'languages/ur/default.yaml'" ,
54+ fontWeights : "bold" ,
55+ style : {
56+ direction : "rtl"
57+ }
58+ } ,
59+ {
60+ id : "HI" ,
61+ default : true ,
62+ code3 : "hin" ,
63+ code2 : "hi" ,
64+ name : "Hindi" ,
65+ i18nName : "Hindi" ,
66+ fontFamily : "'Roboto Mono'" ,
67+ toEnglishDict : "'languages/hi/default.yaml'" ,
68+ } ,
69+ {
70+ id : "EN" ,
71+ code3 : "eng" ,
72+ code2 : "en" ,
73+ name : "English" ,
74+ i18nName : "English" ,
75+ fontFamily : "'Roboto Mono'" ,
76+ } ,
77+ {
78+ id : "GA" ,
79+ code3 : "gle" ,
80+ code2 : "ga" ,
81+ name : "Irish" ,
82+ i18nName : "Gaeilge" ,
83+ fontFamily : "'Roboto Mono'" ,
84+ toEnglishDict : "'languages/ga/default.yaml'" ,
85+ } ,
86+ {
87+ id : "KO" ,
88+ code3 : "kor" ,
89+ code2 : "ko" ,
90+ name : "Korean" ,
91+ i18nName : "한국어" ,
92+ fontFamily : "'Roboto Mono'" ,
93+ toEnglishDict : "'languages/ko/default.yaml'" ,
94+ } ,
95+ {
96+ id : "FR" ,
97+ code3 : "fra" ,
98+ code2 : "fr" ,
99+ name : "French" ,
100+ i18nName : "Français" ,
101+ fontFamily : "'Roboto Mono'" ,
102+ toEnglishDict : "'languages/fr/default.yaml'" ,
103+ } ,
104+ {
105+ id : "TR" ,
106+ code3 : "tur" ,
107+ code2 : "tr" ,
108+ name : "Turkish" ,
109+ i18nName : "Türkçe" ,
110+ fontFamily : "'Roboto Mono'" ,
111+ toEnglishDict : "'languages/tr/default.yaml'" ,
112+ } ,
113+ {
114+ id : "HU" ,
115+ code3 : "hun" ,
116+ code2 : "hu" ,
117+ name : "Hungarian" ,
118+ i18nName : "Magyar" ,
119+ fontFamily : "'Roboto Mono'" ,
120+ toEnglishDict : "'languages/hu/default.yaml'" ,
121+ } ,
122+ // Added Emoji
123+ {
124+ id : "EMOJI" ,
125+ code3 : "emo" ,
126+ code2 : "emoji" ,
127+ name : "The Emoji Language" ,
128+ i18nName : "😀😃😄😁😆" ,
129+ fontFamily : "'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif" ,
130+ toEnglishDict : "'languages/emoji/default.yaml'" ,
131+ style : {
132+ fontSize : "1.2em"
133+ }
134+ } ,
135+ ]
32136
33137const initialCodes = [
34138 { id : 'hello_world' , name : ' Simple Hello World' , en : `print("Hello world!")` } ,
@@ -177,23 +281,34 @@ export default function Home() {
177281 const isBrowser = useIsBrowser ( ) ;
178282 const { country } = useGeoLocation ( ) ;
179283
284+ // States
180285 const [ editorCode , setEditorCode ] = useState ( initialCodes [ 0 ] . en ) ;
181286 const [ code , setCode ] = useState ( initialCodes [ 0 ] . en ) ;
182287 const [ translatedCode , setTranslatedCode ] = useState ( '' ) ;
183288 const [ isWaitingForCode , setIsWaitingForCode ] = useState ( false ) ;
184289 const [ isDetected , setIsDetected ] = useState ( false ) ;
185- const [ sourceLanguage , setSourceLanguage ] = useState ( languages . find ( l => l . code2 === 'en' ) ) ;
186- const [ targetLanguage , setTargetLanguage ] = useState ( languages . find ( l => l . default ) ) ;
290+
291+ // find Urdu by code2 'ur' (you asked for 'ur' key)
292+ const urduLang = languages . find ( l => l . code2 === 'ur' ) || languages . find ( l => l . id === 'UR' ) ;
293+ const defaultSource = languages . find ( l => l . code2 === 'en' ) || languages . find ( l => l . id === 'EN' ) ;
294+
295+ // track whether the user manually changed the target language (Option A)
296+ const userManuallySelectedTarget = useRef ( false ) ;
297+ // track if auto-detection has already run once
298+ const autoDetectDone = useRef ( false ) ;
299+ // track if URL had a tgt param on load
300+ const [ urlHasTgt , setUrlHasTgt ] = useState ( false ) ;
301+
302+ const [ sourceLanguage , setSourceLanguage ] = useState ( defaultSource ) ;
303+ // default target is Urdu per your request
304+ const [ targetLanguage , setTargetLanguage ] = useState ( urduLang || languages . find ( l => l . default ) ) ;
305+
187306 const [ loadingPyscript , setLoadingPyscript ] = useState ( true ) ;
188307 const [ wheelUrl , setWheelUrl ] = useState ( null ) ;
189308
190- // existing read-only snackbar (when overlay clicked)
309+ // existing snackbars / dialogs
191310 const [ snackOpen , setSnackOpen ] = useState ( false ) ;
192-
193- // snackbar for copy confirmations
194311 const [ copySnack , setCopySnack ] = useState ( { open : false , msg : '' , anchorOrigin : { } } ) ;
195-
196- // share dialog state (fallback)
197312 const [ shareDialogOpen , setShareDialogOpen ] = useState ( false ) ;
198313 const [ shareUrlValue , setShareUrlValue ] = useState ( '' ) ;
199314
@@ -213,6 +328,9 @@ export default function Home() {
213328 if ( tgt ) {
214329 const found = languages . find ( l => l . code2 === tgt || l . id === tgt ) ;
215330 if ( found ) setTargetLanguage ( found ) ;
331+ setUrlHasTgt ( true ) ; // mark that URL override exists
332+ // treat URL param as an explicit manual selection
333+ userManuallySelectedTarget . current = true ;
216334 }
217335 } , [ isBrowser ] ) ;
218336
@@ -242,8 +360,16 @@ export default function Home() {
242360 return ( ) => clearTimeout ( t ) ;
243361 } , [ editorCode ] ) ;
244362
245- // swap languages & swap editor text by swapping React state (fixes your issue)
363+ // Helper: mark that user manually selected target (blocks future auto-detect)
364+ const markUserManualTarget = ( alsoClearDetected = false ) => {
365+ userManuallySelectedTarget . current = true ;
366+ if ( alsoClearDetected ) setIsDetected ( false ) ;
367+ } ;
368+
369+ // swap languages & swap editor text by swapping React state (user action -> manual selection)
246370 const swapLanguages = ( ) => {
371+ // mark manual because the user clicked swap
372+ markUserManualTarget ( ) ;
247373 // swap language objects
248374 setSourceLanguage ( prevSource => { const oldSource = prevSource ; setTargetLanguage ( oldSource ) ; return targetLanguage ; } ) ;
249375 // swap code content between editor and translated output (we maintain code as the source text)
@@ -340,6 +466,98 @@ if data_el is not None:
340466 return ( ) => observer . disconnect ( ) ;
341467 } , [ isBrowser ] ) ;
342468
469+ // ---------- AUTO-DETECT LOGIC (Option A: only on first load) ----------
470+ // Hook: browser language detection (first-tier after URL param)
471+ useEffect ( ( ) => {
472+ if ( ! isBrowser ) return ;
473+ if ( autoDetectDone . current ) return ;
474+ if ( urlHasTgt ) {
475+ autoDetectDone . current = true ;
476+ return ;
477+ }
478+ if ( userManuallySelectedTarget . current ) {
479+ autoDetectDone . current = true ;
480+ return ;
481+ }
482+
483+ // Try navigator language first (but ignore English)
484+ try {
485+ const userLang = navigator . language || navigator . userLanguage || '' ;
486+ const _languageCode = ( userLang || '' ) . split ( '-' ) [ 0 ] . toLowerCase ( ) ;
487+ if ( _languageCode && _languageCode !== 'en' ) {
488+ const idx = languages . findIndex ( l => l . code2 === _languageCode ) ;
489+ if ( idx > - 1 ) {
490+ setTargetLanguage ( languages [ idx ] ) ;
491+ setIsDetected ( true ) ;
492+ autoDetectDone . current = true ;
493+ return ;
494+ }
495+ }
496+ } catch ( e ) {
497+ console . warn ( 'browser language detection failed' , e ) ;
498+ }
499+ // If browser didn't produce a non-English match, defer to country-based detection below
500+ } , [ isBrowser , urlHasTgt ] ) ;
501+
502+ // Hook: country (IP) -> restcountries lookup as a fallback after browser language
503+ useEffect ( ( ) => {
504+ if ( ! isBrowser ) return ;
505+ if ( autoDetectDone . current ) return ;
506+ if ( urlHasTgt ) { autoDetectDone . current = true ; return ; }
507+ if ( userManuallySelectedTarget . current ) { autoDetectDone . current = true ; return ; }
508+ if ( ! country ) return ; // wait until react-ipgeolocation provides country code
509+
510+ // Use restcountries to find languages for the alpha country code
511+ ( async ( ) => {
512+ try {
513+ const res = await fetch ( `https://restcountries.com/v3.1/alpha/${ country } ` ) ;
514+ if ( ! res . ok ) throw new Error ( 'restcountries fetch failed' ) ;
515+ const result = await res . json ( ) ;
516+ if ( ! Array . isArray ( result ) || result . length === 0 ) {
517+ throw new Error ( 'restcountries returned no data' ) ;
518+ }
519+ // `languages` is an object with keys like "eng","urd" or sometimes ISO 639-1 like "en","ur".
520+ const countryLangsObj = result [ 0 ] . languages || { } ;
521+ const countryLangCodes = Object . keys ( countryLangsObj || { } ) ; // keys might be 'urd','eng' or 'ur','en' etc.
522+
523+ // Attempt to find a supported language by matching code2 or code3, while skipping English
524+ let foundLang = null ;
525+ for ( const c of countryLangCodes ) {
526+ if ( ! c ) continue ;
527+ const keyLower = String ( c ) . toLowerCase ( ) ;
528+ if ( keyLower === 'eng' || keyLower === 'en' ) continue ; // skip English
529+
530+ // check code2 match first (e.g., 'ur')
531+ const byCode2 = languages . find ( l => l . code2 && l . code2 . toLowerCase ( ) === keyLower ) ;
532+ if ( byCode2 ) { foundLang = byCode2 ; break ; }
533+ // then check code3 match (e.g., 'urd' or 'fra')
534+ const byCode3 = languages . find ( l => l . code3 && l . code3 . toLowerCase ( ) === keyLower ) ;
535+ if ( byCode3 ) { foundLang = byCode3 ; break ; }
536+ }
537+
538+ if ( foundLang ) {
539+ setTargetLanguage ( foundLang ) ;
540+ setIsDetected ( true ) ;
541+ } else {
542+ // If country languages did not yield a non-English supported language, fallback to Urdu
543+ if ( urduLang ) {
544+ setTargetLanguage ( urduLang ) ;
545+ setIsDetected ( false ) ;
546+ }
547+ }
548+ } catch ( e ) {
549+ console . warn ( 'country-based detection failed' , e ) ;
550+ if ( urduLang ) {
551+ setTargetLanguage ( urduLang ) ;
552+ setIsDetected ( false ) ;
553+ }
554+ } finally {
555+ autoDetectDone . current = true ;
556+ }
557+ } ) ( ) ;
558+ } , [ country , isBrowser , urlHasTgt , urduLang ] ) ;
559+ // ---------- END AUTO-DETECT LOGIC ----------
560+
343561 // Snackbar helper (read-only)
344562 const showReadOnlySnack = ( ) => {
345563 setSnackOpen ( true ) ;
@@ -417,6 +635,23 @@ if data_el is not None:
417635 }
418636 } ;
419637
638+ // UI change handlers (ensure manual selection blocks further auto-detects)
639+ const handleSourceSelect = ( e ) => {
640+ const found = languages . find ( l => l . id === e . target . value ) ;
641+ if ( found ) setSourceLanguage ( found ) ;
642+ } ;
643+
644+ const handleTargetSelect = ( e ) => {
645+ const found = languages . find ( l => l . id === e . target . value ) ;
646+ if ( found ) {
647+ setTargetLanguage ( found ) ;
648+ // mark that the user manually changed the target language — Option A
649+ markUserManualTarget ( ) ;
650+ // clear detected flag because user explicitly chose
651+ setIsDetected ( false ) ;
652+ }
653+ } ;
654+
420655 return (
421656 < Layout title = { `${ siteConfig . title } | Programming for everyone` } description = "Write Python in any human language. Can't find yours? Easily contribute." >
422657 < Head >
@@ -458,7 +693,7 @@ if data_el is not None:
458693 { /* Left editor container (position: relative for floating button) */ }
459694 < div style = { { flex : 1 , position : 'relative' } } >
460695 < Box width = "250px" >
461- < TextField label = "From" fullWidth select onChange = { ( e ) => { const found = languages . find ( l => l . id === e . target . value ) ; if ( found ) { setSourceLanguage ( found ) ; setIsDetected ( false ) ; } } } value = { sourceLanguage ?. id } >
696+ < TextField label = "From" fullWidth select onChange = { handleSourceSelect } value = { sourceLanguage ?. id } >
462697 { languages . map ( ( l ) => < MenuItem key = { l . id } value = { l . id } > { l . name } </ MenuItem > ) }
463698 </ TextField >
464699 </ Box >
@@ -481,14 +716,16 @@ if data_el is not None:
481716 </ IconButton >
482717 </ Tooltip >
483718
484- < IDE id = "python-code-editor1" value = { editorCode } mode = "python" onChange = { ( text ) => { setEditorCode ( text ) ; } } height = { '240px' } style = { { margin : 12 , fontFamily : sourceLanguage ?. fontFamily } } />
719+ < div style = { { position : 'relative' , direction : sourceLanguage ?. direction || 'ltr' } } direction = { sourceLanguage ?. direction || 'ltr' } >
720+ < IDE id = "python-code-editor1" value = { editorCode } mode = "python" onChange = { ( text ) => { setEditorCode ( text ) ; } } height = { '240px' } style = { { borderRadius : '0.4rem' , overflow : 'hidden' , margin : 12 , fontFamily : sourceLanguage ?. fontFamily } } />
721+ </ div >
485722 </ div >
486723
487724 < Button sx = { { height : 'fit-content' , width : { xs : '100%' , md : '5%' } , m : { xs : '10px 0' , md : 0 } } } onClick = { swapLanguages } > ⇄</ Button >
488725
489726 < div style = { { flex : 1 , marginLeft : 12 , position : 'relative' } } >
490727 < Box display = "flex" alignItems = "center" justifyContent = "space-between" >
491- < TextField select label = "To" fullWidth onChange = { ( e ) => { const found = languages . find ( l => l . id === e . target . value ) ; if ( found ) setTargetLanguage ( found ) ; } } value = { targetLanguage ?. id } sx = { { maxWidth : 250 } } >
728+ < TextField select label = "To" fullWidth onChange = { handleTargetSelect } value = { targetLanguage ?. id } sx = { { maxWidth : 250 } } >
492729 { languages . map ( ( l ) => < MenuItem key = { l . id } value = { l . id } > { l . name } { targetLanguage ?. id === l . id ? isDetected ? ' - detected' : '' : '' } </ MenuItem > ) }
493730 </ TextField >
494731 </ Box >
@@ -512,8 +749,8 @@ if data_el is not None:
512749 </ Tooltip >
513750
514751 { /* Right-hand editor: real CodeMirror, readOnly. We render an overlay to catch interactions and show snackbar */ }
515- < div style = { { position : 'relative' } } >
516- < IDE id = "python-code-editor2" mode = "python" height = { '240px' } readOnly = { true } value = { translatedCode } basicSetup = { { direction : targetLanguage ?. direction || 'ltr' } } style = { { margin : 12 , whiteSpace : 'pre' , fontFamily : targetLanguage ?. fontFamily } } handleReadOnlyTyping = { showReadOnlySnack } />
752+ < div style = { { position : 'relative' , direction : targetLanguage ?. direction || 'ltr' } } direction = { targetLanguage ?. direction || 'ltr' } >
753+ < IDE id = "python-code-editor2" mode = "python" height = { '240px' } readOnly = { true } value = { translatedCode } basicSetup = { { direction : targetLanguage ?. direction || 'ltr' } } style = { { borderRadius : '0.4rem' , overflow : 'hidden' , margin : 12 , whiteSpace : 'pre' , fontFamily : targetLanguage ?. fontFamily } } handleReadOnlyTyping = { showReadOnlySnack } />
517754
518755 { /* transparent overlay to block interactions and show tooltip/snackbar on attempt */ }
519756 < div
@@ -579,6 +816,8 @@ if data_el is not None:
579816 ) ;
580817}
581818
819+
820+
582821const MaterialThemeWrapper = ( { children } ) => {
583822 const { colorMode } = useColorMode ( ) ; const isDarkTheme = colorMode === 'dark' ;
584823 const theme = React . useMemo ( ( ) => createTheme ( { palette : { mode : isDarkTheme ? 'dark' : 'light' } } ) , [ isDarkTheme ] ) ;
0 commit comments