ヘッドレスCMS + PolyLingo

ヘッドレスCMSの多言語対応。

PolylangはWordPressサイトに完全な多言語ワークフローを提供しました。PolyLingoはそのワークフローをSanity、Contentful、Webflow、Framer、その他すべてのヘッドレスCMSに、午後のうちに統合できるREST APIを通じてもたらします。

Sanity
完全統合例
Contentful
完全統合例
Webflow
完全統合例
36
対象言語数

ヘッドレスCMSの多言語対応は未解決の問題です。

Sanityには国際化プラグインがあります。Contentfulにはロケールがあります。しかしどちらもコンテンツを翻訳するのではなく、単に複数言語で保存しているだけです。その言語スロットを埋める作業は依然として手動です。英語のコンテンツをエクスポートし、リッチテキストやJSON構造を壊す翻訳ツールにかけ、出力を修正し、再インポートし、すべての言語で繰り返す。頻繁に公開する活発なコンテンツチームにとって、このワークフローはスケールしません。また、多くの小規模セットアップでは存在しないため、コンテンツはまったく翻訳されないままです。

ヘッドレスCMSのアーキテクチャはコンテンツ管理と配信を分離しています。これは柔軟性に優れていますが、ギャップも生みます。CMSは言語バリアントを保存しますが、そのバリアントに翻訳コンテンツを埋めるものがありません。そのレイヤーは自分で構築する必要があります。

ほとんどのチームは2つの状況に陥ります。DeepLにコピー&ペーストして手動翻訳(遅くてエラーが多くスケールしない)、または翻訳APIとのカスタム統合を作り永続的に保守する。どちらも良い解決策ではありません。PolyLingoはクリーンな第三の選択肢です。

PolyLingoはあなたのCMSに欠けている翻訳レイヤーです。

PolyLingoはCMSの公開ワークフローに直接統合されます。コンテンツ公開時に発火するWebhookを設定し、コンテンツをPolyLingoに渡し、すべての言語の翻訳版を受け取り、CMSに書き戻します。Sanityではサーバーアクションの数行、ContentfulではWebhookハンドラー、カスタムセットアップではHTTPコールです。翻訳モデルはMarkdown、HTML、JSON、リッチテキストなどのコンテンツフォーマットを理解し、構造を保持します。

パターンはすべてのCMSで一貫しています。ソース言語のコンテンツを取得し、すべての対象言語でPolyLingo APIを呼び出し、管理API経由で翻訳コンテンツをCMSに書き戻す。これはビルド時スクリプト、CIジョブ、Webhookハンドラーとして実行可能で、ワークフローに合わせて選べます。

PolyLingoはMarkdown、HTML、プレーンテキストを扱うため、CMSがリッチコンテンツに使うフォーマットに対応します。構造化フィールド(見出し、本文、抜粋)は個別に翻訳でき、どのフィールドを翻訳するか細かく制御可能です。

🟠

Sanity + PolyLingo

Sanityのドキュメント国際化プラグインはロケールごとにリンクされたドキュメントバリアントを作成します。以下のスクリプトは英語のベースドキュメントを取得し、各対象言語の翻訳バリアントを自動生成します。

ドキュメント単位のi18nパターン(一つのドキュメントに一言語)とフィールド単位パターン(すべてのロケールを一つのドキュメントに含む)に対応。フィールド単位の場合はドキュメントではなくフィールドをループしてください。

scripts/translate-sanity.mjs
// scripts/translate-sanity.mjs
// Fetches published posts and translates each to all target languages

import { createClient } from '@sanity/client'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID,
  dataset: 'production',
  token: process.env.SANITY_TOKEN,
  apiVersion: '2024-01-01',
  useCdn: false,
})

const posts = await sanity.fetch(`*[_type == "post" && __i18n_lang == "en"]`)

