# Multi-language site

A site that's available in two or more languages, with URLs like
`/en/about` and `/es/sobre-nosotros`. Visitors land on their language,
can switch from a header dropdown, and search engines see distinct
indexable URLs per language.

## Important: how slugs work in uat-beam.page

**Slugs cannot contain slashes.** Page slugs must be lowercase
alphanumeric and hyphens only. So `en/about` is NOT a valid slug.

Two patterns work:

### Pattern A: flat slugs with a language prefix

The recommended approach. Each page has a flat slug like `en-about`,
`es-acerca`. The URLs become `https://<slug>.uat-beam.page/en-about`
— close enough to `/en/about` in spirit and fully supported.

### Pattern B: one page per content, language in metadata

Single page per topic (e.g. `about`), with both languages stored in
the same metadata object. The page reads `?lang=en` or `?lang=es`
from the URL and renders the right one. Fewer pages, but URLs don't
distinguish languages.

This file shows **Pattern A**, which gives you proper SEO-friendly
multilingual URLs.

## The script (Pattern A)

```python
import requests
import time

API = "https://api.uat-beam.page"
TOKEN = "<your-token>"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

slug = "carmen-tapas"

# 1. Create the project
requests.post(f"{API}/projects", headers=H, json={
    "slug": slug,
    "context": "A tapas bar in Barcelona. Bilingual site (English + Spanish).",
})
time.sleep(2)

# 2. Brand it
requests.put(f"{API}/projects/{slug}/assets/tailwind-config.js", headers=H, json={
    "content": (
        'tailwind.config = { theme: { extend: { '
        'colors: { brand: "#7c2d12", cream: "#fdf8f3" }, '
        'fontFamily: { serif: ["Playfair Display", "serif"] } '
        '} } }'
    ),
})

requests.put(f"{API}/projects/{slug}/assets/styles.css", headers=H, json={
    "content": "@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&display=swap');",
})

# 3. Create pages for each language. Each page has a language prefix
# in its slug. Each carries a `lang` field in metadata so the site
# knows which language tree it belongs to.
pages = [
    # English
    {"slug": "en-about",   "lang": "en", "title": "About us",   "alt": "es-acerca",
     "body": "Carmen has been serving traditional Spanish tapas in Poble Sec since 2009. Family-run, no fuss, good wine."},
    {"slug": "en-menu",    "lang": "en", "title": "Menu",       "alt": "es-carta",
     "body": "Our menu changes with the seasons. Today we're serving gazpacho, paella, and crema catalana."},
    {"slug": "en-contact", "lang": "en", "title": "Find us",    "alt": "es-contacto",
     "body": "Carrer de Blai 42, Poble Sec. Open Tuesday to Sunday from 12:00."},

    # Spanish
    {"slug": "es-acerca",   "lang": "es", "title": "Sobre nosotros", "alt": "en-about",
     "body": "Carmen lleva sirviendo tapas tradicionales españolas en Poble Sec desde 2009. Familiar, sin tonterías, buen vino."},
    {"slug": "es-carta",    "lang": "es", "title": "Carta",          "alt": "en-menu",
     "body": "Nuestra carta cambia con las estaciones. Hoy servimos gazpacho, paella y crema catalana."},
    {"slug": "es-contacto", "lang": "es", "title": "Visítanos",      "alt": "en-contact",
     "body": "Carrer de Blai 42, Poble Sec. Abierto de martes a domingo desde las 12:00."},
]

for p in pages:
    requests.post(f"{API}/projects/{slug}/pages", headers=H, json={
        "slug": p["slug"],
        "notes": p["title"],
    })
    time.sleep(0.3)

    # The metadata records the language and a link to the equivalent
    # page in the other language. The language switcher uses `alt`
    # to send visitors to the matching page.
    requests.put(
        f"{API}/projects/{slug}/pages/{p['slug']}/metadata",
        headers=H,
        json={
            "lang": p["lang"],
            "title": p["title"],
            "body": p["body"],
            "alt": p["alt"],
        },
    )
    time.sleep(0.3)

# 4. The shared layout — header with logo, language switcher, and a
# nav that adapts to the current language. We define it once as a
# JS snippet that every page loads.
layout_js = """
window.beamLayout = {
  render(currentLang) {
    const t = {
      en: { menu: 'Menu', about: 'About', contact: 'Find us', switch: 'ES' },
      es: { menu: 'Carta', about: 'Sobre nosotros', contact: 'Visítanos', switch: 'EN' },
    };
    const slugs = {
      en: { home: '/en-about', menu: '/en-menu', about: '/en-about', contact: '/en-contact' },
      es: { home: '/es-acerca', menu: '/es-carta', about: '/es-acerca', contact: '/es-contacto' },
    };
    const labels = t[currentLang];
    const links = slugs[currentLang];

    document.body.insertAdjacentHTML('afterbegin', `
      <header class="bg-brand text-cream">
        <div class="max-w-4xl mx-auto px-6 py-4 flex justify-between items-center">
          <a href="${links.home}" class="font-serif text-2xl font-bold">Carmen</a>
          <nav class="flex gap-6 text-sm uppercase tracking-wider items-center">
            <a href="${links.menu}" class="hover:text-amber-200">${labels.menu}</a>
            <a href="${links.about}" class="hover:text-amber-200">${labels.about}</a>
            <a href="${links.contact}" class="hover:text-amber-200">${labels.contact}</a>
            <span class="text-amber-200">|</span>
            <a id="lang-switch" class="hover:text-amber-200 font-bold">${labels.switch}</a>
          </nav>
        </div>
      </header>
    `);

    // Wire the language switcher: navigate to the alt page from the
    // current page's metadata
    document.getElementById('lang-switch').addEventListener('click', () => {
      fetch('/cross_page_metadata.json').then(r => r.json()).then(data => {
        const slug = window.location.pathname.replace(/^\\/|\\/$/g, '');
        const page = data.pages.find(p => p.slug === slug);
        if (page && page.metadata && page.metadata.alt) {
          window.location = '/' + page.metadata.alt;
        }
      });
    });
  }
};
"""

requests.put(
    f"{API}/projects/{slug}/assets/layout.js",
    headers=H,
    json={"content": layout_js},
)

# 5. Page template — reads its own metadata and calls render() with
# the current language
def page_html(lang, page_slug):
    return f"""<!DOCTYPE html>
<html lang="{lang}">
<head>
  <base href="/{page_slug}/">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Carmen</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="/assets/tailwind-config.js"></script>
  <link rel="stylesheet" href="/assets/styles.css">
  <script src="/assets/layout.js"></script>
  <script>document.addEventListener('DOMContentLoaded', () => window.beamLayout.render('{lang}'));</script>
</head>
<body class="font-serif bg-cream text-stone-900"
      x-data="{{ p: null }}"
      x-init="
        const slug = window.location.pathname.replace(/^\\/|\\/$/g, '');
        fetch('/cross_page_metadata.json').then(r => r.json())
          .then(d => p = d.pages.find(pg => pg.slug === slug))
      ">

  <main class="max-w-2xl mx-auto px-6 py-16">
    <template x-if="p && p.metadata">
      <article>
        <h1 class="text-5xl font-bold text-brand mb-6" x-text="p.metadata.title"></h1>
        <p class="text-lg text-stone-700 leading-relaxed" x-text="p.metadata.body"></p>
      </article>
    </template>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js" defer></script>
</body>
</html>
"""

# Upload the right template per language
for p in pages:
    requests.put(
        f"{API}/projects/{slug}/pages/{p['slug']}/files/index.html",
        headers=H,
        json={"content": page_html(p["lang"], p["slug"])},
    )
    time.sleep(0.2)

# 6. The root page detects the visitor's browser language and
# redirects to the right language home
root_html = """<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Carmen</title>
  <script>
    const lang = (navigator.language || 'en').slice(0, 2);
    const target = lang === 'es' ? '/es-acerca' : '/en-about';
    window.location.replace(target);
  </script>
</head>
<body>
  <p>Redirecting...</p>
</body>
</html>
"""

requests.put(
    f"{API}/projects/{slug}/pages/_root/files/index.html",
    headers=H,
    json={"content": root_html},
)

print(f"Done. Live at https://{slug}.uat-beam.page")
print(f"  English: https://{slug}.uat-beam.page/en-about")
print(f"  Spanish: https://{slug}.uat-beam.page/es-acerca")
```

