コンテンツへ移動

多言語コンテンツ

翻訳は、パッケージを自分の言語で使うためのものです。このページでは、もう一方の側面、パッケージがコンテンツの言語を理解する仕組みを扱います。日本語にタイトル60文字の上限を当てはめるのは適切ではなく、単語境界での切り詰めはタイ語を壊します。トルコ語ではİstanbulistanbulは同じ単語で、hreflangのit_ITは無効です。韓国語のサイトではGoogleだけでなくNaverのクローラーも重要です。これらは翻訳ではなく、正しく処理できるかという問題です。すべての機能で一貫するよう、Coreで判断します。

デフォルトとポリシーの上書き設定はconfig/seo.phpにあります。ICUによる単語分割やインストール済みフォントなど、一部の機能は実行環境の依存関係を必要とします。翻訳済みコンテンツはアプリから提供してください。

コンテンツのロケールとUIのロケール

Core 3.17、Filament 1.11、Pro 2.36は、選択したコンテンツのロケールを、メタデータ、算出用フック、プレビューURL、チェックリストのキーワード、AIリクエストに渡します。英語のパネルで、ラベルを変えずにイタリア語や日本語を編集できます。

php
$italian = $post->seoData('it');
$japanese = $post->seoData('ja');

これらの読み取りは、そのロケールのメタデータ行を選び、一時的なロケールの範囲内でgetSEOTitle()getSEODescription()getUrlForSEO()getSEOSchema()などのモデルフックを実行します。フックが例外を投げても、呼び出し元のモデルとアプリのロケールを保持します。SpatieのsetLocale()getTranslatableAttributes()を実装するモデルには、インスタンスのロケールも分離して渡します。フック自身が翻訳済みコンテンツを返す必要があります。Rankbeamは通常のデータベース属性を自動翻訳しません。

ProのモデルベースのAIメソッドと一括入力は、明示的なlocale:を受け付けます。指定しない場合は、翻訳モデルが上書きしたseoData()のデフォルトからコンテンツのロケールを決め、なければアプリのロケールにフォールバックします。Filamentのアクションには、そのフィールド自身のロケールを渡します。単一言語のエディターや追従モードも同じです。独自のキュージョブでは、選択したロケールをシリアライズし、実行時に明示的に渡してください。ワーカーの現在のロケールに依存しないでください。

独自の同期コンテンツ読み取りでは、ModelLocale::run($model, $locale, $callback)が分離したモデルをコールバックに渡し、finallyでアプリのロケールを復元します。ロケール依存の読み取りは、すべてコールバック内で終えてください。遅延イテレーターやクロージャーを返しても、その範囲は延長されません。

文字体系別のタイトル・ディスクリプションの目安

Rankbeamの編集上の文字数目安は、ラテン文字のタイトル・ディスクリプションで60/160書記素、CJKで30/80です。変更可能な近似値であり、ピクセル単位の測定でも、検索エンジンが値全体を表示する保証でもありません。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にあり、文字体系のグループ(latincyrillicgreekcjkthaiarabichebrewdevanagari)をキーにします。一覧にないグループには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の字形を2倍に数えます。そのため「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%以上の位置にあればそこで切り、省略記号は付けず、末尾の句読点を除きます。ラテン文字では従来とバイト単位で同じ結果です。
  • 漢字・仮名・タイ文字には単語間のスペースがないため、上限内の最後の文・節の区切り(。!?、,など)を優先します。次に、韓国語のようにスペースがあればそこを使い、どちらもなければ上限で切ります。
  • 分割は書記素クラスター単位なので、結合文字の並びの途中で切れません。タイ語の母音記号や絵文字の修飾子が、基になる文字から離れることはありません。

ロケールに応じた大小文字処理

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()は比較用です。Coreは、ブランド名を考慮したタイトルサフィックスの重複回避(seo.title_suffix_skip_when_contains)に使います。トルコ語のブランドはiのどちらの形でも一致し、アクセント付きのブランドにも適切な単語境界を使います。Proのキーワードチェックも同じヘルパーを基にします。

ケースフォールディングはアクセントを保持します。アクセントのある綴りとない綴りを、すべて同一視するわけではありません。言語別のステマーが独自の簡約を行う場合はありますが、CaseFolderや同一形の照合とは別の処理です。

hreflang

Googleが読むのはlanguage[-Script][-REGION]、つまりISO 639-1の2文字言語コードに、任意でISO 15924の文字体系とISO 3166-1 alpha-2の地域を加えたもの、およびx-defaultです。es-419のような数値の地域はBCP47では有効ですが、Googleのhreflang仕様の対象外です。Laravelアプリは代わりにロケールit_ITpt_br)を渡しがちですが、アンダースコアはここでは無効です。seo.hreflangの3つのポリシーを、モデルのgetSEOAlternates()一覧に適用してから、<link rel="alternate">タグ、サイトマップの<xhtml:link>項目、llms.txtリンク、監査入力に変換します。すべて同じポリシーを使います。llms.txtは「他の言語版」のリンクから、ページ自身とx-defaultを除きます。

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も追加しません。

無料の監査は、ポリシー適用後の一覧に対して3つのチェックを追加します。

