Author: Jonathan Dufault

  • I Built a Hands-Free French Dictionary So I Could Stop Putting My Book Down

    I Built a Hands-Free French Dictionary So I Could Stop Putting My Book Down

    I’m at that stage of learning French where I can read a novel, but not without stopping every couple of paragraphs to look something up. Solid B1, creeping toward B2. I can follow the plot. I can get the gist. But every few lines there’s a word that could mean three different things depending on context, and if I just guess, I’m probably going to learn the wrong meaning and carry it around for months.

    The standard advice is to look it up. And that’s correct. The act of actively searching for a word builds stronger neural pathways than having someone hand you the answer. But when I’m reading on my couch and I have to pick up my phone, open an app, type the word (with accents I don’t have memorized on the keyboard), read the definition, and then find my place again… I’m not reading anymore. I’m doing vocabulary drills that happen to be interrupted by a novel.

    I wanted something in between. Not a flashcard system, not a study tool. A way to keep reading without stopping too much, and without filling in the gaps wrong from context.

    Phone on the counter

    The concept is dead simple. I set my phone on the counter, or the table, or wherever I’m reading. When I hit a word I don’t know, I say it out loud. The app recognizes the French, looks up the English translation, displays it, and reads it back to me. My eyes never leave the page.

    I know this isn’t optimal for retention. I’m trading memory strength for reading flow. But the goal isn’t to memorize every word on first encounter. It’s to get through 40 pages instead of 12, and to not build a mental dictionary of wrong definitions by guessing from context clues that I’m not advanced enough to read correctly yet.

    How it works

    The whole thing is a single HTML file. No framework, no build step, no backend. It uses the Web Speech API (built into Chrome and Edge) for voice recognition with the language set to fr-FR, the MyMemory translation API for French-to-English lookups, and the browser’s built-in text-to-speech to read the result back.

    There’s a gender detection feature too. French nouns are masculine or feminine, and the endings give you a decent clue. The app guesses based on common patterns and shows a colored badge next to the word, pink or blue. It’s heuristic, not dictionary-backed, so it’s wrong sometimes. But when I’m mid-page and just want a rough sense of the article to use, it does the job.

    French Voice Dictionary app showing the word entre guillemets with its translation between quotation marks, with a red Stop Listening button and dark themed UI
    Looking up “entre guillemets” (between quotation marks). Multi-word phrases work too.

    I can say whole phrases too, not just single words. Saying “entre guillemets” gives me “between quotation marks.” Saying “dispositif de secours” gives me “backup device.” If I’m somewhere I can’t talk out loud, or the recognition is mangling my pronunciation on a specific word, there’s a text input as a fallback.

    The whole thing runs on GitHub Pages. No server, no cost, and I can pull it up on my phone’s browser while I read.

    The debugging war

    Getting speech recognition to work was straightforward. Getting it to work continuously was a different story.

    The first version worked fine for one word. Say “bonjour,” get a definition, great. Say a second word and nothing happened. The app looked alive. The button still said “Stop Listening,” the status dot was green. But it had gone deaf.

    Here’s what was happening: while the app processed a word (the API call, then the text-to-speech playback), a flag blocked all incoming speech. Anything I said during that window got dropped silently. No error, no feedback, just gone. And the browser’s text-to-speech onend event? It sometimes just doesn’t fire. Known quirk, no fix. When that happened, the flag stayed on forever and the app was bricked until I refreshed.

    It got worse before it got better. At one point the microphone was re-prompting for permission on every recognition cycle. The app started catching its own TTS output and trying to look up its own definitions in an infinite loop. We didn’t just screw the pooch. Basically every dog in the neighborhood.

    The fix was an interrupt system. Instead of dropping speech during processing, the app now queues the new word and cancels the current playback. A timeout fallback catches the cases where onend never fires. And recognition stops entirely during TTS so the microphone doesn’t pick up the speaker. Now I can say a new word mid-definition and it switches right over.

    Tradeoffs I’m fine with

    The MyMemory translation API is free, but the free tier caps at 5,000 characters per day. Since it’s on GitHub Pages with no login, every person who visits shares that same daily bucket. For something I use a few times a week, this hasn’t been a problem. If it ever is, I’ll swap APIs or add a key.

    I tried adding English text-to-speech for the translation portion, so it would say “femme signifie” in French and then “woman” in English. Switching TTS languages mid-sentence didn’t work well in any browser I tested. I killed it after one session. Sometimes the simpler version is the better version.

    Gender detection is a guess, not a lookup. Common patterns are right, exceptions are wrong. Good enough for a reading aid, not good enough for a grammar quiz.

    Try it out

    The app is live at jondufault.github.io/french-verbal-dictionary and the source is on GitHub. It’s one HTML file.

    On the list for later: tracking how often each word gets looked up (to spot my persistent gaps), and a local cache with export so I can feed lookups into a spaced repetition system. For now it does the one thing I needed. I can read without stopping.

  • Making Tempeh: An Unreasonably Thorough Approach

    Making Tempeh: An Unreasonably Thorough Approach

    I have had so much trouble making tempeh. Crumbly, inconsistent results, batch after batch. And the troubleshooting guides online? Useless. Every single one boils down to the same set of contradictions:

    • You cooked the beans too much
    • You cooked the beans too little
    • You dried the beans too much
    • You dried the beans too little
    • You incubated too hot
    • You incubated too cold
    • You packed too tight
    • You packed too loose
    • You split the beans too much
    • You split the beans too little

    Right. So that narrows it down to everything. I decided the only way forward was to go clinical — document every step, measure every variable, and remove every excuse. If this batch failed, I’d know exactly how and why.

    Cracking the Beans

    Most instructions say to soak the beans and then scrub the hulls off by hand, squeezing each one between your fingers. I skipped that entirely. It’s a waste of water and time when you can just pre-crack them.

    KoMo Fidibus XL grain mill on granite countertop
    My KoMo Fidibus XL. I’ve had this mill for over a decade and it has paid for itself many times over.
    Soybeans loaded in the grain mill hopper
    Soybeans loaded and ready to crack.

    I widened the grinding wheels and ran a few test passes until I found a setting that splits the beans in half without creating too much dust. When you crack them this way, the hulls tend to fall right off.

    A note: this post mixes photos from two batches — one garbanzo, one soybean. The process is the same for both.

    Cracked garbanzo beans in a blue bowl
    Cracked and dehulled in about two minutes.
    Bean hulls and dust in a blue colander
    Running the cracked beans through a colander to sift out the dust.

    I shook the colander a few times and the empty hulls floated to the top. A quick pass with a hair dryer — one I keep in the kitchen specifically for cooking — cleared them off in a couple of passes.

    Clean split soybean halves in a blue colander
    Clean splits. Hulls removed, minimal dust.

    I boiled the beans until they reached the consistency of a boiled peanut — maybe a lima bean. Soft enough to eat, firm enough to hold shape. I didn’t photograph this step because it’s just boiling beans.

    The Bags

    For tempeh, you need a bag with small holes — enough airflow for the Rhizopus mold to breathe, but not so much that the surface dries out. Traditional tempeh is wrapped in banana leaves; we’re making an artificial one.

    Brother XM2701 sewing machine
    The sewing machine. Another piece of equipment that’s earned its counter space.

    I read a paper that described optimal tempeh incubation using bags with holes punched by a number 7 needle, spaced half an inch apart, on 1.5mm polyethylene. Here’s what I actually used a size 12 sewing needle at one-inch intervals on a 3mm polyethylene bag. Size 12 is thicker than size 7.

    Drying and Inoculation

    This is the step I suspect most guides don’t emphasize enough, and where most batches quietly go wrong.

    Beans drying on a parchment-lined baking sheet in the oven
    Drying in the oven at 170°F, stirring every few minutes.

    I set my oven to 170°F and stirred every few minutes until the beans were dry. Actually dry — not “they look dry.” Dry as in my hand doesn’t get wet when I grab a handful. I raised my fist to my face and told each bean it would become tempeh or die.

    Once the surface moisture was gone, I added a few tablespoons of distilled white vinegar and let that evaporate too. The vinegar lowers the pH enough to give the Rhizopus a head start over competing bacteria.

    Tempeh starter packet labeled Ragi Tempe
    The tempeh starter (Rhizopus oligosporus). Kept in my freezer until needed.

    Mixed the starter into the cooled, dry beans. Packed them into the perforated bags, pressed flat to about an inch thick, sealed them up.

    Incubation

    Brod and Taylor folding proofer displaying 90 degrees
    The Brod & Taylor folding proofer, set to 90°F. Designed for bread, but it holds temperature precisely enough for fermentation work.

    At this point I hadn’t confirmed the optimal incubation range. A quick search turned up this:

    Growth rate vs incubation temperature chart for Rhizopus
    Rhizopus growth rate peaks around 30–35°C (86–95°F) and drops sharply above 37°C. Source: tempeh.info

    I adjusted to 86°F and loaded the bags.

    Four bags of inoculated beans in the incubator
    Four bags loaded, day zero. No visible growth.

    Over-Engineering the Monitoring

    I wanted the actual temperature inside the bean cake, not just the ambient air reading from the incubator’s display. So I ran a probe thermometer directly into one of the bags.

    Temperature probe cable running into the incubator
    Temperature probe running into the bean cake.

    Then I built a data logger.

    An ESP8266 microcontroller, programmed with Arduino to read the temperature sensor and transmit data over WiFi at three-second intervals.

    Raspberry Pi connected to home network panel
    The Raspberry Pi, connected directly to the router. This is the server receiving and logging the temperature data.

    I wrote a small web server so I could check temperatures from my phone. If someone was going to tell me the incubation temperature was wrong, I’d have a timestamped log at three-second resolution to discuss.

    Phone screen showing timestamped temperature log
    Raw temperature log. Timestamped, continuous, three-second resolution.

    Was this level of monitoring necessary for making tempeh? No. But the troubleshooting advice I kept getting was some variation of “your temperature was probably wrong,” and I was done guessing.

    The Wait

    After 12 hours: nothing visible. The bags looked exactly the same as when I loaded them.

    Four bags in incubator showing no visible change after twelve hours
    Twelve hours in. The bags look exactly the same.

    I wrote a pointed review of the tempeh starter on Amazon.

    But I checked back at lunch the next day and noticed something. The tempeh didn’t look different yet, but the temperature probe told a different story — the internal temperature was climbing above ambient. The beans were generating their own heat. Something was growing.

    Annotated scatter plot of temperature vs time
    The temperature log tells the whole story. You can see where I accidentally started at 90°F and had to let it cool, where the temperature crept up and I turned off the incubator a little too long, and finally — around hour 18 — where the tempeh started generating its own metabolic heat. I turned the incubator off entirely and let the mold regulate itself.

    It Worked

    I opened the incubator and saw mycelium.

    White mycelium growing through the soybeans
    Mycelium. Finally.
    Chart showing bean temperature vs incubator setting over time
    The full picture. Blue is the actual bean temperature; red dashed line is the incubator setting. At the end, the incubator is off and the tempeh is holding its own temperature around 30°C. Self-sustaining fermentation.

    A few more hours and the beans were fully bound together. Dense, white, solid blocks.

    Four completed blocks of tempeh
    Four blocks of finished tempeh. Uniform mycelium growth, firm structure.

    I changed my Amazon review.

    Amazon review updated to five stars
    “Pretty good. Don’t give up on it.” — updated to 5 stars.

    What Actually Mattered

    The vague troubleshooting guides aren’t wrong, exactly — they’re just useless without measurement. “Too hot” and “too cold” don’t mean anything without a number attached. After going through this with three-second temperature resolution and documented steps, here’s what I think actually makes the difference:

    1. Dry the beans completely. Not “they look dry” — your hand shouldn’t feel any moisture when you grab a fistful. Then dry them a little more. Then add vinegar and dry that too.
    2. Start around 86°F (30°C), but watch it. Once the mold takes hold at around 18–24 hours, it generates enough metabolic heat to overshoot the optimal range. You may need to turn the incubator down or off entirely.
    3. Twelve hours of nothing is normal. The growth is invisible at first. If your temperature is in range and your beans were properly inoculated, wait. It happens fast once it starts.
    4. Measure what you can. You don’t need an ESP8266 and a Raspberry Pi (probably). But a probe thermometer inside the bean cake, rather than relying on the incubator’s ambient display, would have saved me several failed batches.