Development & Building with AI · Tutorial

How to build a blog in eight languages

Eight languages are not eight copies of the blog. They are one original text, seven translations with their own status, and one simple rule: no language goes live before it is ready.

Responsible editor:
Daniel Ferreira

Published · 8 min read

Originally published in Brazilian Portuguese.

Translated from the Portuguese original with AI assistance.

Also available in: Português

Key points

  • Choose a source language and treat each translation as a separate record, with its own status and review.
  • Turn languages on and off through configuration: the architecture can support eight, but only reviewed ones should be published.
  • Arabic and Japanese need extra care: text direction with dir="rtl", logical CSS and a search that segments words.

Setting up the routes of a multilingual site is the best-documented part of the problem. The hard part comes later: keeping eight versions of a living blog, with new articles every week, without any page mixing languages or publishing a poor translation.

This tutorial covers that second part: content model, interface strings, review, languages with different scripts and checks before publishing. The URL structure with Astro is covered in how to build an international site with Astro, and language annotations for search engines in internationalization and hreflang in Astro projects.

The examples come from Token & Tech itself, which has eight languages in its architecture. We describe only what is in the project’s code.

Step 1: choose a source language

Before writing any code, decide which language your texts are born in. The others will be translations of it.

This may look like bureaucracy, but it solves three practical problems:

  • Corrections. When a fact changes, you fix the original and mark the translations as outdated, instead of having to figure out which version is right.
  • Review. The reviewer for each language compares against a single reference.
  • Priority. If time runs short, the original goes out first and the translations follow when they are ready.

At Token & Tech, Brazilian Portuguese is the source language and lives at the root of the domain. In src/lib/routes.ts, the prefix function returns an empty path for the default language and /<language> for the others. That is why the Portuguese original of this article lives at /desenvolvimento-e-ia-aplicada/..., while this English translation sits under /en/.

When naming languages, use BCP 47 tags (the format of codes such as pt-BR and en). The W3C’s guidance on choosing a language tag has a golden rule: keep the tag as short as possible and add the region only when it carries necessary information. Brazilian and European Portuguese differ considerably in writing, so pt-BR is justified. For English, en is usually enough.

Step 2: separate the article from its translations

The classic mistake is having one file per article with fields like title_en, title_es, title_fr… With eight languages, that file turns into a spreadsheet nobody can review.

The model that scales separates two things:

  1. The article record, which does not depend on language: type, category, authorship, sources, publication date.
  2. One translation per language, with title, description, slug (the final part of the URL) and body text.

At Token & Tech, this appears in src/content.config.ts. The article lives in src/content/articles/<id>.yaml, and each version in src/content/article-translations/<id>/<language>.md. The translations collection has a status field with four values: draft, translation-pending, translation-review and published. It also records who translated, who reviewed the localized text and whether a machine produced the first draft (machineAssisted).

The advantage is that each language moves at its own pace. An article can be published in Portuguese and English and, at the same time, be under review in German, without the German version appearing on the site.

If you are still deciding where to store this content, the article Markdown, MDX or a headless CMS compares the options. The typed collections mechanism is covered in Astro Content Collections.

Step 3: translate the interface with the same keys in every language

Besides articles, a blog has dozens of fixed strings: menu, footer, buttons, error messages, date labels. These strings live in dictionaries, one per language.

The rule that prevents gaps is to require every dictionary to have the same keys. In TypeScript, this comes almost for free: you declare the shape once and make each dictionary conform to it. If a key is missing in Japanese, the project does not compile.

At Token & Tech, the dictionaries live in src/i18n/ui/, one file per language, all typed by the same UI type. Fixed URL segments (such as the topics or authors page) live in src/i18n/segments.ts. A comment in that file records a useful decision: Latin-script languages get translated segments, while Japanese and Arabic use the English segments, because ASCII addresses are easier to type and share. The content and metadata of those pages are still translated.

Dates and numbers do not go in the dictionary. Use JavaScript’s Intl API. Intl.DateTimeFormat formats dates according to the language: the same date becomes “08/10/2026” in Portuguese and “10/8/2026” in U.S. English. Writing that format by hand in eight languages is asking for mistakes.

Step 4: have a switch for each language

This is the decision that protects a multilingual blog the most: the architecture can support eight languages, but only those that are ready should be generated and published.

In src/i18n/locales.ts, each of Token & Tech’s eight languages (Portuguese, English, Spanish, French, German, Italian, Japanese and Arabic) has an enabled field. As of this writing, only Portuguese and English are turned on. The ENABLED_LOCALES list feeds the router, and disabled languages simply do not generate pages. The file’s comment records the project’s rule: a language only goes live after its institutional pages exist and have been reviewed by a person.

There is a quality reason and a search reason for this. Google’s spam policy treats as scaled content abuse the generation of many pages through “automated transformations”, and lists translation among them. The text refers to content produced without value for the reader, not to all assisted translation, but the message is clear: publishing seven languages of unreviewed machine translation is a risk.

