← Back to list

I built a tool that reads actual code diffs and generates multilingual changelogs.

So I was upgrading a package last month and I went to check the changelog. “fix: bug” “update: auth package” “feat: new ui”

Mehul Ligade · 2026-03-15 07:47 · 37 claps · 4.9 min read
#lingo #hackathons #ai #github #project-management-tool
Open on Medium ↗
Wiki topics: AI · AI · General BIZ · Business Strategy LNG · Linguistics & Language 🔓 · Open Source

I built a tool that reads actual code diffs and generates multilingual changelogs. Here is how it went. | M009

So I was upgrading a package last month and I went to check the changelog. “fix: bug” “update: auth package” “feat: new ui”

That is it. Three lines. I still had no idea if upgrading would break anything in my project. I just closed the tab and hoped for the best.

And I thought okay, this is genuinely a problem. Not just for me but every developer does this. Every single day!!!

So I decided to build something. I called it PushNotes.

The idea was simple but The execution was not.

The plan was to take a GitHub repo URL, fetch the commits, summarize them nicely, done right? Wrong!

The first version I built just read commit messages and sent them to Gemini. The output was exactly as vague as the input. “Updated auth module.” “Fixed bug in CLI.” Useless.

Then I realized the problem. Commit messages are written by tired developers at 2am who just want to push and go to sleep. They are not documentation. They never were.

So I changed the approach completely. Instead of reading commit messages I started pulling the actual file diffs. The real code that changed. Lines added, lines removed, the full patch.

I sent that to Gemini and asked it to explain what changed, why it changed, and who would be affected.

The output was completely different.

Instead of “updated auth module” I was getting things like “the whoami function now validates your token against the new API endpoint before returning a response, so if you upgrade without refreshing your token you are going to get a 401 immediately.”

That is actually useful. That is something I can read and decide from.

Then I realized there was a second problem.

Even if I fix the quality problem, the changelog is still always in English.

I have friends who are developers in Japan, China, Brazil. They are all reading English documentation every day because there is no alternative. They manage, but it is extra cognitive load that English speakers never think about.

I wanted to fix that too.

So I integrated Lingo.dev for translation and this is where it got interesting.

My first instinct was to translate on every request. User wants Hindi, call the API, get Hindi, show it. Simple!

But that would mean paying for the same translation every single time someone opens the same changelog & that made no sense.

So I designed it differently. First time someone requests a language, Lingo.dev translates the full content bundle. That translation gets stored permanently in Neon Postgres. Every single person after that gets the cached version served directly from the database.

0 translation cost after the first request. 0 latency. The same changelog in Japanese serves a thousand users for the price of one translation.

And to make sure two users hitting translate at the same time never create duplicate records I added a UNIQUE constraint on changelog_id and locale in the database. The database itself prevents waste at the infrastructure level.

The part I did not plan but became my favourite feature.

Halfway through building this I started thinking about who actually uses changelogs. Developers obviously. But also product managers, Team leads, Security engineers. People who care deeply about what shipped but cannot read a pull request.

For them a list of text entries is not much better than the original “fix bug” entries. They need something visual.

So I built the Knowledge Graph.

It is a visual map of everything that changed in a release. The release title sits at the center. Packages orbit around it. Individual files orbit around their package. Every node is color coded by risk. Red means that file was touched by three or more commits in this release, high churn, something significant changed there. Green means stable and safe.

A PM can look at this for ten seconds and understand the blast radius of a release without reading a single commit. A new developer joining the team can see immediately where the active development is and where the stable parts are.

And if you click any file you get the actual diff and an AI explanation of what changed. Written natively in whatever language you are currently using. Not translated from English. Generated directly in that language.

Building the layout for this graph was its own adventure. I tried dagre first which is the standard library for this. It produced a horizontal mess with nodes overlapping everywhere. Not readable at all.

So I threw dagre out and wrote the layout myself. Pure trigonometry. Each package gets an angle based on its position in the total count. Files spread around their package angle. The polar to Cartesian conversion is literally ten lines of code.

function polar(cx, cy, r, angleDeg) {
  const rad = (angleDeg - 90) * (Math.PI / 180)
  return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }
}

That is the whole layout algorithm. Clean solar system, zero overlaps, works for any number of packages.

The bug that almost broke me.

Because I return the changelog to the user immediately and save to the database in the background, there is a race condition. The user gets redirected to their changelog page before the database finishes saving. The page tries to load and gets a 404.

First time I hit this I thought the whole background save approach was broken. Spent an hour debugging.

The fix was actually simple once I understood the problem. I added a retry loop in the server component. If the URL has a new flag in it, the server retries the database lookup six times with one second gaps before giving up. The user sees a loading skeleton. The database finishes saving in the background. By the time the retries complete the data is there.

Never show a broken page. Always give the database time to catch up.

What I learned from this.

A few things that I will carry into every project after this.

Reading the actual data instead of the summary of the data almost always gives you better AI output. Commit messages are summaries written by humans in a hurry. File diffs are ground truth.

Caching at the right layer changes everything. Translating every request would have made this product economically unviable. Storing translations permanently made it scale infinitely for a fixed cost.

And building for non-technical users from the beginning makes a better product for technical users too. The Knowledge Graph was designed for PMs but developers love it more.

What is next.

I want to add contributor analytics. Who touched what, how often, which developer has the most churn in their files. Useful for team leads doing retrospectives.

I also want to make the graph filterable. Show me only breaking changes. Show me only files touched by more than two commits. Make it queryable.

And I want to explore embedding this into GitHub Actions so the changelog generates automatically on every release tag. No manual URL pasting needed.

If you want to try it locally the repo is at github.com/mehulcode12/push-notes. Setup takes under 2 mins.

Built this for the Lingo.dev Multilingual Hackathon 3. Translation powered by Lingo.dev throughout.

Happy to answer any questions in the comments about the architecture, the graph layout, or the caching approach. All of it was genuinely interesting to figure out.


메타데이터
post_id
e2eb2be07f63
slug
i-built-a-tool-that-reads-actual-code-diffs-and-generates-multilingual-changelogs-e2eb2be07f63
url
https://medium.com/@mehulligade12/i-built-a-tool-that-reads-actual-code-diffs-and-generates-multilingual-changelogs-e2eb2be07f63
canonical_url
https://medium.com/@mehulligade12/i-built-a-tool-that-reads-actual-code-diffs-and-generates-multilingual-changelogs-e2eb2be07f63
author_url
https://medium.com/@mehulligade12
status
ok
fetched_at
2026-06-25 07:00:49