11import { useState } from 'preact/hooks'
2- import { SQL_INFO } from './constants'
2+ import { SQL_QUERIES } from './constants'
3+ import { LOCALES , type Locale , useI18n } from './i18n'
4+ import type { Translations } from './i18n/types'
35import {
46 InnerJoinSVG ,
57 LeftAntiJoinSVG ,
@@ -10,13 +12,29 @@ import {
1012import Tables from './Tables'
1113import type { JoinType } from './types'
1214
15+ /**
16+ * Get the description for a join type from translations
17+ */
18+ function getJoinDescription ( t : Translations , join : JoinType ) : string {
19+ const descriptionMap : Record < JoinType , keyof Translations > = {
20+ inner : 'innerJoinDesc' ,
21+ left : 'leftJoinDesc' ,
22+ leftanti : 'leftAntiJoinDesc' ,
23+ right : 'rightJoinDesc' ,
24+ outer : 'outerJoinDesc' ,
25+ }
26+ return t [ descriptionMap [ join ] ]
27+ }
28+
1329/**
1430 * Main application component for Visual JOIN
1531 * Allows users to select different join types and see the results visually
1632 */
1733function JoinsApp ( ) {
34+ const { t, locale, setLocale } = useI18n ( )
1835 const [ currentJoin , setCurrentJoin ] = useState < JoinType > ( 'inner' )
1936 const [ showDesc , setShowDesc ] = useState ( false )
37+ const [ showLangMenu , setShowLangMenu ] = useState ( false )
2038
2139 /**
2240 * Generates className for join buttons with active state
@@ -29,17 +47,58 @@ function JoinsApp() {
2947
3048 const selectJoin = ( join : JoinType ) => {
3149 setCurrentJoin ( join )
32- setShowDesc ( false )
3350 }
3451
3552 return (
3653 < >
54+ { /* Language Switcher */ }
55+ < div className = "language-switcher" >
56+ < button
57+ type = "button"
58+ className = "button-reset language-toggle"
59+ onClick = { ( ) => setShowLangMenu ( ! showLangMenu ) }
60+ aria-label = { t . language }
61+ aria-expanded = { showLangMenu }
62+ >
63+ < svg
64+ xmlns = "http://www.w3.org/2000/svg"
65+ viewBox = "0 0 24 24"
66+ fill = "none"
67+ stroke = "currentColor"
68+ stroke-width = "2"
69+ stroke-linecap = "round"
70+ stroke-linejoin = "round"
71+ aria-hidden = "true"
72+ >
73+ < circle cx = "12" cy = "12" r = "10" />
74+ < path d = "M2 12h20" />
75+ < path d = "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
76+ </ svg >
77+ </ button >
78+ { showLangMenu && (
79+ < ul className = "language-menu" >
80+ { Object . entries ( LOCALES ) . map ( ( [ code , name ] ) => (
81+ < li key = { code } >
82+ < button
83+ type = "button"
84+ className = { `button-reset language-option ${ locale === code ? 'is-active' : '' } ` }
85+ onClick = { ( ) => {
86+ setLocale ( code as Locale )
87+ setShowLangMenu ( false )
88+ } }
89+ >
90+ { name }
91+ </ button >
92+ </ li >
93+ ) ) }
94+ </ ul >
95+ ) }
96+ </ div >
97+
3798 { /* Header */ }
3899 < div className = "header" >
39- < h1 > Visual JOIN</ h1 >
40- < span >
41- Understand how joins work by interacting and see it visually
42- </ span >
100+ < h1 > { t . title } </ h1 >
101+ < span > { t . subtitle } </ span >
43102 </ div >
44103
45104 < div className = "content" >
@@ -50,16 +109,16 @@ function JoinsApp() {
50109 className = { currentJoinClass ( 'inner' ) }
51110 onClick = { ( ) => selectJoin ( 'inner' ) }
52111 >
53- < h2 > INNER JOIN </ h2 >
54- < div className = "subtitle" > (or JOIN) </ div >
112+ < h2 > { t . innerJoin } </ h2 >
113+ < div className = "subtitle" > { t . orJoin } </ div >
55114 < InnerJoinSVG />
56115 </ button >
57116 < button
58117 type = "button"
59118 className = { currentJoinClass ( 'left' ) }
60119 onClick = { ( ) => selectJoin ( 'left' ) }
61120 >
62- < h2 > LEFT JOIN </ h2 >
121+ < h2 > { t . leftJoin } </ h2 >
63122 < div className = "subtitle" > </ div >
64123 < LeftJoinSVG />
65124 </ button >
@@ -68,16 +127,16 @@ function JoinsApp() {
68127 className = { currentJoinClass ( 'leftanti' ) }
69128 onClick = { ( ) => selectJoin ( 'leftanti' ) }
70129 >
71- < h2 > LEFT ANTI JOIN </ h2 >
72- < div className = "subtitle" > (with WHERE IS NULL) </ div >
130+ < h2 > { t . leftAntiJoin } </ h2 >
131+ < div className = "subtitle" > { t . withWhereIsNull } </ div >
73132 < LeftAntiJoinSVG />
74133 </ button >
75134 < button
76135 type = "button"
77136 className = { currentJoinClass ( 'right' ) }
78137 onClick = { ( ) => selectJoin ( 'right' ) }
79138 >
80- < h2 > RIGHT JOIN </ h2 >
139+ < h2 > { t . rightJoin } </ h2 >
81140 < div className = "subtitle" > </ div >
82141 < RightJoinSVG />
83142 </ button >
@@ -86,23 +145,25 @@ function JoinsApp() {
86145 className = { currentJoinClass ( 'outer' ) }
87146 onClick = { ( ) => selectJoin ( 'outer' ) }
88147 >
89- < h2 > OUTER JOIN </ h2 >
90- < div className = "subtitle" > (with UNION) </ div >
148+ < h2 > { t . outerJoin } </ h2 >
149+ < div className = "subtitle" > { t . withUnion } </ div >
91150 < OuterJoinSVG />
92151 </ button >
93152 </ div >
94153
95154 { /* SQL Query and Description */ }
96155 < div className = "sql-container" >
97- < div className = "sql" > { SQL_INFO [ currentJoin ] . query } </ div >
156+ < div className = "sql" > { SQL_QUERIES [ currentJoin ] } </ div >
98157 < button
99158 type = "button"
100159 className = "show-desc"
101160 onClick = { ( ) => setShowDesc ( ! showDesc ) }
102161 >
103- { showDesc ? 'Hide description »' : 'Description »' }
162+ { showDesc ? ` ${ t . hideDescription } »` : ` ${ t . description } »` }
104163 </ button >
105- { showDesc && < div className = "desc" > { SQL_INFO [ currentJoin ] . desc } </ div > }
164+ { showDesc && (
165+ < div className = "desc" > { getJoinDescription ( t , currentJoin ) } </ div >
166+ ) }
106167 </ div >
107168
108169 { /* Tables */ }
0 commit comments