CodeOX Logo
CodeOX Logo
Vol. I — No. 1
Featured Article
Oct 8, 2026

Designing a Multilingual Web Platform from Database to Browser

Reported By

Muhammad Shifan

Designing a Multilingual Web Platform from Database to Browser
Figure 1. Designing a Multilingual Web Platform from Database to Browser · Original Photography for The Chronicle

For a business serving customers in several regions, a second language on the website is often a core requirement. A visitor who lands on a page that is half translated, or whose text is trapped in a layout built for another reading direction, notices immediately. That visitor tends to trust the business less as a result.

The common mistake is treating translation as a final step: build the site in one language, then “add the others”. By then the database schema, the URLs and the components all assume a single language, and each assumption has to be unpicked. This article explains how to design for several languages from the start across the layers of a MERN-style application, and what to check before promising a multilingual build to a client.

Key points

  • Store each translatable field as an object holding every version, rather than keeping a separate document per version.
  • Put the language in the URL so every page has a stable, indexable address.
  • Some scripts need a mirrored layout, not just translated text. Logical CSS properties make that manageable.
  • Decide what happens when a translation is missing before launch, not after.

Why “translate later” gets expensive

A single-language site makes many quiet assumptions. A title is a string. A button sits on the right because that is where “next” goes. A URL carries no language. Adding another language breaks all three.

Consider a company publishing a service page. Marketing writes the first version of the copy and the translation a week later. If the schema has one title field, the team either duplicates the document or squeezes every version into one string. Duplicating means editing in two places forever. Squeezing means the front end has to guess which text is which. Both approaches get worse with every page added.

Layer 1: The data model

In MongoDB with Mongoose, the cleanest approach is to make each translatable field an object that holds every version. The keys below are placeholders; a real project would use standard locale codes.

// Reusable sub-schema
const translated = {
  primary:   { type: String, default: "" },
  secondary: { type: String, default: "" },
};

const ServiceSchema = new Schema({
  title: translated,
  summary: translated,
  slug: { type: String, required: true, unique: true },
  isPublished: { type: Boolean, default: false },
});

A document then holds the title once, with one entry per language. This has three advantages:

  • One record per page. Prices, images, publish status and ordering are stored once.
  • A predictable shape. The front end always asks for title[locale].
  • Easy to extend. Adding another language means adding a key. It does not mean migrating collections.

Keep the language-neutral fields (slug, images, flags) outside the translated objects. Where a field cannot be a plain string, such as rich text or arrays of cards, decide early whether the whole array is translated or each item is. Nested content is where most of the bugs appear.

Layer 2: The admin panel

The model only works if editors can fill it in without effort. In a React admin CMS, show the inputs for each language side by side or as tabs, and set the correct text direction on each input so editors see what readers will see. A completeness indicator (“6 of 8 fields translated”) saves a lot of back-and-forth, because it shows what is unfinished before the page goes live.

Layer 3: Routing in Next.js

Keep the language in the URL. The Next.js documentation describes this for the App Router. Nest your routes under app/[lang], and use a proxy file (called middleware in Next.js 15 and earlier) to redirect visitors to a locale-prefixed path. In the layout, set both attributes on the root element:

export default async function RootLayout({ children, params }) {
  const { lang } = await params;

  return (
    <html lang={lang} dir={isRtl(lang) ? "rtl" : "ltr"}>
      <body>{children}</body>
    </html>
  );
}

URLs matter for search as well. Each language version should be its own address, and the pages should point to each other with hreflang alternates, so search engines show the right version to the right user. In Next.js you can declare this in generateMetadata using alternates.languages. Include an x-default entry for visitors whose language you don’t recognise.

One caution: if your site also has regional prefixes, build links through a single helper function. Hand-writing paths in components is how users end up on the wrong language after clicking a link.

Layer 4: Layout direction

Some scripts are read right to left. Setting dir="rtl" mirrors text flow, but your CSS can still undo it. Hard-coded margin-left, padding-right and text-align: left stay put when the page flips.

Use CSS logical properties instead:

Physical (breaks when mirrored)Logical (flips automatically)
margin-leftmargin-inline-start
padding-rightpadding-inline-end
text-align: lefttext-align: start
left: 0inset-inline-start: 0

Some things need manual attention anyway:

  • Directional icons. Arrows and chevrons must be flipped. Logos, phone numbers and clocks must not be.
  • Mixed content. Brand names, numbers and email addresses inside text that runs the other way can reorder punctuation unexpectedly. Test with real content, not placeholder text.
  • Fonts. Many typefaces lack glyphs for other scripts, so the browser falls back to a system font. Choose typefaces deliberately and check their weights.
  • Drawers, sliders and carousels. Anything that animates from a side needs to animate from the correct side.

Practical considerations

Decide the fallback rule. If a translation is empty, should the page show the default language, hide the item, or return a 404? Showing the default silently is friendly to users but can leave a translated URL full of untranslated text that search engines index. Whatever you choose, apply it in one shared helper so every component behaves the same way.

Don’t machine-translate and ship. A machine draft is a reasonable starting point. A fluent reviewer should still check anything a customer will read, especially legal text and calls to action.

Budget for testing in every direction. Every new component has more than one layout. Reviewing only the default version is how regressions reach production.

Check the image content. Banners with text baked into the image can’t be translated. Either keep text out of images or store a separate image per language.

A checklist before you start

  • Agree the list of supported languages and the default.
  • Choose one data shape for translated fields and use it everywhere.
  • Put the language in the URL and add hreflang alternates.
  • Write layout styles with logical properties from the first component.
  • Define the missing-translation rule and who owns translation review.

Conclusion

A multilingual site is a set of design decisions spread across the database, the admin panel, the router and the stylesheet. Each is small and cheap when made at the start of a project, and expensive to retrofit. For clients whose customers read more than one language, the investment is repaid in trust and in search visibility. Before you promise it, agree the fallback rule, the translation workflow and the testing scope.

Sources and further reading
  • Internationalization. Next.js documentation, App Router guides. nextjs.org/docs/app/guides/internationalization
  • Mongoose documentation: Schemas and Schema Types. mongoosejs.com
  • CSS Logical Properties and Values. MDN Web Docs. developer.mozilla.org
  • Managing multi-regional and multilingual sites (hreflang). Google Search Central. developers.google.com/search
Designing a Multilingual Web Platform from Database to Browser