## How it hangs together

- **Slug naming convention.** `<lang>-<topic>` for every page.
  English about: `en-about`. Spanish about: `es-acerca` (translated
  topic too, so the URLs are natural in both languages).
- **`metadata.alt`** on each page points to its equivalent in the
  other language. The language switcher reads this and redirects.
- **Shared layout JS** renders the nav and translates the labels
  based on the current language. One file, all pages benefit.
- **Root page redirect** sends visitors to the right language based
  on `navigator.language`. They never see the bare root.
- **`<html lang="...">`** is set per page so screen readers and
  search engines treat each language correctly.

## SEO bonus: hreflang

For real bilingual SEO, add `<link rel="alternate" hreflang>` tags so
search engines know about the equivalent pages. Update the page
template to include them dynamically:

```html
<head>
  <!-- ... existing tags ... -->
  <link rel="alternate" hreflang="en" :href="'https://carmen-tapas.uat-beam.page/' + (lang === 'en' ? slug : altSlug)">
  <link rel="alternate" hreflang="es" :href="'https://carmen-tapas.uat-beam.page/' + (lang === 'es' ? slug : altSlug)">
</head>
```

You'd compute `altSlug` from `metadata.alt` in the Alpine state.

## Pattern B (alternative): one page per topic, language in metadata

If the URLs `/en/about` vs `/es/sobre-nosotros` aren't critical and
you'd rather have fewer pages, store both languages in the same
metadata:

```python
requests.put(f"{API}/projects/{slug}/pages/about/metadata", headers=H, json={
    "title": {"en": "About us", "es": "Sobre nosotros"},
    "body": {"en": "Carmen has been...", "es": "Carmen lleva..."},
})
```

Then in the HTML, read `?lang=` from the URL and render the matching
field:

```html
<body x-data="{
  lang: new URLSearchParams(location.search).get('lang') || 'en',
  p: null
}"
x-init="fetch('/cross_page_metadata.json').then(r => r.json()).then(d => p = d.pages.find(pg => pg.slug === 'about'))">
  <h1 x-text="p && p.metadata.title[lang]"></h1>
  <p  x-text="p && p.metadata.body[lang]"></p>
</body>
```

Half the pages, but URLs don't distinguish languages, which is worse
for SEO and bookmarking. Use Pattern A unless the user has a
specific reason to prefer the simpler structure.
