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:
- The diner sees the wrong language version and can’t easily switch back. (
Accept-Languagedoesn’t always match what they want.) - Google may end up indexing the wrong version because Googlebot’s
Accept-Languageisenby 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
- One PDF with all languages stacked. Fails every audit row.
- Query-string language toggle (
?lang=en). Weakens SEO; Google may pick a canonical for you and it’ll be wrong. - Auto-detect + redirect. Breaks SEO and frustrates diners.
- Translating the menu but not the meta. The page title and description should be in the target language.
- 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.