A bilingual site without an i18n plugin
Every page on this site exists in English and French, and I write both versions myself. The question was not how to translate, but how to keep two languages in step without a framework doing it for me.
One document, three tabs
Each post is a single document in the CMS. A Shared tab holds what does not depend on language: date, category, cover image. An English tab and a Français tab hold the title, slug, excerpt and body.

One publish makes both languages live. No post is ever online in one language only, so the language switch on every page always has somewhere to go.
Interface text lives in code
Navigation labels, captions and buttons are a small dictionary in the repository. It is typed, so a missing French key fails the build instead of showing up as an empty button.
export const ui = {
en: { 'nav.blog': 'Blog', 'contact.copy': 'Copy' },
fr: { 'nav.blog': 'Blog', 'contact.copy': 'Copier' },
} as const;
type Key = keyof typeof ui.en;
export function t(lang: 'en' | 'fr', key: Key) {
return ui[lang][key];
}What I would not do again
- Redirect by browser language. People share links; a link should open what was shared.
- Translate slugs later. Decide them with the title, in both languages.
- Let French be the long one by accident. Test every component with text a third longer.
A language switch is a promise that the other page exists. Keep the promise in the data model, not in the template.
Building something and need a designer who can also ship it? Get in touch →