We added Japanese and Brazilian Portuguese to our whole website for €31

On 29 September we shipped globalize.now in Japanese and Brazilian Portuguese. Every page, every pricing table and all 61 blog posts. The translation itself took eight minutes, and the app billed about €31. globalize.now is AI-powered localization infrastructure, and this is what running it on our own site looked like, with every number taken from the job page and the Git history rather than from memory.

Our post on what the localization growth playbook cost in 2017 ends by saying we do not publish our own numbers yet. This is the first one. It is a cost number, not a growth number: nobody has had time to visit /ja/ yet.

What exactly did we translate?

The whole site, twice. Here is the inventory, counted from the repository:

  • Interface: 1,782 English strings across five message catalogues (the core site, pricing, comparison pages, integrations and translation examples), about 21,000 words.
  • Blog: 61 posts, about 110,000 words.
  • Target languages: Japanese (ja) and Brazilian Portuguese (pt-BR).

That is roughly 130,000 source words going into two languages. The pull request touched 170 files and added 34,681 lines. After the merge the sitemap lists 811 URLs across 11 locales, and the site builds 962 static pages.

How long did it take?

Eight minutes of translation, then a longer quality pass. Arturs, our CTO, added the two languages to the project and the job started on main at 08:56 UTC. The job page breaks it down:

StageDuration
Translation (11,264 new strings, 5,632 per language)8 min 1 s
Automated QA review27 min 11 s
Delivery to a pull request5 min 29 s

The commit with both languages landed at 09:29 UTC. The other 45,106 strings in the job came from translation memory, because the eight existing languages had not changed.

Arturs guessed ten minutes when he mentioned it in our team chat. He was right about the translation and optimistic about the whole job, and the gap is the QA pass. We would rather spend 27 minutes of machine time checking than skip it.

What did the automated QA find?

72 flags out of 11,264 new strings, all rated minor. 11,192 passed clean, and QA applied 182 automatic fixes along the way.

The flags we opened were mostly the length check being cautious about Japanese. "Pricing" becomes 料金, two characters, and a length ratio of 0.29 falls just under the 0.3 floor. That is a correct translation tripping a heuristic built for alphabetic languages, not a mistake. We are leaving the flags visible anyway: a check that never fires is not a check.

What did it cost?

About €31: the project's cost-by-language view shows €15.72 for Japanese and €16.15 for Brazilian Portuguese. That is what the app charges, the same as a customer translating the same volume would see. It is not our internal model cost and it is not a discount.

Across roughly 260,000 translated words, that is about 12 cents per 1,000. Nothing else was billed on top. There is no per-language charge and no per-seat charge, so adding the twelfth language costs the same as adding the third. The current plans are on the pricing page.

What did the engineering actually involve?

One pull request, merged the same afternoon, and none of it was translation. When it opened, the translation job ran again on it and took 56,370 of 56,390 strings straight from translation memory, so the already-translated catalogues arrived in the new files in seven minutes. Translation is the part that got automated. Registering a new locale in a Next.js site is still your code, and it is worth knowing what "your code" means before you plan it.

Adding a locale to next-intl is one line in the routing config. Everything that builds a URL, reads a locale or lists your locales is the rest of the diff:

  • Routing: the locale list, plus a URL prefix override for pt-BR (next section).
  • SEO metadata: hreflang clusters, canonicals, og:locale (ja_JP, pt_BR) and the locale list the metadata helpers loop over.
  • Formatting: Intl tags ja-JP and pt-BR, so dates and numbers render correctly.
  • Language switcher: it links to every locale with a real <a href hreflang>, so crawlers can follow it.
  • Sitemap and checks: the sitemap generator, the lastmod check, the internal link check and the translation verifier all had to learn the two new codes.
  • Catalogues: empty header-only .po files for the two locales, filled in on the same pull request from translation memory.

If your site is less custom than ours, most of that list is config. If you hand-build URLs anywhere, and most sites do somewhere, you will find each place the first time a link comes out wrong. The developer guide covers the setup, and what an AI localization agent actually does covers the split between what is automated and what is not.

Why is Brazilian Portuguese at /pt-br/ and not /pt-BR/?

Because a language tag and a URL segment are different things with different jobs.

The correct tag is pt-BR, per BCP 47. We use it everywhere a machine reads the language: file names, <html lang>, hreflang, og:locale and Intl. Only the URL is lowercased:

// i18n/routing.ts (trimmed)
const prefixes = {
  "pt-BR": "/pt-br",
} as const;

export const routing = defineRouting({
  locales: ["en", "it", "de", "fr", "es", "lv", "hi", "lt", "et", "ja", "pt-BR"],
  defaultLocale: "en",
  localePrefix: { mode: "always", prefixes },
});

/** The URL path segment for a locale: `pt-BR` → `pt-br`, `de` → `de`. */
export function localeUrlSegment(locale: string): string {
  const prefix = (prefixes as Record<string, string | undefined>)[locale];
  return prefix ? prefix.slice(1) : locale;
}

Mixed-case paths get mistyped, and a site that answers on both /pt-BR/ and /pt-br/ has two copies of every page competing with each other. So /pt-BR/ redirects to /pt-br/, and localeUrlSegment() builds every hand-made URL from the prefix map. One source of truth means the language switcher, the canonicals and the sitemap cannot drift apart.

Why Japanese and Brazilian Portuguese?

They are big web languages we did not cover. By W3Techs' count on the day we shipped, Japanese is the content language of 4.9% of websites and Portuguese of 4.1%.

The case for doing it at all is older than we are. CSA Research surveyed 8,709 consumers in 29 countries in 2020, and 76% said they prefer buying products with information in their own language. That is the demand side. What changed is the supply side: two languages used to be a vendor relationship, and now they cost €31.

Our own rule for picking languages is the one in the growth playbook post. Look at where your traffic already comes from, add two languages, change nothing else and measure for a quarter. We will do exactly that with these two.

What have we not checked yet?

Native review. Nobody who reads Japanese or Brazilian Portuguese natively has gone through the output yet.

What we have checked: automated QA on every string, the build passes every prebuild check, and the pages we spot-checked on staging return 200 with the right lang, canonical and hreflang and render in full. What a build cannot check is whether a sentence sounds like a person wrote it. We learned that the hard way when we audited our own German and French and found the wrong register across whole pages.

So the review comes next, and we will update this post with what it finds, including anything that was wrong.

Could you do the same on your site?

If your strings already live in catalogues, yes, and the translation part will take minutes. If they are still hardcoded in components, conversion comes first. That runs once, in the in-app connect flow, and push jobs translate new strings from then on. The vibe coder guide covers apps built with Lovable, Bolt or Cursor, where hardcoded strings are the norm.

The cost scales with the words you translate, not with the languages or the people on the team. That is why the answer to "is it worth adding a language?" changed. The measurement is now cheaper than the meeting about it.

Try it on your own repository

Two languages cost us eight minutes of translation and about €31. Connect your repository, pick two languages your analytics already point at, and see what your own receipt looks like.

globalize.now turns hardcoded app copy into translation-ready locale files and keeps them updated as you ship.

Try globalize.now free