Skip to content

Локализация и города

Templite поддерживает две независимые фичи:

  • Мультиязычность — переводы контента на разные языки.
  • Мультигородность — варианты страниц под разные города.

Обе фичи выключены по умолчанию и включаются в Настройках ядра (/cms/core-settings):

  • multilang_enabled — включает обработку языков.
  • multicity_enabled — включает обработку городов.

Языки

Модель Language

КолонкаНазначение
codeISO 639-1 код (ru, en, fr)
nameОтображаемое название
is_defaultDefault-флаг (ровно один язык должен быть default)
is_activeАктивен ли язык
orderПорядок в списке

Управление — раздел Языки (/cms/languages) или через MCP (create_language, set_default_language, reorder_languages).

Резолвер языка

Middleware LocaleResolver подключается на публичных маршрутах. Алгоритм:

  1. Если multilang_enabled = false — биндит заглушки (current_language = null, is_default_language = true) и пропускает дальше.
  2. Если первый сегмент URL совпадает с активным языком (НЕ дефолтным) — переключается на этот язык, вырезает префикс из URL для дальнейшей маршрутизации.
  3. Вызывает app()->setLocale($currentLang) — стандартный Laravel-translator начинает работать с этим языком.

После middleware в контейнере доступны:

БиндингСодержимое
current_languageКод текущего языка (string или null)
is_default_languagebool — является ли текущий язык дефолтным
default_languageКод дефолтного языка
languagesEloquent Collection активных Language-моделей

URL-схема языков

  • Дефолтный язык — без префикса: /contacts, /about.
  • Не-дефолтный — с префиксом языка: /en/contacts, /fr/about.

Переводы

СущностьТаблицаСодержит
Страницыpage_translationstitle, bread_title, seo_data, social_data
Блоки на страницеpage_block_translationsЗначения полей блока (data JSON)
Глобальные настройкиglobal_field_value_translationsПереводы значений настроек
Текстовые строки UIСтандартные Laravel lang/<code>.jsonДоступны через __('key') после setLocale

При запросе на не-дефолтном языке переводы накладываются поверх дефолтных значений. Если перевод отсутствует — используется дефолт.

Доступ к переводам страницы в Blade

blade
@php
    $lang = app('current_language');
    $translation = $page->translation($lang);
@endphp
<h1>{{ $translation?->title ?? $page->title }}</h1>

Для большинства случаев CMS сама накладывает переводы — обращаться к translations() напрямую нужно редко.

Стандартные Laravel-переводы

Templite не предоставляет helper-функций типа cms_t(). Используйте стандартный Laravel-translator после того, как LocaleResolver вызвал setLocale:

blade
{{ __('site.contact_us') }}

Где lang/en/site.php содержит:

php
return [
    'contact_us' => 'Contact us',
];

Переключение языка в шаблоне

Готовой helper-функции для генерации URL на другой язык нет. Стройте URL вручную:

blade
@foreach (app('languages') as $lang)
    @if ($lang->code !== app('current_language'))
        <a href="{{ $lang->is_default ? $page->full_url : '/' . $lang->code . $page->full_url }}">
            {{ $lang->name }}
        </a>
    @endif
@endforeach

Города

Модель City

КолонкаНазначение
slugИдентификатор в URL и cookie
nameНазвание города (Москва)
name_genitiveРодительный падеж (Москвы)
name_prepositionalПредложный падеж (в Москве)
name_accusativeВинительный падеж (Москву)
regionРегион / область
phone, address, emailКонтактные данные конкретного города
coordinatesJSON с координатами для карты
extra_dataJSON для произвольных полей
is_default, is_active, sort_orderБазовые флаги

Управление — раздел Города (/cms/cities). Массовый импорт — MCP import_cities (CSV или JSON).

Резолвер города

Middleware CityResolver:

  1. Если multicity_enabled = false — пропускает.
  2. Проверяет первый сегмент URL — если это slug города, использует его и вырезает префикс.
  3. Если в URL города нет — берёт из cookie city_slug (живёт 365 дней).
  4. Если и в cookie нет — City::getDefault().
  5. Биндит current_city в контейнер, шарит как $city в view, ставит cookie.

После middleware:

БиндингСодержимое
current_cityEloquent-модель City (или null, если multicity выключен)
city_stripped_urlURL без city-префикса (для дальнейшей маршрутизации)
city_from_urlbool — пришёл ли город из URL или из cookie

В Blade-шаблоне переменная $city доступна автоматически (через view()->share).

URL-схема городов

  • /contacts — для дефолтного города (берётся из cookie или is_default).
  • /spb/contacts — явный город в URL.

Плейсхолдеры в текстах

Объект City предоставляет плейсхолдеры через $city->getPlaceholders():