for (const post of posts) {
  const response = await fetch('https://api.usepolylingo.com/v1/translate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.POLYLINGO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content: post.body_markdown,
      format: 'markdown',
      targets: ['es', 'fr', 'de', 'ja', 'zh'],
    }),
  })

  const { translations } = await response.json()

  for (const [lang, content] of Object.entries(translations)) {
    await sanity.create({
      _type: 'post',
      __i18n_lang: lang,
      __i18n_base: { _type: 'reference', _ref: post._id },
      title: translations[lang + '_title'] || post.title,
      slug: { current: `${post.slug.current}-${lang}` },
      body_markdown: content,
    })
  }
}
🔵

Contentful + PolyLingo

Contentfulは同一エントリー内のフィールドとしてロケールバリアントを保存します。以下のスクリプトはContentful管理APIを使い英語エントリーを取得、翻訳し、翻訳済みコンテンツをロケール固有フィールドに直接書き込みます。手動のコピー&ペーストは不要です。

ContentfulはBCP 47ロケールコード(例:es-ES)を使います。PolyLingoのISO 639-1コードをContentfulのロケール設定に合わせてマッピングしてください。

scripts/translate-contentful.mjs
// scripts/translate-contentful.mjs
// Translates Contentful entries to all target locales

import contentful from 'contentful-management'

const client = contentful.createClient({
  accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
})

const space = await client.getSpace(process.env.CONTENTFUL_SPACE_ID)
const env = await space.getEnvironment('master')
const entries = await env.getEntries({ content_type: 'blogPost', locale: 'en-US' })

for (const entry of entries.items) {
  const enBody = entry.fields.body['en-US']

  const response = await fetch('https://api.usepolylingo.com/v1/translate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.POLYLINGO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content: enBody,
      format: 'markdown',
      targets: ['es-ES', 'fr-FR', 'de-DE'],
    }),
  })

  const { translations } = await response.json()

  for (const [locale, content] of Object.entries(translations)) {
    entry.fields.body[locale] = content
  }

  await entry.update()
  await entry.publish()
}
🌀

Webflow + PolyLingo

WebflowのLocalization API(CMSおよびビジネスプランで利用可能)はロケール固有のフィールドコンテンツをサポートします。以下のスクリプトはCMSコレクションアイテムを取得し、HTML本文フィールドを翻訳し、Webflow v2 API経由で各ロケールバリアントに書き戻します。

WebflowはリッチテキストフィールドをHTMLとして保存します。PolyLingoのHTML翻訳はWebflow生成のマークアップ(カスタムクラス、属性、埋め込み要素)をすべてそのまま保持します。

scripts/translate-webflow.mjs
// scripts/translate-webflow.mjs
// Webflow Localization API + PolyLingo

const headers = {
  'Authorization': `Bearer ${process.env.WEBFLOW_API_TOKEN}`,
  'accept-version': '2.0.0',
  'Content-Type': 'application/json',
}

// Fetch English CMS items
const itemsRes = await fetch(
  `https://api.webflow.com/v2/collections/${process.env.WEBFLOW_COLLECTION_ID}/items`,
  { headers }
)
const { items } = await itemsRes.json()

for (const item of items) {
  const response = await fetch('https://api.usepolylingo.com/v1/translate', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.POLYLINGO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content: item.fieldData['body-html'],
      format: 'html',
      targets: ['es', 'fr', 'de'],
    }),
  })

  const { translations } = await response.json()

  // Write translated content back to Webflow locale fields
  for (const [lang, content] of Object.entries(translations)) {
    await fetch(
      `https://api.webflow.com/v2/collections/${process.env.WEBFLOW_COLLECTION_ID}/items/${item.id}/locales/${lang}`,
      { method: 'PATCH', headers, body: JSON.stringify({ fieldData: { 'body-html': content } }) }
    )
  }
}

PolyLingoがヘッドレスCMSユーザーに提供するもの

  • Sanity — 公開時にWebhook経由で翻訳し、ドキュメントのロケールに書き戻す
  • Contentful — 英語ロケール更新時にエントリーを自動翻訳
  • Webflow — API経由でCMSコレクションアイテムを翻訳
  • APIを持つあらゆるヘッドレスCMS — 統合パターンは同じ
  • リッチテキスト、Markdown、HTMLをすべて正しく保持
  • 36言語すべてを一度のリクエストで — 言語ごとの呼び出し不要
  • 管理APIを持つあらゆるCMSに対応
  • 公開ごとに再翻訳可能 — 手動同期不要

