|
ドキュメント
デモを予約プラットフォーム
プラットフォームMCPCLIAPI
ワークフロー
ガイド変更履歴

ようこそ

  • 概要
  • 認証
  • エラーとステータスコード
  • Webhookシグネチャ

ローカライゼーション

  • 概要
  • ジョブを作成
  • 翻訳対象外のキーをロックする
  • ジョブグループを追跡
  • 単一ジョブを取得
  • ジョブ一覧
  • Webhook配信
  • リアルタイム進捗(WebSocket)

パイプライン

  • 概要
  • ローカライズ前のAI編集
  • 人によるレビュー
  • AI評価(ポストエディット)
  • 自然なコピーに言い換える
  • 逆翻訳チェック
  • パイプラインを設定
  • パイプライン実行を確認する

プロビジョニング

  • 概要
  • プロビジョニングジョブを作成
  • ソースの種類
  • AIが抽出するもの
  • Webhook配信
  • ライブ進行状況(WebSocket)

同期

  • Localize
  • Recognize

エンジン管理

  • エンジン提案

WebSocketで進捗をリアルタイム表示

ジョブグループを作成しました。どこかでユーザーはスピナーを見つめながら、「14言語に翻訳中…」という表示を見ています。間違ってはいませんが、それだけでは役に立ちません。いつまで経っても動かないからです。欲しいのは、目の前で件数が増えていく表示です。3件完了、次に4件、1つのロケールで失敗、そして完了——そんなふうに進捗が見えることです。

ジョブグループをポーリングしても実現はできますが、リクエストが増えますし、返ってくるのは毎回新しいスナップショットです。実際に何が変わったのかを知るには、前回との差分を取らなければなりません。WebSocketはその前提をひっくり返します。一度接続すれば、ロケールが1つ解決するたびにサーバーがイベントをプッシュします。しかもすべてのメッセージにグループ全体の完全な状態が入っています。つまり、差分を突き合わせるのではなく、スナップショットをそのまま描画すればいいということです。1フレーム落としても、再接続しても、タブを再読み込みしても、次のメッセージが再び完全な真実になります。

text
GET /jobs/localization/groups/:groupId/ws

非同期ローカライゼーションが初めてなら、まず概要からご覧ください。ここでのgroupIdは、ジョブを作成したときに返ってきたものです。

このページの内容

  • メッセージタイプ
  • メッセージペイロード
  • UIへの組み込み
  • APIキーはサーバー側に置く

メッセージタイプ#

ソケット上を流れるメッセージタイプは4種類あります。どのメッセージも、直前に何が起きたかを伝えると同時に、その時点でのグループ全体の状態を渡します。

タイプタイミング主要フィールド
snapshot初回接続時グループ全体の完全な状態
job.completedロケールが正常に完了したときjobId、locale、およびグループ全体の完全な状態
job.failedロケールが失敗したときjobId、locale、error、およびグループ全体の完全な状態
group.completedすべてのジョブが解決したときgroupId、status、およびグループ全体の完全な状態。このメッセージの後、サーバーは接続を閉じます。

すべてのメッセージには、現在のグループ状態を表すsnapshotオブジェクトが含まれます。そこにはtotalJobs、completedJobs、completedWithWarningsJobs、failedJobs、さらにジョブIDをキーにしたjobsマップがあり、各エントリにはそれぞれのlocaleとstatusが入っています。これらのカウントは、ジョブグループ endpointが返すものと同じです。つまり、ソケット経由のスナップショットでも、REST endpoint のポーリングでも、グループがどこまで進んだかは一致します。

スナップショットを描画する。差分は突き合わせない

どのイベントをすでに見たかを追跡したり、取りこぼしたメッセージを再生したり、部分更新をローカル状態にマージしたりする必要はありません。各メッセージでsnapshotを読み取り、それをそのままUIに描画してください。再接続すると、まずsnapshotが再送されるので、今参加したクライアントも、最初からずっと接続していたクライアントも、同じ状態に収束します。

メッセージペイロード#

以下は、サーバーが実際に送るフレームそのものです。IDの形式も実際と同じです(グループはljg_、各ジョブはljb_)。snapshotについては、すでに示した構造の繰り返し部分だけを"..."で省略しています。

接続すると、サーバーは現在の状態を送ります。

json
{
  "type": "snapshot",
  "snapshot": {
    "groupId": "ljg_A1b2C3d4E5f6G7h8",
    "totalJobs": 3,
    "completedJobs": 1,
    "completedWithWarningsJobs": 0,
    "failedJobs": 0,
    "jobs": {
      "ljb_A1b2C3d4E5f6G7h8": { "locale": "de", "status": "completed" },
      "ljb_B2c3D4e5F6g7H8i9": { "locale": "fr", "status": "processing" },
      "ljb_C3d4E5f6G7h8I9j0": { "locale": "ja", "status": "queued" }
    }
  }
}

各ロケールが完了するたびに、イベントには変化したロケール名が入り、更新後のスナップショットも含まれます。

json
{
  "type": "job.completed",
  "jobId": "ljb_B2c3D4e5F6g7H8i9",
  "locale": "fr",
  "snapshot": {
    "groupId": "ljg_A1b2C3d4E5f6G7h8",
    "totalJobs": 3,
    "completedJobs": 2,
    "completedWithWarningsJobs": 0,
    "failedJobs": 0,
    "jobs": {
      "ljb_A1b2C3d4E5f6G7h8": { "locale": "de", "status": "completed" },
      "ljb_B2c3D4e5F6g7H8i9": { "locale": "fr", "status": "completed" },
      "ljb_C3d4E5f6G7h8I9j0": { "locale": "ja", "status": "processing" }
    }
  }
}

