How to build a bilingual marketing site the client can actually run
Interface strings and client content are two different problems, and conflating them is why the French half of a site ends up two services behind the English. Here is the split we build to, and why the front end is server-rendered.
A bilingual marketing site is two problems, not one: the interface text your components render, and the content your client writes. Keep them apart. Ship interface text as translation records loaded into every page render, make language a property of each content record so a service can exist in French before its English version is ready, and let the client edit both from one back office. Then server-render the result, so each language arrives as HTML instead of as a promise a crawler has to run JavaScript to collect.
Bilingual sites fail in a quiet, specific way. Launch day is fine — somebody translated every page once, both versions look right, everyone signs off. Six months later the English site lists four services and the French one lists three, because adding the French copy meant editing a file in a repository nobody on the client's side can reach. Nothing is broken. The site is just half true, in the language half the buyers read.
This is how we build them so that does not happen: what belongs in files, what belongs in the database, why the front end is server-rendered even though it is React, and what has to be editable before we consider the thing delivered. The current reference build is CM Metrology, an industrial metrology and calibration company in Tangier whose site and CMS we designed, built and have hosted since November 2025.
01What does "bilingual" actually mean in a marketing site?
It means two categories of text that share nothing except being words on a page.
The first is interface text: "Learn more", "Send message", "All services", the label above a form field, the error underneath it. There are a few hundred of them. They change rarely, they are written by whoever builds the site, and every one of them exists in both languages from the first commit or the build is wrong.
The second is content: service descriptions, product entries, the about page, the testimonial someone added last week. There are as many as the business needs. They change constantly, they are written by people who have never opened a code editor, and a new one is routinely created in one language days or weeks before the other version arrives.
Every bilingual site that decays does so because someone treated those as one problem. Push content into translation files and the client cannot write. Push interface text into the database and you have built a translation admin nobody asked for, with a few hundred rows that will never change again.
02Should the second language live in files or in the database?
Both, and the split is not a compromise — the two kinds of text have opposite properties in every column that matters.
| Interface text | Content | |
|---|---|---|
| Who writes it | The people building the site | The client |
| How often it changes | Almost never after launch | Weekly, unpredictably |
| How many of them | A fixed few hundred | However many the business has |
| Where it belongs | Translation records, loaded per request | The content record itself |
| What "missing" means | A bug — the raw key renders on the page | An editorial state — that version is not ready |
| Who finds the gap first | Your tests, if you wrote them | A visitor, in the language you neglected |
Read the last two rows together, because they are the design. A missing interface string is a defect and should be impossible to ship: keys are enumerable, so a test can walk every one in every locale and fail the build on a gap. A missing translation of a service is not a defect at all — it is Tuesday. The client wrote the French first because the customer who asked was French. The system's job is to render what exists and stay quiet about what does not, rather than to display an English page with a French heading bolted on top.
That is why language belongs to the record. Not a second table shadowing the first, not a JSON blob that only the developer knows the shape of — a field on the thing itself, editable in the same form, so the person writing the French is looking at the English while they do it.
03Why server-render the front end at all?
Because everything that reads a site on a buyer's behalf reads HTML, and most of it does not run your JavaScript.
Search crawlers are the obvious case, and Google's renderer is the most capable of them — which is exactly why people over-generalise from it. The AI answer engines that increasingly stand between a buyer and a supplier are far less patient: they fetch, they parse, and what is not in the markup is not in the answer. Neither is the preview card that gets generated when somebody pastes the link into LinkedIn or WhatsApp, which for a B2B industrial supplier is a meaningful share of how the site is first seen.
So the stack is React on the front, Laravel behind it, and Inertia's server-side rendering in between. The page arrives as HTML with the service copy, the product entries and the metadata already in it, in whichever language was requested, before a single script executes. The client-side application then takes over for navigation.
The second reason is the one users feel. A maintenance engineer opening your site on a phone from an industrial estate is not on your office fibre. Server-rendering means they read while the JavaScript is still arriving, rather than watching a spinner decide whether their signal is good enough.
04What belongs in the CMS, and what does not?
Our test: if a developer would be embarrassed to bill for the change, it belongs in the CMS. That single rule settles almost every argument, and it settles them in the client's favour.
| What the client controls | Why it is not code |
|---|---|
| Services — copy, image, order, published or not | The service list is the business, and the business changes |
| Products — description, specification, price, currency, featured flag | A price change must never be a deployment |
| Client logos and testimonials, each with its own position | Social proof arrives on the customer's schedule, not the roadmap's |
| Hero slides and their calls to action | The campaign changes faster than the codebase should |
| Contact details and social links, in one place | A phone number hard-coded into a footer is a phone number that will be wrong somewhere |
| Per-page title and meta description | Whoever owns the search results owns the fields that produce them |
What stays in code is everything with a failure mode: the layout, the routing, the form handling, the schema, the caching, the deploy. Clients do not want that responsibility and should not be sold it. The line is not "simple things go in the CMS" — it is "reversible things go in the CMS". A wrong price is fixed in ten seconds by the person who noticed. A wrong template is an outage.
05How do you stop a client breaking their own site?
By making the destructive moves unavailable rather than by writing a manual nobody reads. Four mechanisms do most of the work.
- Publish state on every record. Draft and published, not a delete key. Half-written content simply is not rendered, so nobody has to hold a page hostage while they finish the French.
- Explicit ordering. An integer the editor controls, not "most recently updated" — otherwise fixing a typo silently promotes a service to the top of the list.
- Layouts that survive missing data. No testimonials yet means the section is absent, not a bordered box with nothing in it. Every section a client can empty has to be designed empty first.
- Images handled on upload. Resized, converted, dimensions recorded, alt text required. Otherwise the first 8 MB photograph straight off a phone quietly undoes the performance work.
None of that is clever. All of it is the difference between a site the client uses and a site the client is afraid of — and a client who is afraid of their CMS stops updating, which is the same outcome as not having one.
06What does the contact form owe the business?
More than an email. An enquiry that exists only as a message in an inbox is an enquiry that gets lost the first time a mail relay has a bad afternoon, or the first time the person who owns that inbox is on holiday.
So the submission is stored before anything else happens, and the notification is sent afterwards. If the mail fails, the lead is still there. Beyond that: validate on the server whatever you validated in the browser, keep the honest signals a business needs — name, company, phone, subject — and resist bots without making a customer prove they are human by identifying traffic lights. For an industrial supplier, one lost enquiry can outweigh the entire cost of the site.
07How this shipped for CM Metrology
CM Metrology runs 3D measurement and inspection, 3D digitisation and reverse engineering, calibration, and the training around them, from a laboratory in Tangier serving automotive, aerospace, rail and manufacturing customers. People who work to a tolerance are exacting about software, which is the best kind of client to build for.
What they got is the architecture above: a Laravel back end, an Inertia and React front end rendered on the server, English and French throughout, and a back office where their team controls services, products, hero slides, client logos, testimonials, contact details and the interface strings themselves. We designed it, built it, and have hosted and maintained it since November 2025 — which matters, because the handover is where most agency sites die, and there is no handover when the team that wrote the code also runs the servers.
The pattern generalises past marketing sites. It is the same discipline as the custom web applications we build for other clients, and the reason Ledgerline lets an administrator reconfigure an approval chain without a release. Software you cannot change is software you have already started replacing. If your site needs a developer to change a price, or your second language is two services behind your first, tell us what you are running now and we will tell you which of it is a rebuild and which is an afternoon.
Working on deliverability or a product like this?
This is the kind of problem we solve as infrastructure, on the client stacks we run. Tell us what you are building.