標準的な多言語CMSワークフロー

1

ソース言語でコンテンツを書く

英語(またはソース言語)でコンテンツを作成・公開します。CMSはこれを権威あるバージョンとして保存します。編集ワークフローを変更する必要はありません。

2

翻訳スクリプトを起動する

スクリプトを手動、スケジュール、またはCMSの公開イベントでトリガーされるWebhook経由で実行します。スクリプトはドキュメントごとに一度PolyLingoを呼び出し、すべての対象言語の翻訳を取得し、一括でCMSに書き戻します。

3

デプロイ — 翻訳済みコンテンツが公開される

フロントエンドは通常通りCMSからロケール固有のコンテンツを読み込みます。フロントエンドコードの変更は不要です。翻訳コンテンツは各ロケールルートで正しい言語で表示されます。

対象ユーザー

✍️

SanityやContentfulのコンテンツチーム

編集者は英語で公開します。翻訳コンテンツは編集チームが翻訳ツールに触れることなく自動的にすべてのロケールに表示されます。

🏢

多言語サイトを構築する代理店

すべてのクライアントサイトに多言語対応が必要です。PolyLingoはあらゆるヘッドレスCMSで使える再利用可能で請求可能な統合を提供します。

🌍

ローカライズされた商品コンテンツを持つEコマース

商品説明、カテゴリーページ、ブログコンテンツが公開時に自動翻訳されます。ロケール固有の価格設定と組み合わせて完全にローカライズされたショッピング体験を提供します。

ヘッドレスCMS多言語に関するよくある質問

PolyLingoはここにないCMSでも使えますか?

はい。管理APIを持つCMSなら同じパターンで統合可能です。コンテンツ取得、PolyLingo呼び出し、書き戻し。Prismic、Storyblok、DatoCMS、Strapi、Ghost、Directusも管理APIを持ち、この方法で動作します。上記のSanity、Contentful、Webflowの統合例がパターンを示しています。

埋め込み画像やリンクを含むリッチテキストも翻訳できますか?

はい。HTML翻訳は画像(srcとalt属性を正しく処理)、リンク(href保持、リンクテキスト翻訳)、iframeなどすべての埋め込み要素を保持します。非翻訳指定されたコンテンツ(例:コードブロック)は翻訳されません。

翻訳すべきでないコンテンツはどう扱いますか?

非翻訳フィールド(スラッグ、日付、技術的識別子)を持つ構造化コンテンツの場合、翻訳したいフィールドだけを送信してください。翻訳可能と非翻訳部分が混在するリッチテキストはHTML形式を使い、PolyLingoがコードブロックなどの構造要素を保持しつつテキストを翻訳します。

CMSにネストされたコンテンツタイプがある場合は?

深くネストされたコンテンツ(他ドキュメントへの参照を含む)はドキュメントタイプごとに独立して翻訳してください。循環参照を避け、翻訳対象を明確に制御できます。ドキュメント間の参照はCMSが管理し、PolyLingoはフィールドコンテンツのみを扱います。

ソースコンテンツが変わったとき翻訳を同期するには?

推奨パターンはCMSのWebhookで公開イベントごとに翻訳スクリプトをトリガーすることです。これによりソース変更時に翻訳が更新されます。更新頻度が低い場合は夜間スケジュールや本番デプロイ前の実行も有効です。

翻訳を自動公開ではなく「レビューが必要」とマークする方法は?

CMSによります。ContentfulとSanityはドラフト状態をサポートし、翻訳コンテンツを公開ではなくドラフトとして書き込み、各ロケールの公開前に人間のレビューを可能にします。上記スクリプト例は即時公開ですが、最終ステップをドラフト作成に変更すればレビュー用ワークフローになります。

今日からヘッドレスCMSに多言語対応を追加しましょう。

無料プラン。月50,000トークン。クレジットカード不要。

無料で翻訳を始める

無料プラン — 月50,000トークン。あらゆるCMSで動作。

ヘッドレスCMSの多言語対応 — Sanity、Contentful、その先へ — PolyLingo | PolyLingo