Многоязычный контент
Переводы позволяют пакету говорить на вашем языке. Эта страница — о другой половине задачи: как научить пакет понимать язык вашего контента. Лимит заголовка в 60 символов не подходит японскому, обрезка по границе слова ломает тайский, İstanbul и istanbul — одно слово в турецком, hreflang it_IT некорректен, а корейскому сайту важен робот Naver, а не только Google. Это вопросы корректности, а не перевода; они решаются в ядре, чтобы все точки вывода были согласованы.
Значения по умолчанию и переопределения политик находятся в config/seo.php. Для некоторых возможностей нужны зависимости среды выполнения, включая сегментацию слов ICU и установленные шрифты; переведённый контент должно предоставлять приложение.
Локаль контента и локаль интерфейса
Core 3.17, Filament 1.11 и Pro 2.36 передают выбранную локаль контента через метаданные, вычисляемые хуки, URL предпросмотра, ключевые слова чек-листа и запросы ИИ. Англоязычная панель может редактировать итальянский и японский контент, не меняя подписи интерфейса.
$italian = $post->seoData('it');
$japanese = $post->seoData('ja');Эти операции чтения выбирают строку метаданных указанной локали и выполняют хуки модели, такие как getSEOTitle(), getSEODescription(), getUrlForSEO() и getSEOSchema(), во временном контексте локали. Модель вызывающего кода и локаль приложения сохраняются, в том числе при исключении в хуке. Модели, реализующие setLocale() и getTranslatableAttributes() Spatie, также получают изолированную локаль экземпляра. Хуки всё равно должны возвращать переведённый контент: Rankbeam не переводит обычные атрибуты базы данных автоматически.
Pro принимает явный locale: в методах ИИ на основе модели и массовом заполнении. Без него локаль контента задаёт переопределённое значение по умолчанию seoData() модели перевода, с резервным переходом к локали приложения. Действия Filament получают локаль собственного поля, включая одноязычный редактор и режим следования. В собственном задании очереди сериализуйте выбранную локаль и явно передавайте её при выполнении. Не полагайтесь на текущую локаль воркера.
Для собственных синхронных читателей контента ModelLocale::run($model, $locale, $callback) передаёт изолированную модель в callback и восстанавливает локаль приложения в finally. Завершайте все зависящие от локали операции чтения внутри callback: возврат ленивого итератора или замыкания не продлевает действие контекста.
Лимиты заголовка и описания по письменности
Rankbeam использует редакционные лимиты в 60/160 графем для заголовков/описаний на латинице и 30/80 для CJK. Это настраиваемые приближения, а не измерения в пикселях или гарантии полного отображения поисковой системой. Google не задаёт фиксированный лимит символов для заголовков-ссылок и метаописаний; отображаемый текст может сокращаться под ширину устройства.
Rankbeam\Seo\I18n\LengthPolicy определяет лимит для переданного текста:
use Rankbeam\Seo\I18n\LengthPolicy;
$policy = LengthPolicy::for($title, $locale); // detects the dominant script
$policy->script; // 'cjk'
$policy->titleMax; // 30
$policy->descriptionMax; // 80
$policy->length($title); // user-perceived characters (graphemes)
$policy->titleTooLong($title);Эту политику читают предупреждения редактора (SEOWarningEvaluator), бесплатный seo:audit, сокращение вычисляемого описания, сканирование Pro и счётчики Filament, поэтому правила у них едины. Предупреждения проверяют итоговые значения, включая суффикс заголовка, а редактор также может показывать несохранённый текст. Длина считается в графемных кластерах, а не байтах или кодовых точках. Границы кластеров зависят от установленной реализации Unicode; это не счётчик слогов и не измерение пикселей поисковой выдачи.
Строки находятся в seo.length_policy с ключами групп письменности: latin, cyrillic, greek, cjk, thai, arabic, hebrew, devanagari. Для отсутствующих групп используется default. Строка может задавать только часть ключей, наследуя остальные:
'length_policy' => [
'default' => ['title_min' => 30, 'title_max' => 60, 'description_min' => 70, 'description_max' => 160],
'cjk' => ['title_min' => 15, 'title_max' => 30, 'description_min' => 35, 'description_max' => 80],
'thai' => ['title_max' => 55], // everything else from `default`
],По умолчанию отличается только cjk. После обновления установка со старой опубликованной конфигурацией получает встроенную строку cjk без дополнительных изменений.
Смешанные заголовки
Определение основано на взвешенном подсчёте букв: глиф CJK считается за два. Поэтому "Laravel SEO の完全ガイド" относится к CJK, а "Laravel SEO for the 東京 developer" остаётся латиницей. Значение совсем без букв, например год или цена, использует письменность локали страницы.
Константы SEOWarningEvaluator::TITLE_MAX_LENGTH / DESCRIPTION_MAX_LENGTH по-прежнему существуют как значения по умолчанию для латиницы, для кода, который их читает.
Сокращение с учётом письменности без разрыва графем
Вычисляемое описание (seo.computed.description_max_length, лимит для латиницы) масштабируется политикой: описание CJK получает половину лимита. Сокращение выполняет Rankbeam\Seo\I18n\Truncator:
- для текста с пробелами между словами сохраняется прежнее правило: последняя граница слова в пределах лимита, если она находится не раньше 60 % лимита; без многоточия, с удалением конечной пунктуации. Для латиницы результат побайтово совпадает с прежним;
- Han, Kana и тайский не имеют пробелов между словами, поэтому выбирается последний знак конца предложения или части предложения (。!?、,…) в пределах лимита, затем пробел, если он есть (корейский), и только затем жёсткий срез;
- срез проходит по графемным кластерам и не может попасть внутрь комбинируемой последовательности: знак тайской гласной или модификатор эмодзи не отделяется от основы.
Регистр с учётом локали
mb_strtolower() не учитывает локаль. Rankbeam\Seo\I18n\CaseFolder учитывает:
use Rankbeam\Seo\I18n\CaseFolder;
CaseFolder::lower('İSTANBUL', 'tr'); // "istanbul" — dotted İ → i under Turkish rules
CaseFolder::equals('ΟΔΟΣ', 'οδος', 'el'); // true — final sigma folded
CaseFolder::equals('ΟΔΟΣ', 'οδός', 'el'); // false — the accent is preserved
CaseFolder::equals('Straße', 'STRASSE', 'de'); // true — ß folded to ss
CaseFolder::containsWord('Notizie dalla Città', 'città'); // true — Unicode word boundarieslower() предназначен для отображения; fold(), equals(), contains() и containsWord() — для сравнений. Ядро использует этот механизм при пропуске суффикса заголовка с учётом бренда (seo.title_suffix_skip_when_contains): турецкий бренд сопоставляется с обеими формами i, а бренд с диакритикой получает настоящую границу слова. Проверки ключевых слов Pro используют тот же помощник.
Сведение регистра сохраняет диакритику. Оно не делает любое написание с диакритикой эквивалентным написанию без неё. Стеммер конкретного языка может выполнять собственные преобразования; это отдельный механизм, не CaseFolder и не точное сопоставление.
hreflang
Google читает language[-Script][-REGION]: двухбуквенный язык ISO 639-1, необязательную письменность ISO 15924 и необязательный регион ISO 3166-1 alpha-2, а также x-default. Числовые регионы вроде es-419 корректны в BCP47, но не входят в контракт hreflang Google. Приложения Laravel часто передают вместо этого свою локаль (it_IT, pt_br), а подчёркивание здесь недопустимо. Три политики в seo.hreflang применяются к списку getSEOAlternates() модели до его преобразования в теги <link rel="alternate">, записи карты сайта <xhtml:link>, ссылки llms.txt и входные данные аудита. Все используют одну политику; llms.txt исключает саму страницу и x-default из ссылок “Also in”:
'hreflang' => [
'normalize' => true, // it_IT → it-IT, zh_hans_cn → zh-Hans-CN
'include_self' => false, // append the page itself when the list omits it
'x_default' => null, // e.g. 'en': duplicate that language's alternate as x-default
],normalize(по умолчанию включено) преобразует разделители, регистр и зарегистрированные псевдонимы (iw_IL→he-IL). Повторяющиеся разделители сохраняются (en__US→en--US), чтобы аудит мог их отметить. Выключите, чтобы сохранить переданные байты.include_selfдобавляет локаль и канонический URL самой страницы, если в списке нет ни её URL, ни её кода. Google требует, чтобы каждая языковая версия указывала себя; включите это, если хук возвращает только другие языки.x_defaultзадаёт язык, чья альтернатива дублируется какx-default, если такой записи в списке нет.
Пустой список остаётся пустым: страница без переводов не получает ни ссылки на себя, ни x-default.
Бесплатный аудит добавляет три проверки списка после применения политики:
| Код | Серьёзность | Значение |
|---|---|---|
hreflang_invalid_code | warning | Код вне контракта Google (en-UK, jp, english, es-419, fil). |
hreflang_duplicate_code | notice | Один код указан дважды. |
hreflang_missing_self | warning | Собственного URL страницы нет в её списке. |
Для проверки взаимности — ссылается ли другая страница обратно — требуется обход. Это задача сканирования Pro: необязательный check_hreflang_reciprocity загружает каждую альтернативу через SsrfGuard и выдаёт hreflang_not_reciprocal, если другая страница не объявляет URL исходной с её языковым кодом (Pro 2.38+, см. проблемы сканирования). Помощник доступен публично, если он вам нужен:
use Rankbeam\Seo\I18n\Hreflang;
Hreflang::fromLocale(app()->getLocale()); // 'it_IT' → 'it-IT'
Hreflang::isValid('pt-BR'); // true
Hreflang::isValid('en-UK'); // falseТри контракта языковых кодов
Core 3.18+ отделяет настройку приложения от значения, отдаваемого в HTML:
| Вход | Нормализация приложения | Язык HTML | Google hreflang |
|---|---|---|---|
pt_BR | pt-BR | Некорректен в переданном виде | Некорректен в переданном виде |
de-CH-1901 | Сохраняется | Корректный зарегистрированный вариант | Неподдерживаемый вариант |
es-419 | Сохраняется | Корректный числовой регион | Неподдерживаемый числовой регион |
zh-Hant-TW | Сохраняется | Корректен | Корректен |
fil | Сохраняется | Корректный зарегистрированный язык | Вне двухбуквенного контракта |
iw_IL | he-IL | Подчёркивание недопустимо; iw-IL остаётся корректным устаревшим тегом | Используйте нормализованный he-IL |
en__US | en--US | Некорректен | Некорректен |
x-default | Сохраняется | Отклоняется политикой языка контента Rankbeam | Корректный маркер резервной версии |
use Rankbeam\Seo\I18n\LanguageTag;
LanguageTag::isValidHtml('de-CH-1901'); // true
LanguageTag::isValidHtml('en_US'); // false: inspect the served value
LanguageTag::isValidHtml(''); // true: HTML defines this as unknown
LanguageTag::isValid('x-default'); // true: generic BCP47 private use
LanguageTag::isValidHtml('x-default'); // false: Rankbeam content-language policy
Hreflang::isValid('es-419'); // false: Google compatibility
Hreflang::isValid(Hreflang::fromLocale('pt_BR')); // true: application boundaryМиграция с ядра 3.17 и более ранних версий: Hreflang::isValid() и parse() строго проверяют коды в отдаваемом виде. Если вызывающий код передаёт локаль Laravel, сначала вызовите fromLocale(). При проверке HTML-атрибута lang используйте LanguageTag::isValidHtml() без обрезки пробелов и нормализации. Устаревшие зарегистрированные теги остаются корректными для HTML; нормализация применяет только явные предпочтительные псевдонимы IANA и не предполагает, что en-UK означает en-GB. Некорректные записи не отфильтровываются до того, как аудит сможет о них сообщить.
Валидатор включает сведения реестра IANA от 2026-08-08, хеши источников и воспроизводимый генератор. Он проверяет структуру RFC 5646, зарегистрированные подтеги, префиксы extlang и повторы вариантов/расширений. Поддерживаются исторически признанные теги и диапазоны частного использования. Рекомендации префиксов вариантов не являются обязательными правилами валидности; проверяются пространства имён и структура расширений, но смысл параметров CLDR и частного использования остаётся вне API. ICU и загрузки во время работы не требуются. См. RFC 5646 и определение языка HTML.
Pro 2.38+ сообщает об отсутствующем или пустом lang как о неизвестном/отсутствующем языке, а некорректные переданные байты дают html_lang_invalid. Проверки несоответствия письменности используют фактический подтаг письменности или зарегистрированное значение IANA по умолчанию; данные частного использования/расширений и незнакомые языки не означают латиницу. Неподдерживаемые группы письменностей не оцениваются. Это не полноценный детектор языка.
Для взаимности используются корректные коды ссылки исходной страницы на себя, а при их отсутствии — её корректный HTML-язык, совместимый с Google. Обратный URL с другим языковым кодом не проходит проверку. Если код исходной страницы определить невозможно, результат остаётся hreflang_target_unverified. Повторяющиеся целевые URL загружаются один раз в пределах существующих ограничений альтернатив и тела ответа; защита от SSRF, отказ от перенаправлений и обработка неподтверждённых сбоев сохраняются.
inLanguage в графе разметки
Узел WebPage получает inLanguage из итоговой локали страницы (it_IT → it-IT), а ArticleSchema::fromModel() — из локали сохранённой seo_meta. Узел WebSite берёт языки из конфигурации:
'schema' => [
'in_language' => true, // off = no inLanguage anywhere
'website' => ['inLanguage' => ['it', 'en']], // one code or a list
],Региональные поисковые системы
Каталог роботов для seo:robots-txt теперь содержит классических поисковых роботов, важных за пределами Google и Bing: Yandex, Baidu, Naver (Yeti), Seznam, Sogou, 360, Cốc Cốc и DuckDuckGo. Они помечены назначением search_engine и разрешены по умолчанию. Они участвуют в политике и индивидуальных переопределениях, поэтому магазин, не обслуживающий Китай, может не расходовать пропускную способность на двух роботов:
'ai_crawlers' => [
'policy' => ['search_engine' => 'allow', /* … */],
'overrides' => ['baiduspider' => 'disallow', 'sogou' => 'disallow'],
],SEO::aiCrawlers()->all() и match() по-прежнему охватывают только ИИ: журнал ИИ-ботов Pro и все счётчики «N ИИ-роботов» не меняются. Запрашивайте поисковые системы через searchEngines(), all(true) или match($ua, true). См. Управление ИИ-роботами.
Baidu
Поддержка робота и тега подтверждения не гарантирует обнаружение, индексацию или позиции в Baidu.
Подтверждение сайта
Токены владения выводятся на каждой странице как один метатег на настроенную поисковую систему. Google принимает тег на любой странице; Yandex, Baidu и Naver ищут его на главной, которая тоже покрыта. Для пустого ключа ничего не выводится:
'verification' => [
'google' => env('SEO_VERIFY_GOOGLE'), // google-site-verification
'bing' => env('SEO_VERIFY_BING'), // msvalidate.01
'yandex' => env('SEO_VERIFY_YANDEX'), // yandex-verification
'baidu' => env('SEO_VERIFY_BAIDU'), // baidu-site-verification
'naver' => env('SEO_VERIFY_NAVER'), // naver-site-verification
'seznam' => env('SEO_VERIFY_SEZNAM'), // seznam-wmt
'pinterest' => env('SEO_VERIFY_PINTEREST'), // p:domain_verify
'facebook' => env('SEO_VERIFY_FACEBOOK'), // facebook-domain-verification
],Значение может быть списком токенов: Google выдаёт отдельный токен каждому владельцу ресурса.
OG-изображения для любой письменности
Встроенный шрифт карточек покрывает латиницу, кириллицу и греческий. Остальным письменностям нужен шрифт на машине, где выполняется seo:og-images. Другие шрифты не включены, поскольку шрифт CJK занимает от 16 МБ. Шаблоны содержат цепочку резервных шрифтов для каждой письменности (seo.og_image.font_stack, с семейством Noto CJK языка страницы в начале, чтобы иероглифы Han использовали нужные национальные формы). Команда один раз на письменность предупреждает, если на хосте нет шрифта для заголовка, который предстоит отрисовать:
No installed font covers cjk text — its cards may render as boxes. Install one: apt-get install fonts-noto-cjkДля Debian/Ubuntu: apt-get install fonts-noto-cjk fonts-noto-core fonts-noto-color-emoji. Подробнее — Генерация OG-изображений.
llms.txt на нескольких языках
При включённом seo.llms_txt.alternates пункт страницы с версиями на других языках заканчивается Also in: [it](…), [de](…). Это альтернативы после применения политики, за исключением x-default и самой страницы. По умолчанию выключено.
Unicode URL
Rankbeam не создаёт slug и не переписывает ваши URL, поэтому пути вроде /città/ или /検索 сохраняются без изменений во всех результатах. Аудит принимает канонические URL с IDN-хостом (https://münchen.example/), Unicode-путём или путём с процентным кодированием: Rankbeam\Seo\I18n\Url::isValid() заменяет FILTER_VALIDATE_URL PHP, допускающий только ASCII. Используйте одну форму каждого URL — исходный Unicode или процентное кодирование, а не оба варианта, — чтобы канонический URL, hreflang и записи карты сайта совпадали побайтово.
Какие языки поддерживаются и что это означает
Пакеты включают строки и выбор средств анализа для семнадцати локалей ниже. Таблица описывает техническое покрытие, а не редакционное одобрение носителем языка и не гарантию отображения на ненастроенном хосте. Анализ слов японского и китайского требует работоспособного ICU; при его отсутствии соответствующие проверки по словам пропускаются. Для нелатинского текста нужны подходящие шрифты. Выбор средств анализа закреплён тестами обоих репозиториев: tests/Feature/I18n/SupportedLanguagesTest.php ядра фиксирует список локалей, коды hreflang и лимиты, а tests/Feature/OnPage/LanguageSupportMatrixTest.php Pro — движки анализа. Если строка таблицы перестанет соответствовать реализации, CI завершится ошибкой.
| Язык | Локаль | Заголовок / описание | Подсчёт слов | Сопоставление ключевых слов | Читаемость |
|---|---|---|---|---|---|
| Английский | en | 60 / 160 | По пробелам | Snowball | Flesch Reading Ease |
| Итальянский | it | 60 / 160 | По пробелам | Snowball | Gulpease |
| Немецкий | de | 60 / 160 | По пробелам | Snowball | Wiener Sachtextformel |
| Французский | fr | 60 / 160 | По пробелам | Snowball | Kandel-Moles |
| Испанский | es | 60 / 160 | По пробелам | Snowball | Fernández-Huerta |
| Португальский (Бразилия) | pt_BR | 60 / 160 | По пробелам | Snowball | Martins |
| Нидерландский | nl | 60 / 160 | По пробелам | Snowball | Flesch-Douma |
| Турецкий | tr | 60 / 160 | По пробелам | Snowball | Ateşman |
| Русский | ru | 60 / 160 | По пробелам | Snowball | Oborneva |
| Польский | pl | 60 / 160 | По пробелам | Snowball | Pisarek |
| Японский | ja | 30 / 80 | Словарь ICU | Точное, со сведением регистра | Эвристика, без оценки |
| Китайский (упрощённый) | zh_CN | 30 / 80 | Словарь ICU | Точное, со сведением регистра | Эвристика, без оценки |
| Китайский (традиционный) | zh_TW | 30 / 80 | Словарь ICU | Точное, со сведением регистра | Эвристика, без оценки |
| Корейский | ko | 30 / 80 | По пробелам | Точное, со сведением регистра | Эвристика, без оценки |
| Греческий | el | 60 / 160 | По пробелам | Snowball | LIX |
| Украинский | uk | 60 / 160 | По пробелам | Точное, со сведением регистра | LIX |
| Чешский | cs | 60 / 160 | По пробелам | Snowball | LIX |
Таблица намеренно не скрывает три ограничения:
- Snowball включён начиная с Pro 2.37. Двенадцать языков используют закреплённые алгоритмы 3.1.1 независимо от необязательных пакетов. Украинский и CJK используют точное сопоставление без стемминга; пакет не выдумывает для них суффиксные правила. Точное сопоставление может пропускать словоизменённые формы, а стемминг — объединять разные слова. См. настройки движков и примечания по миграции.
- «Эвристика, без оценки» и «LIX» — разные вещи. Для японского, китайского и корейского пакет использует метод без числовой оценки: чек-лист сообщает уровень на основе длины предложений и доли кандзи, с оценкой
null, и остаётся рекомендательным при любой конфигурации. Греческий, украинский и чешский используют LIX, поскольку отдельная формула здесь не реализована. LIX не требует подсчёта слогов, но её пороги не откалиброваны для каждого языка. Все формулы используют приближённые входные данные; см. контракт статистики. - Переводы являются первыми версиями, если
TRANSLATING.mdне сообщает о проверке носителем языка. Итальянский проверен; остальным нужен рецензент, и такая проверка — самый доступный способ внести вклад в свой язык и получить упоминание в пакете.
Для отсутствующих в списке локалей могут использоваться английские строки, лимиты письменности или общие лимиты, точное сопоставление ключевых слов и LIX либо эвристическая читаемость. Такой резервный режим не является проверенной языковой поддержкой. Блок analysis чек-листа показывает письменность, сегментатор, стеммер и метод читаемости; проверяйте их доступность и пропущенные оценки, а не только подписи.
Выход к значимым местным поисковым системам
Выпуск языка — не только текст. В каталоге рядом с Google и Bing есть Yandex, Baidu, Yeti от Naver, Seznam, Sogou, 360 и Cốc Cốc, а seo.verification выводит их теги подтверждения сайта: Naver для корейского сайта, Seznam для чешского, Yandex для украинского или русского. См. Региональные поисковые системы и Подтверждение сайта.
Что добавляют другие пакеты
- laravel-seo-filament читает ту же политику длины для живых счётчиков и предпросмотра поисковой выдачи, а с 1.9 редактирует одну строку
seo_metaна язык: вкладка каждой локали со своими счётчиками, предпросмотром и индикаторами резервных значений либо следование переключателю локали плагина переводов. - laravel-seo-pro читает её для проверок сканирования
title_length/description_lengthи промптов ИИ-помощника, а с 2.34 анализирует страницу на её языке: сегментация слов ICU для китайского, японского и тайского, стемминг Snowball, сопоставление ключевых слов с учётом локали через этотCaseFolder, опубликованные формулы читаемости с приближёнными входными данными для десяти языков, явно обозначенные эвристики для CJK и LIX для греческого, украинского и чешского, стоп-слова для шестнадцати языков, проверкиhtml langи взаимности hreflang, промпты ИИ с языком страницы и отчёт через Chrome для письменностей, которые dompdf не может отрисовать. См. постраничный чек-лист, проблемы сканирования, ИИ-помощник и отчёты.