Локализация и города
Templite поддерживает две независимые фичи:
- Мультиязычность — переводы контента на разные языки.
- Мультигородность — варианты страниц под разные города.
Обе фичи выключены по умолчанию и включаются в Настройках ядра (/cms/core-settings):
multilang_enabled— включает обработку языков.multicity_enabled— включает обработку городов.
Языки
Модель Language
| Колонка | Назначение |
|---|---|
code | ISO 639-1 код (ru, en, fr) |
name | Отображаемое название |
is_default | Default-флаг (ровно один язык должен быть default) |
is_active | Активен ли язык |
order | Порядок в списке |
Управление — раздел Языки (/cms/languages) или через MCP (create_language, set_default_language, reorder_languages).
Резолвер языка
Middleware LocaleResolver подключается на публичных маршрутах. Алгоритм:
- Если
multilang_enabled = false— биндит заглушки (current_language = null,is_default_language = true) и пропускает дальше. - Если первый сегмент URL совпадает с активным языком (НЕ дефолтным) — переключается на этот язык, вырезает префикс из URL для дальнейшей маршрутизации.
- Вызывает
app()->setLocale($currentLang)— стандартный Laravel-translator начинает работать с этим языком.
После middleware в контейнере доступны:
| Биндинг | Содержимое |
|---|---|
current_language | Код текущего языка (string или null) |
is_default_language | bool — является ли текущий язык дефолтным |
default_language | Код дефолтного языка |
languages | Eloquent Collection активных Language-моделей |
URL-схема языков
- Дефолтный язык — без префикса:
/contacts,/about. - Не-дефолтный — с префиксом языка:
/en/contacts,/fr/about.
Переводы
| Сущность | Таблица | Содержит |
|---|---|---|
| Страницы | page_translations | title, bread_title, seo_data, social_data |
| Блоки на странице | page_block_translations | Значения полей блока (data JSON) |
| Глобальные настройки | global_field_value_translations | Переводы значений настроек |
| Текстовые строки UI | Стандартные Laravel lang/<code>.json | Доступны через __('key') после setLocale |
При запросе на не-дефолтном языке переводы накладываются поверх дефолтных значений. Если перевод отсутствует — используется дефолт.
Доступ к переводам страницы в 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:
{{ __('site.contact_us') }}Где lang/en/site.php содержит:
return [
'contact_us' => 'Contact us',
];Переключение языка в шаблоне
Готовой helper-функции для генерации URL на другой язык нет. Стройте URL вручную:
@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 | Контактные данные конкретного города |
coordinates | JSON с координатами для карты |
extra_data | JSON для произвольных полей |
is_default, is_active, sort_order | Базовые флаги |
Управление — раздел Города (/cms/cities). Массовый импорт — MCP import_cities (CSV или JSON).
Резолвер города
Middleware CityResolver:
- Если
multicity_enabled = false— пропускает. - Проверяет первый сегмент URL — если это slug города, использует его и вырезает префикс.
- Если в URL города нет — берёт из cookie
city_slug(живёт 365 дней). - Если и в cookie нет —
City::getDefault(). - Биндит
current_cityв контейнер, шарит как$cityв view, ставит cookie.
После middleware:
| Биндинг | Содержимое |
|---|---|
current_city | Eloquent-модель City (или null, если multicity выключен) |
city_stripped_url | URL без city-префикса (для дальнейшей маршрутизации) |
city_from_url | bool — пришёл ли город из 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_id | FK на исходную страницу |
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():
- Находит исходную страницу по
city_stripped_url. - Находит
CityPageдля этой страницы и текущего города. - Применяет overrides поверх исходника:
- title, bread_title, seo/social/template data.
- На уровне блоков — overrides из
city_page_blocks.
- Подставляет плейсхолдеры из
$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:
LocaleResolverраспознаётen, вырезает префикс языка →/spb/contacts.CityResolverраспознаётspb, вырезает префикс города →/contacts.- Дальше CMS ищет страницу по
/contacts.
Порядок резолверов определяется порядком регистрации в middleware-группе CMS.
Кэширование
Все механизмы локализации кэшируют список языков и городов:
Language::getCachedActive()— двухуровневый кэш (Redis/file + in-memory на запрос). Ключ —cms:languages_active.City::getCachedAll()— аналогично. Ключ —cities:active_models.GlobalFieldsMiddlewareкэширует глобальные настройки с привязкой к языку (global_fields_<lang>).
При изменении языков или городов в админке кэш сбрасывается автоматически. Принудительный сброс:
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перехватит и попытается найти город с этим слагом.
Связанные разделы
- Страницы —
city_scope,template_data, переводы страниц - Блоки —
page_block_translations,city_page_blocksoverrides - Глобальные настройки — переводы значений настроек
- Artisan и безопасность —
cms:cache-clear - MCP — Tools —
create_language,import_cities,materialize_city_page, и т.п.