Skip to content
Sami Aktaş

12 entries · September 1, 2026

TR

Ledger by venture

Ekran Çeviri

12 entries — real, dated progress notes for Ekran Çeviri.

Visit the Ekran Çeviri website

Dropped the Grok API: Claude is now the single AI hub

I cancelled the Grok API subscription; from now on Claude is my single AI hub. Ekran Çeviri's translation engine was wired to Grok — Google and Bing engines are still in the code as fallbacks, but the quality gates assume the Grok path. So this cancellation calls for a rework of the app's translation pipeline; the code still contains GrokMotor. I log abandoned decisions too, because building in public means showing what got reversed, not only what worked.

Grok API · Claude · Translation Engine · Reversed Decision

Stripped the repo down to Windows and shipped v1.1.0

I removed the old platform's source from the repo (it still lives in git history), moved the Windows project to the root, and rewrote the README from scratch for a Windows user. I wired the API key flow end to end: the key is asked for on first launch, masked as you type, stored with Windows' own encryption (DPAPI), and never written to the settings file; text that doesn't start with "xai-" is rejected, because a bad paste used to silently break every translation. GitHub Actions passed 54/54 tests on a real Windows machine and produced the 74 MB single-file .exe that needs no installer — and if the tests fail, no .exe is produced at all.

Release · GitHub Actions · DPAPI · API Key

Five-Expert Audit: 77 Findings and the Risk of Instructions Coming From the Screen

I ran a background audit from five expert angles (systems, security, linguistics, UX, QA); it produced 77 findings, distilled to 18 after removing duplicates. The most critical was a security one: the other person's on-screen messages were being fed raw into the system instruction and the output could be pasted without me reading it — meaning text on screen could effectively dictate my replies. Untrusted content is now explicitly labeled as data. The linguist caught "1,5 saat 150 chf" turning into "15 stund 150" in the outgoing message, so I added a number-preservation rule and a quality gate. I also took a backup and rewrote 13 commits of git history to purge a leaked xAI key, and closed QA's sneakiest finding: the tests were validating a copy of the production code, and now call the real functions.

Security · Prompt Injection · Git History · Testing · Audit

Wrote the Windows version from scratch as a second app

My friends are on Windows, and "adapting" the existing version wasn't possible — screen capture, text recognition and the entire UI were all platform-specific. So I wrote a separate app in C# / .NET 8 / WPF: capture via GDI BitBlt, on-device text recognition via Windows.Media.Ocr, the translation overlay as a click-through WPF layered window, and the shortcut via RegisterHotKey + SendInput. I ported the translation intelligence over intact — dialect detection, dictionaries, prompts, and the number and price protection gates. I ran 44 pure-logic tests, and one caught a real bug: the word "chli" was in Zürich's strong-signal list even though Bern uses it too, so someone writing in Bern dialect was getting replies in Zürich dialect. Fixed in both versions.

Windows · C# · .NET 8 · WPF · Port

The audit surfaced 32 defects; the worst one was deleting prices

I traced the "translations are wrong enough to be misread" complaint to its root: in live mode, blocks were matched purely by their position on screen, with no look at the text — so when a new message arrived and the list scrolled up, every message inherited the translation of the one above it. The second serious defect was sneakier: the timestamp cleaner was treating patterns like 12.50 (a price), 10-15 (a duration) and 12.05 (a date) as clock times and stripping them from the text. After the fix I measured it: number loss 0/5. I also put timeouts on the screen capture calls — with no response the work queue locked up permanently — and versioned and wiped the polluted translation memory, which had the app's own output and one-letter keys stored in it.

Audit · Translation Accuracy · OCR · Bug Fix

Found the Deadlock That Silently Killed "Translate Region"

After closing the app and hitting "Translate Region" again, nothing happened — not even a warning. The cause was a call in the middle of the background work queue that waited on the main thread: if the main thread was busy with a window, the queue deadlocked and the "translation in progress" flag stayed on forever. I removed the blocking call, took the confirmation dialog off the queue, and added a 15-second cancel for stuck jobs plus a health watchdog that checks every 5 seconds. I ran my own reproduction for 20 rounds and got 20/20 clean; that loop is now part of the release gate, so no build ships unless it passes.