コード重大度意味
hreflang_invalid_codewarningGoogleの仕様外のコード(en-UKjpenglishes-419fil)。
hreflang_duplicate_codenotice同じコードが2回記載されている。
hreflang_missing_selfwarningページ自身のURLが一覧にない。

相互参照(相手のページも戻りの参照を持つか)の確認にはクロールが必要で、Proスキャンが担当します。任意で有効にするcheck_hreflang_reciprocityはSsrfGuardを通して各代替ページを取得し、相手がこのページのURLをその言語コードとともに宣言していなければ、hreflang_not_reciprocalを出します。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

言語コードの3つの仕様

Core 3.18以降は、アプリの設定値とHTMLで配信する値を区別します。

入力アプリ側の正規化HTMLの言語Google hreflang
pt_BRpt-BRそのまま配信すると無効そのまま配信すると無効
de-CH-1901保持有効な登録済みバリアント未対応のバリアント
es-419保持有効な数値地域未対応の数値地域
zh-Hant-TW保持有効有効
fil保持有効な登録済み言語2文字コードの仕様外
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

Core 3.17以前からの移行:Hreflang::isValid()parse()は、配信するコードを厳格に検証します。呼び出し側がLaravelのロケールを渡す場合は、先にfromLocale()を呼んでください。HTMLのlang属性を検査する場合は、トリミングや正規化をせず、LanguageTag::isValidHtml()を使います。非推奨でも登録済みのタグはHTMLでは有効です。正規化は、IANAで優先別名として明示されたものだけを適用し、en-UKen-GBを意味すると推測しません。監査が報告できるよう、不正な項目を事前に除去することもありません。

バリデーターには、2026年8月8日付のIANAレジストリの情報を、ソースハッシュと再現可能なジェネレーターとともに同梱しています。RFC 5646の構造、登録済みサブタグ、extlangの接頭辞、バリアント・拡張の重複を検証します。旧来の登録タグ(grandfathered)と私用範囲にも対応します。バリアントの推奨接頭辞は、有効性を決める必須規則ではありません。拡張の名前空間と構造は検証しますが、CLDRオプションの意味や私用部分の意味はAPIの対象外です。ICUや実行時のダウンロードは不要です。RFC 5646HTMLの言語定義を参照してください。

Pro 2.38以降は、langがない場合や空の場合を不明・欠落として報告し、不正な配信バイト列にはhtml_lang_invalidを出します。文字体系の不一致チェックは、実際の文字体系サブタグ、またはIANAに登録されたデフォルトを使います。私用・拡張の内容や未知の言語を、ラテン文字だとはみなしません。未対応の文字体系グループは判定しません。これらは完全な言語検出ではありません。

相互参照の確認には、元ページの有効な自己参照コードを使います。それがなければ、有効でGoogleに対応するHTMLの言語を使います。別の言語コードに付いた戻りURLでは合格しません。元の言語コードを確定できなければ、結果はhreflang_target_unverifiedのままです。重複する宛先URLは、既存の代替ページ数・本文サイズの制限内で1回だけ取得します。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以外の地域で重要な従来型のWeb検索クローラーも含まれます。Yandex、Baidu、Naver(Yeti)、Seznam、Sogou、360、Cốc Cốc、DuckDuckGoです。目的をsearch_engineとして分類し、デフォルトでは許可します。ポリシーとボット別の上書きの対象になるため、たとえば中国向けに提供していないショップでは、2つのクローラーによる帯域使用を避ける設定ができます。

php
'ai_crawlers' => [
    'policy'    => ['search_engine' => 'allow', /* … */],
    'overrides' => ['baiduspider' => 'disallow', 'sogou' => 'disallow'],
],

SEO::aiCrawlers()->all()match()はAI専用のままです。ProのAIボットログと「AIクローラーN種」の件数は変わりません。検索エンジンを取得するには、searchEngines()all(true)match($ua, true)を使います。AIクローラー制御を参照してください。

Baidu

クローラーと所有権確認タグへの対応は、Baiduでの発見、インデックス登録、検索順位を保証しません。

サイトの所有権確認

所有権確認トークンは、設定した検索エンジンごとに1つのmetaタグとして、すべてのページに出力します。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はプロパティの所有者ごとに1つ発行します。

あらゆる文字体系のOG画像

同梱のカード用フォントは、ラテン文字、キリル文字、ギリシャ文字に対応しています。それ以外の文字体系は、seo:og-imagesを実行するマシンにインストールしたフォントに依存します。CJKフォントは16MB以上あるため、他のフォントは同梱しません。テンプレートには文字体系別のフォールバックスタック(seo.og_image.font_stack)があります。漢字が適切な国・地域の字形になるよう、ページの言語のNoto CJKファミリーを先頭にします。描画予定のタイトルに対応するフォントがホストにない場合、コマンドは文字体系ごとに1回警告します。

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はURLをスラッグ化したり書き換えたりしません。/città//検索のようなパスは、すべての出力でそのまま残ります。IDNホスト(https://münchen.example/)や、Unicodeまたはパーセントエンコードされたパスの正規URLも、監査で受け付けます。PHPのASCII専用FILTER_VALIDATE_URLの代わりにRankbeam\Seo\I18n\Url::isValid()を使います。canonical、hreflang、サイトマップの項目をバイト単位で一致させるため、各URLの形式はUnicodeのままかパーセントエンコードか、どちらか一方に統一してください。