ПлейсхолдерСодержимое
{city}Название города
{city_genitive}Родительный падеж
{city_prepositional}Предложный падеж
{city_accusative}Винительный падеж
{city_slug}Слаг города
{phone}Телефон города
{address}Адрес города
{email}Email города

Эти плейсхолдеры автоматически подставляются в текст блоков при рендере виртуальной city-страницы. Например, поле title блока с значением Доставка в {city_prepositional} превратится в Доставка в Москве для города «Москва».

City Pages — виртуальные vs материализованные

Связь между страницей и городом хранится в CityPage:

КолонкаНазначение
source_page_idFK на исходную страницу
is_materializedВиртуальный или материализованный вариант
title_override, bread_title_overrideПереопределения заголовков
seo_data_override, social_data_override, template_data_overrideПереопределения JSON-полей
status_overrideПереопределение статуса

Дополнительно CityPageBlock (city_page_blocks) — переопределения для конкретных блоков на city-странице.

Два режима:

РежимОписание
Виртуальный (is_materialized = false)Нет отдельной записи в pages. Рендерится через PageRenderer::renderVirtualCityPage(): берётся исходник, применяются overrides и плейсхолдеры.
Материализованный (is_materialized = true)Создаётся реальная запись Page с city_scope = materialized и city_id. Можно править как обычную страницу, независимо от исходника.

Несинхронность

Материализованные страницы не синхронизируются с исходником. Изменение в page-source НЕ переносится в материализованные копии. Поддерживайте их вручную или используйте только виртуальные варианты, если контент должен быть синхронным.

Виртуальные city-страницы

При запросе на city-URL (/spb/contacts) PageRenderer::renderVirtualCityPage():

  1. Находит исходную страницу по city_stripped_url.
  2. Находит CityPage для этой страницы и текущего города.
  3. Применяет overrides поверх исходника:
    • title, bread_title, seo/social/template data.
    • На уровне блоков — overrides из city_page_blocks.
  4. Подставляет плейсхолдеры из $city->getPlaceholders() в текстовые поля.

Без CityPage для конкретного города показывается исходник напрямую (с подстановкой плейсхолдеров, если есть).

Материализация страницы

Превратить виртуальный city-вариант в материализованный — MCP-tool materialize_city_page или UI «Города» → выбрать город → выбрать страницу-источник → «Материализовать».

После материализации:

  • В pages появляется новая запись с city_scope = materialized.
  • В city_pages запись становится is_materialized = true.
  • Можно править через обычный UI «Страницы».

Обратное действие — dematerialize_city_page (MCP) удаляет материализованную копию, оставляя виртуальный вариант.


Языки + города одновременно

Оба middleware (LocaleResolver и CityResolver) могут работать вместе. URL вида /en/spb/contacts:

  1. LocaleResolver распознаёт en, вырезает префикс языка → /spb/contacts.
  2. CityResolver распознаёт spb, вырезает префикс города → /contacts.
  3. Дальше CMS ищет страницу по /contacts.

Порядок резолверов определяется порядком регистрации в middleware-группе CMS.


Кэширование

Все механизмы локализации кэшируют список языков и городов:

  • Language::getCachedActive() — двухуровневый кэш (Redis/file + in-memory на запрос). Ключ — cms:languages_active.
  • City::getCachedAll() — аналогично. Ключ — cities:active_models.
  • GlobalFieldsMiddleware кэширует глобальные настройки с привязкой к языку (global_fields_<lang>).

При изменении языков или городов в админке кэш сбрасывается автоматически. Принудительный сброс:

bash
docker exec templite-app php artisan cms:cache-clear

Подводные камни

Подводные камни

  • Фичи выключены по умолчанию. Включаются в Настройки ядра (/cms/core-settings): multilang_enabled, multicity_enabled. Без этого middleware-резолверы возвращают заглушки.
  • Helper'а cms_t() не существует. Используйте стандартный Laravel __('key'). Templite только настраивает setLocale() через LocaleResolver.
  • Helper'а для генерации URL на другой язык нет. Стройте URL вручную с учётом дефолтного языка (без префикса) vs не-дефолтного (с префиксом).
  • Город из URL имеет приоритет над cookie. Это нужно учитывать при переключателе городов: переключение через cookie работает только если в URL города нет.
  • Материализованные city-страницы независимы от исходника. Синхронизации нет — изменение в page-source не переносится в материализованные копии.
  • Плейсхолдеры применяются только для виртуальных city-страниц. Для материализованных — это уже обычные страницы, плейсхолдеры в них вычитываются как литералы.
  • Slug города в URL имеет приоритет над slug страницы. Если у вас есть страница /spb, она будет недоступна — CityResolver перехватит и попытается найти город с этим слагом.

Связанные разделы

Распространяется под лицензией MIT.