Jak tworzyć wielojęzyczne strony w CMS z automatycznym przełączaniem języka i poprawnym SEO.
Architektura
Wielojęzyczność opiera się na trzech elementach:
- Master page (PL) z locale-keyed fields
- Slave pages (EN, ES, FR…) — puste, dziedziczą content z mastera
- Domeny z ustawionym locale (np. mojafirma.pl=pl, mojafirma.com=en)
Site: mojafirma-marketing (multilang: true)
├── Master: path="cennik", locale=pl
│ fields: { "pl": {"hero": "Cennik"}, "en": {"hero": "Pricing"} }
│ content: <h1>{{ hero }}</h1>...
├── Slave EN: path="pricing", locale=en, based_on=master
│ content: "" (pusty — dziedziczy z mastera)
└── Slave FR: path="tarifs", locale=fr, based_on=master
content: "" (pusty)
Locale-keyed fields
Wszystkie teksty na masterze PL w strukturze per język:
{
"pl": {
"hero_title": "Cennik",
"hero_lead": "Wybierz plan dla siebie"
},
"en": {
"hero_title": "Pricing",
"hero_lead": "Choose your plan"
}
}
CMS automatycznie dobiera odpowiedni język na podstawie domeny.
Slave pages — zasady
Slave to strona per język z trzema polami:
-
path — ścieżka w docelowym języku (np.
pricing,tarifs) -
locale — kod języka (
en,fr,es) - based_on_page_id — ID mastera PL
Content i fields pozostają puste — CMS dziedziczy je z mastera.
Path bez prefixu locale
Slave ma path w docelowym języku, bez prefixu:
-
pricing(nieen/pricing) -
tarifs(niefr/tarifs)
Wyjątek: gdy path koliduje z masterem PL (ten sam tekst w obu językach):
- Master PL:
crm→ Slave EN:en/crm(bocrm=crm) - Master PL:
blog→ Slave EN:blog-en
Homepage
Homepage (path=””) to jeden master z locale-keyed fields, bez slave’ów.
Strony /en, /fr to puste redirect pages (redirect_to: “/”) — fallback gdy ktoś wejdzie ręcznie.
Layout
Layout używa {{ html_lang }} (nie {{ locale }}) do conditionals:
{% if html_lang == "en" %}
<a href="/pricing">Pricing</a>
{% else %}
<a href="/cennik">Cennik</a>
{% endif %}
html_lang jest ustawiany automatycznie z locale domeny.
Zmienne layoutowe dostępne przez {{ layout.nazwa }} (nie {{ nazwa }}).
Blog wielojęzyczny
Tag <cms type="article"> nie filtruje po locale — pokazuje wszystkie artykuły z danej kategorii.
Rozwiązanie: osobny category_code per język + Liquid conditional:
{% if html_lang == "en" %}
<cms type="article" category_code="blog-en" per_page="12" site="current">
<list><!-- karty artykułów --></list>
<show><!-- pełny artykuł --></show>
</cms>
{% else %}
<cms type="article" category_code="blog-pl" per_page="12" site="current">
<list><!-- karty artykułów --></list>
<show><!-- pełny artykuł --></show>
</cms>
{% endif %}
Ważne: każdy branch musi mieć kompletny blok <cms>...<list>...<show>...</cms>. Nie wolno dzielić otwierającego i zamykającego tagu między branche — CMS zwróci błąd 500.
SEO
-
Canonical:
<link rel="canonical" href="{{ url }}">w layoucie -
Hreflang:
{{ seo_head }}w layoucie generuje hreflang automatycznie (wymaga multilang: true na site) - Schema.org: JSON-LD Organization + SoftwareApplication w layoucie
Domeny
Każda domena ma przypisany locale:
| Domena | locale |
|---|---|
| mojafirma.pl | pl |
| mojafirma.com | en |
| mojafirma.es | es |
Ustawienie: Konta → Domeny (Account::Domain z subject_type=Cms::Site).
Locale codes
- Czech: cs (nie cz)
- Ukrainian: uk (nie ua)
- Pozostałe: pl, en, fr, sk, de, es
Checklist
- Master PL z locale-keyed fields (pl, en, es…)
- Content z Liquid variables — bez hardkodowanych tekstów
- Slave per język: path + locale + based_on_page_id, content i fields puste
- Path slave’a bez prefixu (chyba że koliduje z masterem)
- Artykuły blogowe z category_code per język
- Layout z html_lang conditionals
- multilang: true na site
- Domeny z locale