Blog

Wiedza o CRM, zarządzaniu firmą i technologii dla biznesu

← Wróć do bloga

CMS wielojęzyczny - hreflang, domeny i routing w praktyce

Michał Stefaniak · 2026-02-24
CMS wielojęzyczny - hreflang, domeny i routing w praktyce

InHelp działa w 6 językach - po polsku, angielsku, francusku, hiszpańsku, czesku i ukraińsku. Każdy język ma swoje strony, menu, CTA, opisy. Sugester ma ponad 500 stron w trzech językach. BitFaktura UA obsługuje ukraiński na osobnej domenie.

Wszystkie te strony siedzą na jednym CMS. Nie na osobnych instalacjach per język. Nie na kopiach stron z ręcznie zmienionymi tekstami. Na jednym systemie z jednym panelem administracyjnym.

Dobranie się do tego stanu zajęło nam sporo czasu. Oto jak działa multilang w naszym CMS i czego się nauczyliśmy.

Problem z kopiowaniem stron

Większość prostych CMS-ów rozwiązuje wielojęzyczność przez kopiowanie. Masz stronę /cennik po polsku? Skopiuj ją, przetłumacz, wrzuć pod /pricing. Gotowe.

Do momentu kiedy masz 50 stron w 6 językach. To 300 stron. Zmieniasz numer telefonu w stopce - musisz otworzyć 300 stron. Dodajesz nowy punkt w menu - 6 razy. Poprawiasz błąd w CTA - 6 razy.

I największy problem: Google nie wie że /cennik i /pricing to ta sama strona. Widzi dwie osobne strony z podobną treścią. Bez tagów hreflang wyszukiwarka nie potrafi połączyć wersji językowych i czasem traktuje je jako duplikaty.

Master i slave - jedna strona, wiele języków

Nasze rozwiązanie: każda strona ma jedną wersję “master” (zazwyczaj polską) i powiązane wersje “slave” per język.

Master to strona z szablonem HTML i danymi w JSON. Zawiera layout (HTML z tagami Liquid) i treści pogrupowane per język w fields:

fields:
  pl:
    hero_title: "Wspólna skrzynka"
    hero_lead: "Zarządzaj mailami w zespole"
  en:
    hero_title: "Shared inbox"
    hero_lead: "Manage emails as a team"
  es:
    hero_title: "Bandeja compartida"
    hero_lead: "Gestiona emails en equipo"

Szablon HTML używa tagów Liquid - , . System wie z jakiego języka pobrać tekst na podstawie domeny lub locale strony.

Slave to powiązana strona z pustym contentem i pustymi fields - dziedziczy wszystko z mastera. Ma tylko swój path (np. /pricing zamiast /cennik) i locale (np. en).

Zmiana numeru telefonu w stopce? Edytujesz mastera - slave’y dziedziczą automatycznie. Dodanie nowego języka? Tworzysz slave z locale i path, reszta się dziedziczy.

Hreflang - jak powiedzieć Google o wersjach językowych

Tagi hreflang w sekcji head mówią wyszukiwarce: “ta strona istnieje też w innych językach, oto linki”. System generuje je automatycznie z powiązań master-slave:

<link rel="alternate" hreflang="pl" href="https://intum.pl/cennik" />
<link rel="alternate" hreflang="en" href="https://intum.com/pricing" />
<link rel="alternate" hreflang="es" href="https://intum.es/precios" />
<link rel="alternate" hreflang="x-default" href="https://intum.pl/cennik" />

x-default wskazuje na wersję domyślną - tę którą Google powinien pokazać użytkownikom w językach których nie obsługujesz.

Nie trzeba tego wklejać ręcznie. Tag w layoucie generuje canonical, hreflang i Open Graph meta tagi automatycznie.

Domeny z locale

Każda domena może mieć przypisany locale. intum.pl serwuje po polsku, intum.com po angielsku. CMS wie jaki język serwować na której domenie.

Kiedy ktoś wchodzi na intum.com/pricing - system szuka strony z path pricing i locale en. Canonical wskazuje na tę domenę. Sitemap zawiera tylko strony w odpowiednim języku.

