This post is for Day 8 of Merpay & Mercoin Advent Calendar 2025.
For more than a decade, Mercari’s Marketplace has been a Japanese service – sometimes to the annoyance of our many, many employees who don’t speak Japanese. But as of late November, we’ve finally shipped English UI support for our main flea market app – across iOS, Android, and web. Huzzah! I’m fenomas, a tech lead for our website, and today I’d like to share a look behind the scenes at how it worked, why it took so long, and what comes next. (There’s also a good bilingual pun near the end.)
Getting from 1 to 2 (locales)
Normally, the biggest and hairiest part of an i18n project is when you replace all your hard-coded strings with keyed resource lookups. That is, you make changes like this:
- <h1>My Website!</h1>
+ <h1>{ t('project.main.headline') }</h1>
…for every string, on every page, in your app or website.
In our case, there’s a twist—four or five years ago we did a “ground-up” rebuild of our apps and website, and the brilliant engineers who worked on that project (it was before I joined) had the foresight to build for i18n from day one! They used standard libraries and patterns, like i18next for web and go-i18n for backend, and our engineers have avoided hard-coded UI strings ever since.
At the source code level, we’ve been prepared to support English for several years. Why did it take so long? Looking back, there was no single reason – teams got shuffled around, and priorities changed. But one key event sticks out (for me, at least) – early on, we added a way for internal users to flip their locale over and use the English UI for their own accounts. We did this for testing and to gather feedback, but it also meant that our non-Japanese-speaking employees could switch their UI to English for their day-to-day work. And that’s a dangerous pattern – once your internal stakeholders stop feeling a pain point, it can feel less urgent and wind up being deferred in favor of other features.
So let this be a cautionary tale: it’s often necessary to add internal flags and overrides, but beware getting so used to them that you neglect to ship the feature to actual users!
Getting from localized strings to releasable strings (with AI)
Since our codebases have supported i18n for years, most of our UI strings already had English translations in the source. Until now, we had no particular process for localizing, because English support has been an unreleased internal feature. Some teams got their strings professionally translated, others used AI, and the EN strings were often written by whoever was available at the time, even if they weren’t a native speaker.
So we needed to review and fix all our English strings. At the technical level, this wasn’t such a large task – our marketplace app has around 15,000 strings, spread among several repositories. Reviewing 15K string translations would have been daunting just a few years ago, but here in the age of AI we took the following approach:
- We exported all our string resources from source code into a TMS (translation management system) called Phrase Strings.
- Using the TMS’s export features, we grouped all strings into unique (EN, JA) pairs.
- We submitted these (in batches of 100) to an LLM, with a prompt to look for mistranslations or terms that are likely to need business or legal review.
- Tip: we found that simply asking the LLM for a list of errors didn’t work well in practice – asking it to give each pair a rating like “low/medium/high” gave better results.
- We also ran simple (non-AI) scripts to flag all the strings that included the names of branded Mercari services, so we could make sure they were translated consistently.
This way, we narrowed our task down to around 1,000 strings that needed fixing or new translations. Since many of them included legal and marketing terms, we eschewed AI from this point on, and our excellent internal (human) translators took over.
The non-technical side of making strings releasable
If you take on a project like this, you may find that the technical challenge of localizing all your strings is small compared to the business challenge of getting lots of different teams to agree that their feature’s localization is definitely okay to release publicly.
We planned for this early. We expected that many other teams would have concerns about how their feature was localized, but they might not have time or resources to fully review the localizations my team wanted to release. Allaying such concerns is one of the big reasons why we decided to do a thorough AI review of all our strings, rather than just dogfooding the strings we already had and asking other teams to review the result.
The other big business concern with new localizations was QA testing. There’s a whole category of i18n-related bugs that teams rarely encounter until they support their second locale—in our case, the most common one was truncated UI strings. This can happen anywhere that the localized version of a button is longer than the source language – and our source language is Japanese, which is of course much denser than English.
But beyond small-picture bugs, like string lengths or handling plurals correctly, the big picture is that doing QA for a multi-language app has all kinds of unique challenges. Do you run all your existing tests in the new locale? Do you need new tooling in order to emulate clients with different locales? If you take on an i18n project like this, make sure your QA team has plenty of time to plan.
Other technical wrinkles
Each platform we worked on had its own little side-quests. For web, the biggest of these was routing and redirection – we decided to store the user’s preferred locale on the backend, and redirect them when they visit a route for another locale. This way, if an EN user clicks a social media link to a JA route, we redirect them back to an EN route – and vice-versa. But this means that our routing code has the potential for a redirect loop, which is something that should make every web developer think twice. Once you mix in complications like feature flags and toggles for internal dev tools, it takes a lot of care and testing to make sure users in production can never wind up in a redirection loop.
For iOS, our biggest complication was that iOS treats app locale as a system-level setting. This means that once you include resources for a new locale in your app, users with their system set to that locale may suddenly see their UI changed, without the app having a chance to ask if they wanted to switch. This isn’t really a technical challenge, but it means that when you plan to release a new locale, your UI flow will likely need to treat iOS as a special case.
Meanwhile for large services like ours, i18n isn’t just a frontend issue—a lot of localizable strings are stored in the backend as well. This is naturally true of things like error messages and notifications, but in some cases we also use server-driven UI—which means many strings that look like static UI can actually live on the backend. Since we heavily use microservices, we found our backend strings were spread out across quite a few repos—most of which supported i18n, but not all, and not all used the same i18n libraries.
Getting over the finish line
For us, the final critical step was extensive dogfooding. We did this early and often – and pro tip: getting catered snacks helps attract testers. (But not as much as when our QA engineer Alexander prepared a bunch of Android and iOS phones that already had English enabled, so dogfooding users could get started immediately.)
Dogfooding turned up a lot of fun issues. My personal favorite was that in our database of category strings, we had “Chino Pants” translated as “Chino Bread”. (If you speak Japanese this makes sense – チノパン, right?)
The other notable issue we discovered late was with how we handle mailing addresses. By default an English UI encourages users to enter their address in their display language, but some of our external logistics partners require mailing addresses to be in Japanese. For a fully global service this would typically be handled differently, but in our case we can assume that Mercari Japan users already know how to input their address in Japanese, so we just needed to make sure the UI clearly explained what inputs were required.
Then the final step for a huge i18n project is that you have to draw the line somewhere, and release some localized features while you work on the rest. Mercari has lots of services, lots of websites, and lots of dynamic features, and if we waited until everything was perfect we’d likely never release anything. So for our Phase 1, we’ve released English support for static UI only, in our main Japan marketplace apps and web, even though some related services aren’t localized yet. And moving fast this way is best for our users, after all—if you can’t read Japanese, partial localization is more useful than no localization at all.
What’s next
Our biggest next step is to translate dynamic content, like the titles and descriptions of listed items, and most crucially user comments. Getting this right won’t just be a technical problem—the process of buying and selling items on Mercari is often quite a social one, with users messaging back and forth about the state of the item, asking about discounts, and so on. The nature of a Japan-based service is that local users are likely to feel apprehensive at the thought of receiving comments in English, but if we can provide a great UX with AI-driven translations, we believe we can enable cross-language buying and selling, without changing the positive vibe we strive for in our marketplace. Look for a Phase 2 release early next year!
Wrapping up
If you set out to support i18n for a large service, I hope this article gives you some ideas of what to expect—your biggest challenges are likely to involve quality and cross‑team alignment, more than code changes and PR reviews. Manage the scope, plan for several cycles of dogfooding, and beware the pitfall of making it too easy for internal users to flip on the feature before it’s been released to end users.
And if you’re somebody who uses our apps or website in English, I hope my team’s effort made your experience a little better!
Tomorrow’s article will be by @seitau. Look forward to it!