対応言語と「対応」の意味

パッケージは、以下の17ロケールの文言と分析処理の振り分けを提供します。この表は技術上の対応範囲であり、ネイティブによる編集承認や、未設定のホストでの描画保証ではありません。日本語・中国語の単語分析には、利用可能なICUが必要です。使えなければ、影響する単語ベースのチェックはスキップします。ラテン文字以外の描画には適切なフォントが必要です。処理の振り分けは両リポジトリのテストで検証します。Coreのtests/Feature/I18n/SupportedLanguagesTest.phpはロケール一覧、hreflangコード、文字数目安を固定し、Proのtests/Feature/OnPage/LanguageSupportMatrixTest.phpは分析エンジンを固定します。表の行が正しくなくなれば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 / 80ICU辞書ケースフォールド後の完全一致ヒューリスティック、スコアなし
中国語(簡体字)zh_CN30 / 80ICU辞書ケースフォールド後の完全一致ヒューリスティック、スコアなし
中国語(繁体字)zh_TW30 / 80ICU辞書ケースフォールド後の完全一致ヒューリスティック、スコアなし
韓国語ko30 / 80スペースケースフォールド後の完全一致ヒューリスティック、スコアなし
ギリシャ語el60 / 160スペースSnowballLIX
ウクライナ語uk60 / 160スペースケースフォールド後の完全一致LIX
チェコ語cs60 / 160スペースSnowballLIX

この表では、次の3点も明確にしています。

  • Pro 2.37からSnowballを同梱しています。 12言語は、任意パッケージとは独立して、固定した3.1.1のアルゴリズムを使います。ウクライナ語とCJKは同一形の照合を使い、パッケージが独自に語尾規則を作ることはありません。同一形の照合は活用形を見逃す場合があり、ステミングは異なる単語をまとめてしまう場合があります。エンジン制御と移行時の注意を参照してください。
  • 「ヒューリスティック、スコアなし」と「LIX」は別です。 このパッケージでは、日本語・中国語・韓国語にスコアを付けない方法を使います。チェックリストは文の長さと漢字の割合からレベルを報告し、スコアはnullです。設定にかかわらず助言にとどまります。ギリシャ語・ウクライナ語・チェコ語には専用の式を実装していないためLIXを使います。LIXは音節を必要としませんが、その閾値はすべての言語に合わせて校正されているわけではありません。各式の入力には推定値が含まれます。統計の仕様を参照してください。
  • パッケージのUI翻訳は初稿です。 TRANSLATING.mdにネイティブがレビューしたと記載されているものを除きます。イタリア語はレビュー済みですが、それ以外はレビュアーを求めています。翻訳をレビューすることは、パッケージの貢献者として自分の言語でクレジットされるための、最も手軽な方法です。

一覧にないロケールでは、英語の文言、文字体系別またはデフォルトの文字数目安、同一形のキーワード照合、LIXまたはヒューリスティックな読みやすさ評価にフォールバックする場合があります。そのフォールバックは、検証済みの言語対応ではありません。チェックリストのanalysisブロックで、文字体系、分割器、ステマー、読みやすさの方法を確認できます。ラベルだけでなく、利用可否やスキップした判定も確認してください。

地域で重要な検索エンジンに対応する

言語の提供は、テキストだけの問題ではありません。クローラーカタログにはGoogleとBingに加え、Yandex、Baidu、NaverのYeti、Seznam、Sogou、360、Cốc Cốcが含まれ、seo.verificationがそれぞれのサイト所有権確認タグを出力します。韓国語サイトならNaver、チェコ語ならSeznam、ウクライナ語やロシア語ならYandexなどです。地域別の検索エンジンサイトの所有権確認を参照してください。

他のパッケージが追加する機能

  • laravel-seo-filamentは、ライブカウンターとSERPプレビューに同じ文字数ポリシーを使います。また1.9から、言語ごとのseo_metaを編集できます。ロケールごとのタブに専用のカウンター、プレビュー、フォールバック表示を持つか、翻訳プラグインの言語切り替えに追従します。
  • laravel-seo-proは、スキャンのtitle_length / description_lengthチェックとAI支援のプロンプトに同じポリシーを使います。2.34から、ページ自身の言語で分析します。中国語・日本語・タイ語のICU単語分割、Snowballステミング、このCaseFolderによるロケールに応じたキーワード照合、10言語の公表済み読みやすさの式と推定入力、CJK向けと明示したヒューリスティック、ギリシャ語・ウクライナ語・チェコ語向けと明示したLIX、16言語のストップワード、html langとhreflang相互参照のスキャンチェック、ページ言語を指定するAIプロンプト、dompdfで描画できない文字体系向けのChrome出力レポートを提供します。ページ内チェックリストスキャンの課題AI支援レポートを参照してください。

rankbeam/laravel-seoはMITライセンスで公開されています。