Workflow Published

Bilingual QR menu setup for tourist-heavy restaurants (sibling URLs, hreflang, no auto-redirect)

Detailed setup guide for bilingual or trilingual QR menus — sibling URLs not query strings, hreflang annotations, allergen translation review, RTL layouts, and the auto-redirect mistake to avoid.

Primary intent: bilingual qr menu setup restaurant Sources: S-008,S-016,S-017,S-019,S-022,S-301

If a meaningful slice of your covers don’t speak the local language, a bilingual QR menu is a near-zero-cost upgrade that compounds well. This is the detailed setup post — the tourist-market best-practices checklist covers the high-level shape; here we go into the implementation. New here? Start with the 10-row mobile audit to find which rows your current menu already fails before you add a second language.

TLDR

Put each language at a sibling URL (/menu/en, /menu/es). Add hreflang to each <head>. Do not auto-redirect on browser locale. Have a human translator review the allergen line and the dietary tags. For RTL languages, mirror the layout. For SEO, give each version a distinct title and meta description in the native language.

Why this matters

In our 5-restaurant sample (S-301), the Berkeley menu was bilingual EN/ES but rendered both languages on the same PDF page. The result was a menu that worked for neither audience — too dense in either language, illegible body text after pinch-to-zoom. The Berlin and Lisbon menus were single-language and missed the international audience entirely.

The fix is structural, not stylistic.

Setup, end-to-end

Step 1 — Decide your languages

Look at:

  • Reservation provider (Resy/OpenTable) breakdown of customer first names + email TLDs.
  • Walk-in patterns by season (tourist peaks vs locals).
  • Server reports — “we get a lot of German tourists in summer”.

Most tourist markets justify the local language + English + one regional (e.g., Lisbon: Portuguese, English, Spanish). More than four languages is rarely worth the maintenance cost.

Step 2 — Have a translator do the menu once

For the initial translation:

  • A professional menu translator is $100-300 for a typical menu. Worth it.
  • A bilingual server who works the relevant rooms can do it in 1-2 hours; pay them for the time.
  • Google Translate / DeepL as a base, always reviewed by a human, especially for the allergen line.

Avoid: machine translation as the final step.

Step 3 — Publish at sibling URLs

URL structure:

https://yourdomain.com/menu/en
https://yourdomain.com/menu/es
https://yourdomain.com/menu/pt

Not:

https://yourdomain.com/menu?lang=en   # weaker for SEO
https://yourdomain.com/en/menu        # acceptable, but harder to maintain

Step 4 — Each version has its own metadata

<!-- /menu/en -->
<title>Dinner menu — Your Restaurant (Lisbon, English)</title>
<meta name="description" content="Dinner menu in English. Open Tue-Sun, 18:00-23:00.">
<link rel="canonical" href="https://yourdomain.com/menu/en">

<!-- /menu/pt -->
<title>Ementa de jantar — O Seu Restaurante (Lisboa)</title>
<meta name="description" content="Ementa de jantar em português. Aberto terça a domingo, 18h-23h.">
<link rel="canonical" href="https://yourdomain.com/menu/pt">

Translate the title and description, not just the body.

Step 5 — hreflang annotations

In every version’s <head>:

<link rel="alternate" hreflang="en" href="https://yourdomain.com/menu/en">
<link rel="alternate" hreflang="pt" href="https://yourdomain.com/menu/pt">
<link rel="alternate" hreflang="es" href="https://yourdomain.com/menu/es">
<link rel="alternate" hreflang="x-default" href="https://yourdomain.com/menu/en">

x-default is the fallback for users in regions you don’t have a language for. Pick English unless there’s a reason not to.

Step 6 — The language toggle UI

<nav aria-label="Languages" class="lang-toggle">
  <a href="/menu/en" hreflang="en" lang="en">English</a>
  <a href="/menu/pt" hreflang="pt" lang="pt" aria-current="page">Português</a>
  <a href="/menu/es" hreflang="es" lang="es">Español</a>
</nav>

Style it as a row of pills, font 14px+, padding 8px+, tap target ≥44×44px.

Step 7 — Do not auto-redirect

A common mistake: redirect based on Accept-Language or browser locale. This breaks two things:

  1. The diner sees the wrong language version and can’t easily switch back. (Accept-Language doesn’t always match what they want.)
  2. Google may end up indexing the wrong version because Googlebot’s Accept-Language is en by default.

Just present the toggle. Let the diner pick.

Step 8 — Allergen translation review

This is the row that bites. The allergen labelling post details the specific pitfalls (S-017, S-019). Summary:

  • “Shellfish” → Spanish mariscos colloquially, but EU law (S-019) splits crustáceos (crustaceans) from moluscos (molluscs). Use the law’s terms.
  • “Tree nuts” → list specific nuts, not the umbrella term.
  • “Wheat” vs “gluten” — split them out if your audience includes coeliac/wheat-allergy guests.

Have the translator read the FDA “big 9” list (S-017) or EU 14 list (S-019) and translate each term independently. Don’t translate the whole menu and assume the allergen line is correct.

Step 9 — RTL languages

If you publish in Arabic, Hebrew, Persian, Urdu:

<html lang="ar" dir="rtl">

Mirror the layout:

  • Prices on the left, not the right.
  • Allergen icons to the right of the item, not left.
  • The language toggle wraps the other way.

Test in Safari with the actual language — RTL bugs are easy to miss in LTR development.

Step 10 — Server-side render or static-export each version

Static generation (Astro, 11ty, Hugo, Next.js export) is preferable to client-side language switching because each version gets its own HTML document, its own canonical, and its own SEO signal. Client-side i18n libraries are fine for apps; they’re overkill for a 3-language menu.

Edge cases

Same item name, two languages

Some items don’t translate (e.g., “bucatini cacio e pepe” stays Italian in every language). That’s fine. Translate the description below the item name, not the name itself.

Currency

Always show prices in local currency. Some restaurants in tourist hubs add a second currency in parentheses (22€ / ~$24); this is fine but the conversion rate is a maintenance burden — pick a round monthly rate and update it.

Server availability

If a section of the menu is only available at certain hours (lunch only, weekends only), translate that note. Server-rendered conditional rendering is overkill for this — just put “Lunch only / Apenas almoço” in plain text.

Common mistakes

  1. One PDF with all languages stacked. Fails every audit row.
  2. Query-string language toggle (?lang=en). Weakens SEO; Google may pick a canonical for you and it’ll be wrong.
  3. Auto-detect + redirect. Breaks SEO and frustrates diners.
  4. Translating the menu but not the meta. The page title and description should be in the target language.
  5. Skipping the allergen translation review. This is the highest-stakes line of text on the menu.

FAQ

Do I need separate sitemaps per language?

You can have one sitemap that lists every language version of every page. Make sure each URL is in the sitemap; hreflang handles the relationships.

Should I use a CDN for each language?

Static HTML on Vercel/Netlify already runs on a global CDN. No additional work needed.

How do I handle a third dialect (e.g., Brazilian vs European Portuguese)?

If your audience is Brazilian, use hreflang="pt-BR"; if European, pt-PT. Most tourist markets pick one based on dominant audience.

What about French Canadian for a Montreal restaurant?

hreflang="fr-CA" for the Quebec French version, fr-FR for European French (if you publish both). Most don’t need both.

How do I track which language version converts?

Vercel Analytics or any privacy-respecting analytics shows per-URL pageviews. Filter by /menu/en vs /menu/pt to see which version diners spend more time on.


After setup, run the 10-row audit against each language version separately. If a row fails in one language but not another (usually row 2, body text size, when translated text overflows), fix per-language.