Można też robić języki pod jedną domeną z prefixem - /ua/funktsii, /es/funciones. Ale osobne domeny dają czystszą strukturę URL i lepszą lokalizację w Google.

Szablon Liquid zamiast duplikacji HTML

Kluczowa różnica od “kopiuj i tłumacz”: HTML jest jeden. Tłumaczenia siedzą w danych (fields JSON), nie w kodzie strony.

Prosty przykład - sekcja hero na stronie głównej:

<h1></h1>
<p></p>
<a href=""></a>

Ten sam HTML obsługuje wszystkie 6 języków. System podstawia odpowiednie teksty z fields na podstawie locale. Zmiana designu to edycja jednego mastera - nie 6 kopii.

Dla elementów które różnią się per język (np. link do rejestracji po polsku vs angielsku) - wartość linku też siedzi w fields.

Nawigacja dynamiczna

Menu w layoucie nie może być zahardkodowane. Link /crm nie działa w trybie podglądu (preview) - prowadzi do panelu zamiast do strony CMS.

Layout buduje menu dynamicznie przez zmienną pages:


  <a href="/regulamin">Regulamin</a>

  <a href="/polityka-prywatnosci">Polityka Prywatności</a>

  <a href="/wspolpraca-partnerska">Współpraca</a>

  <a href="/kalendarz">Intum Calendar</a>

  <a href="/blog">Blog</a>

  <a href="/crm">CRM v2</a>

  <a href="/funkcje">Funkcje</a>

  <a href="/cennik1">Cennik</a>

  <a href="/drive">Drive</a>

  <a href="/formularze">Formularze</a>

  <a href="/webchat">Webchat</a>

  <a href="/voip">VoIP</a>

  <a href="/dokumentacja">Dokumentacja</a>

  <a href="/cms-z-ai">CMS</a>

  <a href="/mail">Mail</a>

  <a href="/zadania">Zarządzanie zadaniami</a>

  <a href="/baza-wiedzy">Baza wiedzy</a>

  <a href="/workinfo">Rejestracja czasu pracy</a>

  <a href="/helpdesk">Helpdesk</a>

  <a href="/o-nas">O nas</a>

  <a href="/kontakt">Kontakt</a>

automatycznie zwraca poprawny URL - z prefixem preview w trybie podglądu, bez prefixu na produkcji.

Strony mają pole menu_code do grupowania - “top” dla menu głównego, “footer” dla stopki, “produkty” dla dropdownu.

Czego się nauczyliśmy

Ścieżki (path) muszą być unikalne per site. Jeśli PL ma /crm i EN też chce /crm - masz kolizję. Albo EN dostaje prefix (en/crm) albo inny path (customer-management). Lepiej od razu planować ścieżki w docelowych językach.

Nie mieszaj sufiksów z locale-keyed fields. Stary wzorzec to - osobna zmienna per język. Nowy wzorzec to - jedna zmienna, system rozwiązuje język z fields. Stary wzorzec nie skaluje się powyżej 2 języków.

Homepage jest specjalny. Path pusty ("") jest jeden per site. Strona główna nie ma slave’a - tłumaczenia obsługujesz przez locale-keyed fields na masterze.

Testuj każdy język po zmianach. Zmiana w masterze propaguje się na slave’y - ale jeśli slave ma własny content (nie pusty), nie dziedziczy zmian. Trzeba wiedzieć co jest dziedziczone a co nadpisane.

Skala

Na naszych stronach mamy teraz:

  • InHelp: 15 masterów + 60 slave’ów = 75 stron w 6 językach
  • Sugester: 128 masterów + 372 slave’ów = 500 stron w 3 językach
  • Paragony.pl: 11 masterów + 22 slave’ów w 3 językach
  • Siteor.net: 18 masterów + 54 slave’ów w 4 językach

Zarządzanie 700 stronami z jednego panelu, z automatycznym hreflang, canonical i dziedziczeniem treści. Bez kopiowania, bez ręcznych tagów SEO, bez synchronizacji tłumaczeń między kopiami stron.

← Wróć do bloga