失敗は接続切れではなく、通常のメッセージとして届きます。job.failedにはロケールとerrorが入り、同じ完全なスナップショットも一緒に送られます。失敗したロケールはjobsマップ内でstatus: "failed"になり、ほかのロケールはそのまま流れ続け、ソケットもgroup.completedまで動き続けます。

json
{
  "type": "job.failed",
  "jobId": "ljb_C3d4E5f6G7h8I9j0",
  "locale": "ja",
  "error": "Model timeout after 30 seconds",
  "snapshot": { "...": "..." }
}

すべてのジョブが解決すると、サーバーは最後のイベントを送り、接続を閉じます。

json
{
  "type": "group.completed",
  "groupId": "ljg_A1b2C3d4E5f6G7h8",
  "status": "completed",
  "snapshot": { "...": "..." }
}

最後のstatusは、すべてのロケールが成功した場合はcompleted、すべてのロケールで出力は生成されたものの、そのうち少なくとも1つで任意のpipelineステージが失敗した場合はcompleted_with_warnings、一部のロケールが成功して一部が失敗した場合はpartial、すべて失敗した場合はfailedです。これらがグループ全体として何を意味するのかは、ジョブグループを追跡するをご覧ください。

認識できないメッセージでもスナップショットから描画する

対応しているメッセージタイプで分岐し、それ以外はsnapshotから再描画するようにしておけば十分です。どのメッセージにも完全なスナップショットが入っているので、個別の分岐がないフレームでも、それを元に描画するクライアントなら正しい状態を保てます。

UIへの組み込み#

グループがそのまま進捗モデルになります。ジョブを作成したとき、202レスポンスでgroupIdとjobs配列が返ってきます。ロケールごとに1エントリずつです。そのレスポンスを元に進捗レコードを初期化すれば、ソケットが埋めていく形がそのまま手に入ります。つまり、カウント対象となる総数と、0から始まるカウンターです。

javascript
const { groupId, jobs } = await response.json();

await db.translationProgress.create({
  contentId: content.id,
  groupId,
  totalLanguages: jobs.length,
  completedLanguages: 0,
});

次に、そのgroupIdに対してソケットを開き、各メッセージでsnapshotを読んで再描画します。ロケールが完了するたびにカウンターが増えていき、group.completedが届いたら止めます。

javascript
import WebSocket from "ws";

const groupId = "ljg_A1b2C3d4E5f6G7h8";
const ws = new WebSocket(
  `wss://api.lingo.dev/jobs/localization/groups/${groupId}/ws`,
  { headers: { "X-API-Key": process.env.LINGO_API_KEY } }
);

ws.on("message", (raw) => {
  const event = JSON.parse(raw);
  const { snapshot } = event;

  switch (event.type) {
    case "snapshot":
      console.log(`${snapshot.completedJobs}/${snapshot.totalJobs} complete`);
      break;
    case "job.completed":
      console.log(`${event.locale} ready (${snapshot.completedJobs}/${snapshot.totalJobs})`);
      break;
    case "job.failed":
      console.error(`${event.locale} failed: ${event.error}`);
      break;
    case "group.completed":
      console.log(`All translations done: ${event.status}`);
      ws.close();
      break;
  }
});

3ロケールのグループで動かすと、進行中の様子は次のように出力されます。

text
1/3 complete
fr ready (2/3)
ja failed: Model timeout after 30 seconds
All translations done: partial

カウンターは自然に進み、1つのロケールが失敗してもストリームは止まらず、partialが最終的な着地点を教えてくれます。つまり、ただのスピナーを本物の進捗バーに変えるのに必要な情報が、すべて揃っています。ここで大事なのは、このループが状態を蓄積しないことです。各分岐は、その時点のメッセージに入っているsnapshotを読むだけなので、初回接続時でも、更新時でも、再接続時でも、同じコードで正しく動きます。

APIキーはサーバー側に置く#

このソケットはAPIキーで認証します。REST endpoint と同じ、組織スコープのキーです。つまり、これを開く場所はブラウザではありません。クライアントJavaScriptにAPIキーを置けば、ソースを見た人なら誰でも、組織内のすべてのエンジンにアクセスできてしまいます。

接続元はブラウザではなくバックエンドにする

WebSocketは、すでにキーを保持しているサーバーから開き、その後イベントを自分で管理するチャネル経由でブラウザに中継してください。たとえば、WebSocket や server-sent events のストリームです。これならフロントエンドはリアルタイム進捗を受け取れ、キーがインフラの外に出ることもありません。

これはwebhookモデルと同じです。Lingo.dev に接続するのはサーバー側で、ユーザーに届くのは、自分のアプリが転送すると決めたものだけです。

この機能の位置づけ#

WebSocketはライブ表示のための仕組みです。1つのグループに紐づき、そのグループが完了すると接続は閉じます。タブを閉じても、あるいはデプロイをまたいでも失われない、耐久性のあるサーバー間配信が必要なら、webhooksと組み合わせてください。ソケットは画面上のUIをリアルタイムに動かし、webhookは各結果が届いた瞬間に記録します。同じcreate callから両方を配線すれば、ユーザーには進捗がリアルタイムで見え、バックエンドは誰が見ているかに関係なく出力を保持できます。

Webhook配信
各ロケールが完了した時点で確実に届く、耐久性のあるサーバー間配信
ジョブを作成
翻訳するコンテンツを送信し、ここで接続に使うgroupIdを取得します
ジョブグループを追跡する
グループのステータスと、部分的な完了がグループ全体にとって何を意味するか

このページは役に立ちましたか?

Max PrilutskiyMax Prilutskiy·更新済み 約2か月前·2分で読めます