Scribe

インライントークン

翻訳不要な文字列やアセットURL、リンクをMDX内に埋め込めるトークン機能。一度設定すれば、再翻訳なしで全ロケールの情報が自動更新されます。

View as Markdown

MDXの本文では、Scribeがすでに保持している値が必要になることがよくあります。別エントリへのリンク、アセットの公開URL、絶対に翻訳してはいけない文字列、または複数箇所で使い回されるドキュメント固有の短いテキストなどです。これらをハードコードすると、本文の保守性が著しく低下し(スラグの変更やアセットの移動によってリンクが密かにリンク切れになります)、翻訳作業の妨げにもなります(URLなどは本来「翻訳」されるべきではありません)。

インライントークンは、この問題を解決します。英語の本文内でトークンを一度記述するだけで、読み取り時に各ロケールに合わせて適切に解決されます。一方、翻訳プロセスにおいてこのトークンは、不透明で変更不可のプレースホルダーとして扱われます。

構文

トークンは ${{<kind>:<args>}} の形式で記述します。以下の4種類があります。

トークン解決される値
${{static:"text"}}そのまま出力されるリテラル文字列 text (JSON文字列として扱われるため、引用符やエスケープが明確に定義されます)。翻訳の対象外となり、すべてのロケールで完全に一致します。
${{relation:<typeId>:<enSlug>:href}}ターゲットとなるエントリへのリンクパスリレーションモードを参照)。モードのサフィックスは必須です。
${{relation:<typeId>:<enSlug>:slug}}ターゲットの英語スラグ文字列。Scribeのコンテンツを独自に読み込むMDXコンポーネント向けの安定した識別子として機能します。
${{asset:/web/path.webp}}アセットの公開URL(assets.publicPath にパスを結合したもの)。assets を参照してください。
${{var:key}}同一ドキュメントの frontmatter.vars[key] の値。

スラグに : を含めることはできないため、リレーションのパースで曖昧さが生じることはありません。モードのサフィックスを省略した単体の ${{relation:<typeId>:<enSlug>}} はバリデーションエラーになります。

リレーションモード

すべてのリレーショントークンは、:href または :slug のいずれかで終わる必要があります。

モード目的
:hrefナビゲーション用のリンク。解決後の形式は、読み取り側の環境によって異なります(後述)。
:slug識別子。常にターゲットの英語スラグ文字列となります。

:href の解決結果は、ドキュメントの読み取り環境によって異なります。

読み取り環境:href の解決結果
createScribe() (アプリ実行時)ローカライズされたスラグを含む、ロケール情報のないパス名(例: /for/vestidos)。ルーターの Link コンポーネントにそのまま渡すことができます。
静的 .md エクスポートファイル拡張子を含む、ローカライズされた完全な公開パス(例: /es/for/vestidos.md)。エクスポートされるファイルの配置と一致します。

エスケープハッチ

$\\{{ (ドル記号と波括弧の間にバックスラッシュを挟む)と記述すると、リテラルの ${{ としてレンダリングされ、トークンとしては扱われません。ドキュメント内でトークンの構文自体を説明したい場合に使用します(このページでも実際にこの方法を使用しています)。

vars フロントマターキー

vars は任意の Record<string, string> であり、型スキーマに追加することなく各エントリで自由に宣言できます。

---
title: Spring sale
vars:
  cta: Shop the sale
---

Ready? ${{var:cta}}. Limited time only.

vars は予約されたキーです。スキーマのバリデーション前に抽出されるため、厳密なスキーマによって拒否されることはありません。また、翻訳対象のフィールドとして扱われることもなく、英語のドキュメントにのみ存在します。翻訳されたドキュメントは、親となる英語ドキュメントの vars マップから ${{var:key}} を読み取るため、情報のソースは常に1つに保たれます。

ハッシュ化と翻訳

トークンはハッシュ化の前に抽出されます。各トークンは不活性な番号付きマーカー(出現順に %%1%%%%2%% など)に置き換えられます。このプレースホルダーが埋め込まれた本文がハッシュ化され、翻訳者に送信されます。これにより、以下のようになります。

  • トークンの値(静的テキスト、リレーション先、アセットパス、var の値など)を変更しても英語のハッシュは変わりません。そのため、値の変更のみで各ロケールが古い状態(stale)になることはありません。
  • トークンの追加、削除、順序変更を行うとハッシュが変わるため、期待通りに翻訳が再ステージングされます。
  • トークンが1つも含まれない本文のハッシュは以前と完全に一致するため、トークン機能を導入したからといって大規模な再翻訳が発生することはありません。

翻訳者には、%%n%% マーカーは変更不可であるという指示が出されます。各マーカーを正確に一度だけ再現し、翻訳したり番号を変更したりせず、文法上必要な場合にのみ文中での位置を移動させます。翻訳の受け取り時には、すべてのマーカーが正確に一度ずつ出現するかどうかの確認が行われます。不一致があった場合、その行はエラーとなり再試行されます。保存される翻訳済みの本文にはマーカーが保持され、読み取り時に値が埋め込まれます。

読み取り時の置換

置換は読み取り時のプロセスであり、アセットの解決と同様に制御されます。createScribe() はアプリ実行時に置換を有効化し、静的エクスポートは .md レイアウト用に置換を有効化します。CLI、scribe validate、およびStudioでは、生のトークン構文を維持します。これにより、ソース本文の解析と再ハッシュ化が可能になります。

  • 英語ドキュメントは、その場でトークンが置換され、デフォルトロケールとして解決されます。
  • 翻訳済みドキュメントは、現在の英語の本文から抽出されたトークンリストを使用して %%n%% マーカーを埋め込み、そのドキュメントのロケールとして解決されます。そのため、翻訳自体が古い場合であっても、リレーションリンクは常に最新のローカライズされたスラグを指し示します。

実行時に空の文字列として解決される(そして scribe validate によってフラグが立てられる)特殊なケースが2つあります。ターゲットタイプに :href モードでの path が存在しないリレーションと、存在しない var キーの参照です。

バリデーション

scribe validate は、これらをエントリレベルの問題として報告します。

  • 不正なトークン構文(誤ったJSON文字列、引数の数の間違い、不明な種類): エラー。
  • relation: 不明な typeId、不明な enSlug、モードの欠落、またはルーティング不可能なタイプをターゲットとする :href リレーション: エラー。
  • asset: ディスク上にファイルが存在しない: エラー。
  • var: ドキュメントの vars マップにキーが存在しない、または vars が文字列対文字列のレコードではない: エラー。

生のトークンはMDX本文のバリデーション前に不活性なテキストにマスクされます。そのため、有効なトークンがMDXのパースエラーを誤って引き起こすことはありません。

本文での参照と削除

Studioの「Used by(使用元)」パネルやアセットブラウザは、本文をスキャンします。${{relation:...}} トークンはバックリファレンス(フィールドラベル body)として表示され、${{asset:...}} トークンは宣言されたアセットの参照として登録されます。他のエントリの本文からのみ参照されているエントリを削除しても、連鎖的な削除、切り離し、またはブロックが発生することはありません(エントリの削除を参照)。削除プランでは、このような参照は警告のみの「本文での参照」セクションにリストされます。これらは削除後に宙吊り状態となり、バリデーションエラーになります。

インライントークン · Scribe