Google’s guide to multilingual sites adds another warning: translating only the boilerplate (menu, footer) and leaving the main content in another language creates a poor experience. The same guide states that Google identifies a page’s language from its visible content, not from the lang attribute.

Step 5: decide what happens when a translation is missing

On an eight-language blog, almost every article will exist in fewer languages than the site has. You need a policy for that.

Astro’s built-in routing offers a fallback mechanism: when a page does not exist in a language, it can redirect to the version in another language (redirect, the default) or display the other language’s content at the requested URL (rewrite).

In our newsroom’s assessment, rewrite deserves caution on a blog: it puts Portuguese text at a URL marked as English, the kind of mixing Google’s guide advises against. The simpler alternative is not to generate the page. If the article has no German version, there is no German URL for it, and the language switcher shows only the versions that actually exist.

That is what Token & Tech does. In src/lib/content.ts, the list of published articles includes only article-translation pairs whose status is published. The router generates one page per pair, and the switcher and hreflang use the same list.

Another decision of the same kind: do not redirect visitors based on their browser language. Google’s guide recommends avoiding automatic redirection between language versions. At Token & Tech, the LanguageSuggest.astro component reads the browser’s preference and only suggests the version in the reader’s language, with the option to stay on the page.

Step 6: prepare for Arabic and Japanese from the start

Of the eight languages, two change the layout and the search.

Arabic is written from right to left, known as RTL. The W3C’s guidance on markup for right-to-left text is direct: declare dir="rtl" on the html element when the whole document runs in that direction, and never use CSS to set the base direction. It also notes that lang does not set direction; the two attributes are independent. At Token & Tech, the src/layouts/Base.astro layout writes lang and dir from the languages table, and only Arabic has dir: "rtl".

The complement is in the CSS. Logical properties describe the layout by the flow of text rather than by fixed sides. Instead of margin-left, you write margin-inline-start, which becomes the left margin in Portuguese and the right margin in Arabic. If your CSS is full of left and right, the Arabic version will require a parallel stylesheet.

Japanese does not separate words with spaces. That affects site search. Pagefind, a search tool for static sites, creates a separate index per language based on the html element’s lang attribute and loads only the index for the page’s language in the browser. For Chinese, Japanese and Korean, it segments the text into words, but the documentation states that these languages have no stemming (reducing words to their root). Token & Tech’s search uses Pagefind, as described in how to add site search to an Astro website.

Step 7: check the output before publishing

With eight languages, reviewing page by page is no longer feasible. Checks need to be automatic and run on the generated site, not on the code.

Token & Tech has a script, scripts/check-dist.mjs, that runs after the build and fails if it finds, among other problems:

  • a page in a disabled language or a link pointing to a disabled language;
  • an <html lang> that differs from the URL’s language;
  • hreflang missing the page itself, missing x-default or lacking reciprocity between versions.

The sitemap (src/pages/sitemap.xml.ts) is generated from the same route list, with the alternate versions of each page. That way, what the reader sees, what the sitemap announces and what the script checks all come from a single source. To scale this kind of check, see how to test internal links and routes automatically and how to structure 5,000 pages on a static site.

Key takeaways

  • A blog in eight languages is one original with seven translations, each with its own status and review.
  • Separate the article record from the translations and require the same keys in every interface dictionary.
  • Have a switch for each language and only turn on what a person has reviewed.
  • When a translation is missing, do not generate the page. Do not mix languages at the same URL or redirect based on the browser.
  • Arabic calls for dir="rtl" and logical CSS; Japanese calls for a search that segments words.
  • Automate checks on the generated site: declared language, links and hreflang.

Disclosure: The Token & Tech website is built with Astro and uses the multilingual architecture described here; the examples cited come from the project's own code.

Sources

  1. Internationalization (i18n) Routing, Astro Docs. Accessed October 8, 2026
  2. Managing multi-regional and multilingual sites, Google Search Central. Accessed October 8, 2026
  3. Spam policies for Google web search, Google Search Central. Accessed October 8, 2026
  4. Intl.DateTimeFormat, MDN Web Docs. Accessed October 8, 2026
  5. CSS logical properties and values, MDN Web Docs. Accessed October 8, 2026
  6. Multilingual search, Pagefind. Accessed October 8, 2026
  7. Structural markup and right-to-left text in HTML, W3C Internationalization. Accessed October 8, 2026
  8. Choosing a language tag, W3C Internationalization. Accessed October 8, 2026

Related reading

Development & Building with AI · Explainer

Astro Content Collections: content at scale

When a site grows from a few pages to hundreds of articles, the problem is no longer writing but keeping everything consistent. See how Astro solves this with typed collections.

Development & Building with AI · Explainer

Astro: why use it for sites with thousands of pages

When a site has thousands of content pages, every architectural decision is multiplied. Based on the official documentation and dated field data, see what Astro does well in this scenario and where its limits are.

Development & Building with AI · Analysis

Astro vs. Next.js: architecture and performance compared

Astro and Next.js both generate HTML on the server, but they start from different premises: one was born for content sites, the other for React applications. With official documentation and dated field data, see what that changes in practice.