import { useState } from 'react'
import { genderCheck, translateText } from '../api'
import { speak } from '../tts'
// Shared "quick tools" — the Gender Checker and Translator from the Tools
// screen, also reusable as a side panel while doing exercises (see
// Exercises.jsx).
// ── Gender Checker ───────────────────────────────────────────────────────────
const GENDER_COLORS = { Masc: '#4A90D9', Fem: '#D96B8A' }
const GENDER_LABELS = { Masc: 'Masculine ♂', Fem: 'Feminine ♀' }
export function GenderChecker() {
const [word, setWord] = useState('')
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleCheck = async () => {
const w = word.trim()
if (!w) return
setLoading(true)
setError('')
try {
const result = await genderCheck(w)
setData(result)
} catch (e) {
setError(`Could not check this word: ${e.message}`)
} finally {
setLoading(false)
}
}
const color = data?.gender ? GENDER_COLORS[data.gender] : 'var(--fc-ink)'
return (
Type a French noun to see its gender, articles (le/la, un/une), an example sentence, and a tip for remembering it.
setWord(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCheck()}
placeholder="e.g. pomme, restaurant, voiture…"
/>
{loading ? 'Checking…' : '🔍 Check'}
{error &&
⚠ {error}
}
{data && (
{data.article ? `${data.article} ` : ''}{data.word}
speak(`${data.article} ${data.word}`.trim())}>
🔊
{data.gender && (
{GENDER_LABELS[data.gender]}
{data.article} {data.word}
{data.indefinite_article} {data.word}
)}
{data.example && (
{data.example}
speak(data.example)}>🔊
{data.example_translation &&
{data.example_translation}
}
)}
{data.pattern_note &&
💡 {data.pattern_note}
}
)}
)
}
// ── Translator ───────────────────────────────────────────────────────────────
const DIRECTIONS = [
{ id: 'en_fr', label: 'English → French' },
{ id: 'fr_en', label: 'French → English' },
]
export function TranslatorWidget({ lessonText, onRemove }) {
const [text, setText] = useState('')
const [direction, setDirection] = useState('en_fr')
const [useContext, setUseContext] = useState(false)
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleTranslate = async () => {
const t = text.trim()
if (!t) return
setLoading(true)
setError('')
try {
const result = await translateText(t, direction, useContext ? lessonText : '')
setData(result)
} catch (e) {
setError(`Could not translate: ${e.message}`)
} finally {
setLoading(false)
}
}
const switchDirection = (id) => {
if (id === direction) return
setDirection(id)
setData(null)
}
return (
{onRemove && (
✕
)}
{DIRECTIONS.map((d) => (
switchDirection(d.id)}
>
{d.label}
))}
)
}
let widgetIdCounter = 0
function nextWidgetId() {
widgetIdCounter += 1
return widgetIdCounter
}
export function TranslatorPanel({ lessonText }) {
const [widgetIds, setWidgetIds] = useState(() => [nextWidgetId()])
const addWidget = () => {
setWidgetIds((ids) => (ids.length < 3 ? [...ids, nextWidgetId()] : ids))
}
const removeWidget = (id) => {
setWidgetIds((ids) => (ids.length > 1 ? ids.filter((i) => i !== id) : ids))
}
return (
Translate a word or phrase between English and French — with alternatives and an example in context.
Add more translators to check a few words or sentences side by side.
{widgetIds.map((id) => (
1 ? () => removeWidget(id) : null}
/>
))}
{widgetIds.length < 3 && (
+ Add another translator
)}
)
}