Leadership announced that we'd be localising the entire platform the following quarter.
It was clearly going to be an impactful project - but it had no real plan or ownership yet.
So my team stepped up to lead the effort, creating a clear point of contact, setting direction, and making sure we actually got it shipped.

VEED localized in multiple languages
The Challenges
This wasn't a super-technical project - at least not at first glance.
We just needed to serve a different set of copy depending on language, right?
The tricky parts were going to be:
- Communication – The project touched 8+ codebases and 10+ teams. Everyone needed to stay in sync and keep their own roadmaps aligned.
- Maintaining translation quality – Automated translations… were they going to be good enough? Probably not.
- Measuring impact – We needed this to all go live simultaneously, and make sure it actually moves the needle.
Execution
We started from scratch: choosing tools, setting up infrastructure, and building the workflows.
We landed on Lokalise, i18next, and a few custom tools we built along the way.
Everything was feature-flagged, since we couldn't launch localisation half-done.
Half the team went on a mission to extract and translate every bit of copy they could find (painfully manual work... if only we'd had Cursor back then 😅).
The rest set up language detection, selectors, and tooling.
Our First Issue: Untranslated Keys Slipping Through
Pretty quickly, we hit our first issue - untranslated keys.
Occasionally, devs would see things like {{hero.button.copy}}. Not good, and definitely not something we could ship.
We realised i18next's defaultValue doesn't help if you already have some translations (e.g. English but not German). In that case, it just shows the key.
This was only going to happen more as more teams got involved, so we built our first internal tool.
🛠️ Translation Validator GitHub Action
- Runs on every PR
- Validates that all translation keys in the JSON files actually exist
- If a key is missing, the action fails and the PR can't be merged
This immediately stopped broken strings from hitting production. No more handlebars 🎉
Keeping Everyone Informed
Constant communication was absolutely vital:
- Regularly liaising with teams to decide which areas we'd tackle next
- Checking when they were free to test - translating a feature mid-refactor never ends well
- Sending twice-weekly updates to leadership to keep everyone aligned
This was a real cross-team effort, and staying proactive with communication was the only reason it went smoothly.
Testing, Launch & Results
We decided not to launch anything until the whole product was complete - seeing half-English, half-Spanish content obviously wouldn't work.
We also ran our favourite pre-launch ritual: a testing party 🎉 Everyone jumps on a call, tries to break things, and makes sure it's good to go.
Then we rolled it out as an A/B test, starting small and gradually ramping up to a 50/50 split over a few days, monitoring metrics as we went.
Once we hit significance, the results were huge: double-figure uplift in exports and revenue across our core language markets (Spanish, French, and a few others).
We kept adding new languages over the following weeks - thanks to the way it was built, adding a new one took about 20 minutes.
What Came Next
Of course, new problems popped up once localisation was live:
- Developers were slowed down needing translations for every PR
- Occasionally someone forgot, leaving bits of English copy on translated pages
- Lokalise was pay-per-seat, and we definitely didn't want to buy 60+ accounts
So I built our second custom tool.
🤖 Auto-Translation PR Bot
- Listened for PRs and copy changes via GitHub & Lokalise webhooks
- Triggered a Cloud Function to request translations from Lokalise
- Used the Lokalise and GitHub APIs to open PRs automatically with the translated copy
No more manual steps - devs didn't need to log into Lokalise or request anything. It just worked.
Developer experience went way up 🚀
It also meant anyone could edit translations directly in Lokalise, and PRs would appear automatically in GitHub. Super simple.
Final Thoughts
This wasn't the hardest project technically, but it was one of the most cross-functional and communication-heavy ones I've led.
Success here was really about ownership, clarity, and relentless communication - and maybe a few too many testing parties.
We only managed to do good-enough, automated translations. To get really good translations you really need to get a human with context of each key, we simply didn't have time for that.