こんにちは、岸川克己です。
SwiftのAutomatic Grammar Agreementとは、英語における複数形や三単現のsのように、翻訳テキストの一部に語形の変化がありうるという情報を埋め込み、実行時にOSが 数などに合わせて指定した語句を文法的に正しい文章に自動的に修正してくれる仕組みです。
例を見てみましょう。
Text("Add ^[\(count) ticket](inflect: true) to your order.")
この例ではcount変数に入る数によって後続のticketの語形が単数形か複数形のどちらかに自動的に変化します。
Add 0 tickets to your order. Add 1 ticket to your order. Add 2 tickets to your order.
この機能を使わずに実装するなら、あらかじめticket/ticketsの複数のテキストを用意しておいて、使い分ける処理を書くか、String Catalogなどで条件によって正しいテキストが選ばれるようにします。
Automatic Grammar Agreementを使うと複数のテキストを用意する必要がなく、リソースをシンプルに保てます。
また、翻訳対象の言語について詳しくなければ、どの語系に変化させるのか判断できないこともあります。この例ではゼロの場合は0 ticketsと複数形が正しいですが、意外と迷うところではないでしょうか。ゼロの扱いは言語によって異なる場合があり、Automatic Grammar Agreementはこうした言語の差を吸収できます。
言語によっては名詞に性別がある場合があります。また、離れたところにある語が変化に影響することもあります。

"Our \(food) is made of \(ingredients)."
上記のテキストのスペイン語翻訳が下記である場合、スペイン語は名詞の性別によって他の語形が変化します。
"^[Nuestro %@](inflect: true) está ^[hecho](agreeWithArgument: 1) de %@."
このとき、food変数の名詞によってNuestroの語形が変化しますが、語形の変化は後ろのhechoにも影響します。
スペイン語では、所有形容詞(nuestro/nuestra)や過去分詞(hecho/hecha)が、参照する名詞の性・数に一致して変化します。
このように、ある単語が離れた別のところの単語を変化させうる場合、^[hecho](agreeWithArgument: 1)のように書きます。
ここで1が示すのはテキストに与えられた変数で1始まりのインデックス番号です。
言い換えると、「文字列補間(引数)の1番目」を示します。※%@ はString Catalog上のプレースホルダです。AttributedString(localized:)中に文字列補間を使って引数として渡されます。
foodが"ensalada"(女性名詞)だった場合、翻訳テキストは最終的に次のようになります。
Nuestra ensalada está hecha de lechuga y tomate.
NuestroがNuestraに、hechoがhechaにそれぞれ語形変化しています。
同様のケースで、異なるUI要素のテキストが他のUIのテキストに影響する場合があります。 次の図では、左の「メニュー名」と右の「サイズ表示」が別々のUI要素になっています。サイズ側がメニュー名に一致して変化するのがポイントです。

この例では、選択されたメニュー(Ensalada)が女性名詞なので、大きさを形容する単語Pequeñoもそれに合わせて女性形であるpequeñaに変化する必要があります。
このような語形変化を引き起こすテキストがテキスト自身に含まれていない場合、agreeWithConceptを使います。
例えば翻訳テキストを次のように用意します。
| Key | English | Spanish |
|---|---|---|
salad |
salad |
ensalada |
small |
small |
^[pequeño](agreeWithConcept: 1) |
^[pequeño](agreeWithConcept: 1)は与えられたconceptの単語によって変化する、ということを示します。1は1始まりのインデックス番号です。
conceptはAttributedString.LocalizationOptions()を使って配列の形で渡します。
let food = String(localized: "salad") ... var options = AttributedString.LocalizationOptions() options.concepts = [.localizedPhrase(food)] let size = AttributedString(localized: "small", options: options) // pequeño → pequeña
課題
アノテーションはApple独自の形式(Markdown拡張属性)なので独特な文法の知識が必要になります。文字列中に書くことになるので型安全性は期待できず、書き間違いによって期待通りの結果が得られない場合があります。
またアノテーションはほとんどの場合、コード中ではなく翻訳テキストとしてString Catalogなど別のファイルに記述されます。つまり、コードを読解するために翻訳テキストを参照する必要があるので、可読性やメンテナンス性が悪くなる恐れがあります。
^[pequeño](agreeWithConcept: 1)といったアノテーションを網羅的に解説したドキュメントはないので、どのようなアノテーションがあるのか複数のWWDCのセッションを参照して覚える必要があります。
アノテーションの文法は実装が露出しすぎているので、一般的な翻訳者が理解することを期待するのは現実的ではありません。ではソフトウェアエンジニアがアノテーションを付加できるかというと翻訳対象の言語の知識が求められるためにそれも現実的とは言えません。この機能を活用するためには翻訳のワークフローを考え直す必要があります。
まとめ
Automatic Grammar Agreementは、ローカライズ文字列の一部にアノテーション(inflect / agreeWithArgument / agreeWithConcept など)を付け、実行時にOSが数・性・参照関係に基づいて語形を自動調整する仕組みです。英語の複数形のような分かりやすい例だけでなく、スペイン語のように離れた語が一致するケースや、別UI要素間で依存関係を持つケースにも対応できます。
一方で、アノテーションはApple独自の記法で翻訳テキストにロジックが混ざるため、型安全性・可読性・翻訳者・開発者の理解コスト、ドキュメント不足といった課題もあります。使いこなせればかなり強力な仕組みであることは間違いないですが、実際に運用するには綿密なコミュニケーションやワークフローの構築が求められるでしょう。
個人的には現段階で実運用に載せることは難しいと考えています。はたして上手に活用できる方法はあるのでしょうか。