I’ve had the peterbabic.sk domain sitting unused for years. Meanwhile this blog has been English-only. Recently I figured why not use both? The Slovak version could help with local SEO and make my content accessible to people who prefer reading in Slovak.

The challenge was doing this without duplicating the entire codebase.

The goal #

I wanted:

  1. One repository, one codebase
  2. Two Cloudflare Pages deployments (.com and .sk)
  3. Same URLs, just different content language
  4. Shared layouts, components, styles
  5. Machine-translated content with a notice banner

The last point is important. I’m not going to manually translate 250+ posts. But I want readers to know it’s machine-translated and link them to the original.

Content structure #

My first instinct was separate content/en/ and content/sk/ directories. But then slugs could get out of sync. Typo in Slovak folder name? Now you have mismatched URLs.

Instead I went with both language files in the same folder:

src/content/blog/
├── some-post/
│   ├── en.md
│   └── sk.md
├── another-post/
│   ├── en.md
│   └── sk.md

The folder name IS the slug. Can’t mess it up.

The content config picks up both with a glob pattern:

loader: glob({ pattern: "**/{en,sk}.md", base: "./src/content/blog" })

Locale selection #

The language is selected at build time via environment variable:

// astro.config.mjs
const locale = process.env.LOCALE || "en"
const site =
  locale === "sk" ? "https://peterbabic.sk" : "https://peterbabic.com"

Then a helper function filters posts by locale:

export function getLocale(): Locale {
  const locale = import.meta.env.LOCALE
  if (locale === "sk") return "sk"
  return "en"
}

When loading posts, I group them by slug folder and pick the right language file. If Slovak doesn’t exist, it falls back to English and emits a build warning.

UI strings #

Hardcoded text like “Published”, “About”, “Home” needed translation too. Simple object with both languages:

const ui = {
  en: {
    "nav.home": "Home",
    "post.published": "Published",
  },
  sk: {
    "nav.home": "Domov",
    "post.published": "Publikované",
  },
}

export function t(key: keyof typeof ui.en): string {
  return ui[getLocale()][key]
}

Components use t("nav.home") instead of hardcoded strings.

Translation notice #

Since the Slovak content is machine-translated, I added a banner linking to the original English version. It follows the same feature flag pattern I use for comments and newsletter:

const featureEnabled =
  runtimeEnv?.FEATURE_TRANSLATION_NOTICE === "true" ||
  import.meta.env.FEATURE_TRANSLATION_NOTICE === "true"
const showNotice = featureEnabled && locale === "sk"

The notice only shows on the Slovak site when the flag is enabled.

Cloudflare Pages setup #

Both deployments point to the same repo with different environment variables, but peterbabic.com does not need any as it follows the default setup and variables have fallbacks set up in code. For peterbabic.sk I have two environment variables:

  • LOCALE=sk
  • FEATURE_TRANSLATION_NOTICE=true

This ensures that both deployment show different, but proper output.

The translation itself #

I used Claude to translate all 250 posts in parallel batches. The translations preserve frontmatter structure, keep code blocks untouched, and maintain the same URLs. Tags stay in English since they’re used for filtering.

The whole translation took maybe an hour of wall time with parallel agents.

What I like about this approach #

  • No code duplication - fix a bug once, both sites get it
  • Atomic deploys - push to master, both sites update
  • Impossible to have mismatched slugs - folder name is the source of truth
  • Graceful degradation - missing translation falls back to English

What could be better #

I’m not doing any automatic language detection or switching. If someone lands on the wrong domain, they have to manually go to the other one. For now that’s fine. Maybe later I’ll add a language switcher in the header.

Also the sitemap only includes pages that exist in that locale. Not sure if that’s ideal for SEO but it seems correct.

Conclusion #

If you have an unused domain in your native language, this approach might work for you. Single codebase, build-time locale selection, no runtime overhead. The LOCALE environment variable is the only difference between the two deployments.

Enjoy!