Swift · Concurrency · Deadlock · Test Automation

Root cause of the crashes: two threads writing the same data

The app was crashing intermittently, and other times it threw a network error and translated nothing at all. The audit showed the translation cache, the block list and the translation strings were all being touched from two threads at once — that means memory corruption, i.e. a crash. I locked every one of them and then ran an 8-queue × 3,000-round hammer test without a single crash. On the network side, one transient failure was killing the whole translation; I gave it its own session with 3 retries spaced 0.6s and 1.8s apart, which dropped the wait on an unreachable host from 11 seconds to zero. The third problem was my own fault: I rebuilt the app ~20 times that day, and since the signature changed on every build the OS kept revoking the accessibility permission. I created a stable signing identity and verified the permission now survives across builds.

Crash · Concurrency · Network Resilience · Permissions

Built a Local Translation Memory and Fixed OCR Dropping Letters

I set up a flow that writes every translation to disk and checks that memory before calling the engine: re-translating the same messages dropped from 8.3 seconds to 0.00, and fuzzy matching catches it even when OCR reads the text slightly differently. I measured the real cause of the "dropped letters" complaint — the text was reaching Vision far too small; upscaling with Lanczos took the character error rate from 0.50% to 0.16% and fully-correct lines from 88.5% to 96.6%. Along the way I caught a numbering bug that was pinning translations to the wrong messages, stopped trusting the model for ordering, and added a regression test. I also closed a memory leak that grew from 31 MB to 106 MB in six minutes; it now sits flat at 81 MB.

OCR · Apple Vision · Caching · Memory Leak · Grok API

Taught it to tell Swiss German dialects apart, one by one

The app handled standard German fine but produced nonsense on Swiss German — which was exactly where I needed it. I pulled a golden test set of 15,610 messages out of real chat history and wrote detection that automatically distinguishes Zürich, Bern, Basel, Eastern Swiss and Wallis dialects; the panel shows which one is active, and the reply I write in Turkish comes out in that same dialect. In the chat-switching test, Bern → Zürich → Bern → Hochdeutsch all resolved correctly; 31/31 unit tests and a 6-minute, 40-round soak test passed clean. I also added a local translation memory, so a sentence translated once never goes to the model again.

Dialect Detection · Swiss German · Testing · Translation Memory

Translate-what-I-type shortcut, and an API key leaking into the build

I added a feature that takes what I type in Turkish and, on a keypress, rewrites it in the other person's language and in the tone I picked; the shortcut is now assignable from inside the app. During an audit I found the packaging script was copying my dev machine's config.json into the app bundle as the default settings — meaning my personal API key was shipping inside the distributed build. I cut that out. I also wrote 13 offline unit tests and gated the release script on them: no package is produced unless the tests pass.

Shortcut · Security · Testing · Release Gate

The app was reading its own translations and translating them again

I noticed live mode had gone into an infinite loop: the Turkish translation it painted on screen was picked up on the next pass as a "new message" and translated again. I excluded the translation layer from screen capture and the loop closed. In the same round I chained the engines — Bing → Google → Grok; if one fails the next takes over, and the panel shows which one produced the result. I also fixed the grouping logic that merged separate chat bubbles into a single block, since merged bubbles destroyed the meaning entirely.

Live Translation · OCR · Translation Engine · Bubble Grouping

Turned an Alfred script into a real desktop app

This started as a single translation script bolted onto Alfred; I moved it into a properly installed app that lives in the menu bar on its own. You drag a rectangle over a region of the screen and the app reads the text there and gives back the translation. Along the way I fixed two traps: because it ran as a background app, the OS was silently hiding every notification window it created, and the screen recording permission wasn't being requested under the right identity. By the end of the first day the app actually launched and could select a region.

Desktop · OCR · Screen Capture · Menu Bar