Перейти к содержимому

Многоязычный контент

Переводы позволяют пакету говорить на вашем языке. Эта страница — о другой половине задачи: как научить пакет понимать язык вашего контента. Лимит заголовка в 60 символов не подходит японскому, обрезка по границе слова ломает тайский, İstanbul и istanbul — одно слово в турецком, hreflang it_IT некорректен, а корейскому сайту важен робот Naver, а не только Google. Это вопросы корректности, а не перевода; они решаются в ядре, чтобы все точки вывода были согласованы.

Значения по умолчанию и переопределения политик находятся в config/seo.php. Для некоторых возможностей нужны зависимости среды выполнения, включая сегментацию слов ICU и установленные шрифты; переведённый контент должно предоставлять приложение.

Локаль контента и локаль интерфейса

Core 3.17, Filament 1.11 и Pro 2.36 передают выбранную локаль контента через метаданные, вычисляемые хуки, URL предпросмотра, ключевые слова чек-листа и запросы ИИ. Англоязычная панель может редактировать итальянский и японский контент, не меняя подписи интерфейса.

php
$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 определяет лимит для переданного текста:

php
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. Строка может задавать только часть ключей, наследуя остальные:

php
'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 учитывает:

php
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 boundaries

lower() предназначен для отображения; 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”:

php
'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_ILhe-IL). Повторяющиеся разделители сохраняются (en__USen--US), чтобы аудит мог их отметить. Выключите, чтобы сохранить переданные байты.
  • include_self добавляет локаль и канонический URL самой страницы, если в списке нет ни её URL, ни её кода. Google требует, чтобы каждая языковая версия указывала себя; включите это, если хук возвращает только другие языки.
  • x_default задаёт язык, чья альтернатива дублируется как x-default, если такой записи в списке нет.

Пустой список остаётся пустым: страница без переводов не получает ни ссылки на себя, ни x-default.

Бесплатный аудит добавляет три проверки списка после применения политики:

КодСерьёзностьЗначение
hreflang_invalid_codewarningКод вне контракта Google (en-UK, jp, english, es-419, fil).
hreflang_duplicate_codenoticeОдин код указан дважды.
hreflang_missing_selfwarningСобственного URL страницы нет в её списке.

Для проверки взаимности — ссылается ли другая страница обратно — требуется обход. Это задача сканирования Pro: необязательный check_hreflang_reciprocity загружает каждую альтернативу через SsrfGuard и выдаёт hreflang_not_reciprocal, если другая страница не объявляет URL исходной с её языковым кодом (Pro 2.38+, см. проблемы сканирования). Помощник доступен публично, если он вам нужен:

php
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:

ВходНормализация приложенияЯзык HTMLGoogle hreflang
pt_BRpt-BRНекорректен в переданном видеНекорректен в переданном виде
de-CH-1901СохраняетсяКорректный зарегистрированный вариантНеподдерживаемый вариант
es-419СохраняетсяКорректный числовой регионНеподдерживаемый числовой регион
zh-Hant-TWСохраняетсяКорректенКорректен
filСохраняетсяКорректный зарегистрированный языкВне двухбуквенного контракта
iw_ILhe-ILПодчёркивание недопустимо; iw-IL остаётся корректным устаревшим тегомИспользуйте нормализованный he-IL
en__USen--USНекорректенНекорректен
x-defaultСохраняетсяОтклоняется политикой языка контента RankbeamКорректный маркер резервной версии
php
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_ITit-IT), а ArticleSchema::fromModel() — из локали сохранённой seo_meta. Узел WebSite берёт языки из конфигурации:

php
'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 и разрешены по умолчанию. Они участвуют в политике и индивидуальных переопределениях, поэтому магазин, не обслуживающий Китай, может не расходовать пропускную способность на двух роботов:

php
'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 ищут его на главной, которая тоже покрыта. Для пустого ключа ничего не выводится:

php
'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 завершится ошибкой.

ЯзыкЛокальЗаголовок / описаниеПодсчёт словСопоставление ключевых словЧитаемость
Английскийen60 / 160По пробеламSnowballFlesch Reading Ease
Итальянскийit60 / 160По пробеламSnowballGulpease
Немецкийde60 / 160По пробеламSnowballWiener Sachtextformel
Французскийfr60 / 160По пробеламSnowballKandel-Moles
Испанскийes60 / 160По пробеламSnowballFernández-Huerta
Португальский (Бразилия)pt_BR60 / 160По пробеламSnowballMartins
Нидерландскийnl60 / 160По пробеламSnowballFlesch-Douma
Турецкийtr60 / 160По пробеламSnowballAteşman
Русскийru60 / 160По пробеламSnowballOborneva
Польскийpl60 / 160По пробеламSnowballPisarek
Японскийja30 / 80Словарь ICUТочное, со сведением регистраЭвристика, без оценки
Китайский (упрощённый)zh_CN30 / 80Словарь ICUТочное, со сведением регистраЭвристика, без оценки
Китайский (традиционный)zh_TW30 / 80Словарь ICUТочное, со сведением регистраЭвристика, без оценки
Корейскийko30 / 80По пробеламТочное, со сведением регистраЭвристика, без оценки
Греческийel60 / 160По пробеламSnowballLIX
Украинскийuk60 / 160По пробеламТочное, со сведением регистраLIX
Чешскийcs60 / 160По пробеламSnowballLIX

Таблица намеренно не скрывает три ограничения:

  • 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 не может отрисовать. См. постраничный чек-лист, проблемы сканирования, ИИ-помощник и отчёты.

rankbeam/laravel-seo распространяется под лицензией MIT.