🗺️

String Catalogsの概要と妥当な運用方針

2023/09/13に公開

String Catalogsとは

String Catalogsとは、WWDC23で発表されたAppleのプラットフォームにおける新しいローカライズのツールです。

今までは、ローカライズをする際には Localizable.stringsLocalizable.stringsdict ファイルを用いていましたが、ローカライズ漏れや複数形対応の複雑さが課題としてありました。

そこでXcode 15から導入されたのがString Catalogsです。

Xcodeをビルドすると毎回ソースコード中のローカライズ対象の文字列を拾い上げてString Catalogs( Localizable.xcstrings )へ集約してくれます。

開発言語を中心に他言語の翻訳達成率が可視化される他、各文字列に対して NEWNEEDS REVIEW, STALE のステートを付与することが可能なので、翻訳の進捗を管理しやすくなります。

String Catalogsはもちろん複数形にも対応していて、以前の stringsdict と比較すると直感的でより洗練されたものとなっています。

ソースコードの翻訳対象の文字列にString Interpolationで変数を埋め込むと、ビルド時に適切にString Catalogsへ抽出され、右クリックで Vary by Plural をクリックすると複数形対応を進めることができます。

複数形対応の難しい点は、対応方針が言語によって大きく異なるというところです。
例えば、英語であれば単数 or 複数のみの対応で十分ですが、ウクライナ語の場合その対応はより複雑なものとなります。

しかし、String Catalogsではこのような言語ごとの複数形対応方針を明示してくれるようになっており、英語では One / Other が、ウクライナ語では One / Few / Many / Other がrequiredなフィールドとして表示されるようになっています。

気になるポイント: KeyとValueが同じ!

String Catalogsはソースコードから翻訳対象の文字列を抽出します。
その特徴からか、WWDCのセッションで例示されるコードではKey名がそのままValueとなっています。

このソースコードから文字列を抽出する機構自体は実は昔からあり、genstringsコマンドやXcodeのExport Localizationsの機能が該当します。
その頃からAppleはKeyとValueにて同じ文字列を使うという方針がローカライズにおいて一般的であるとしています。

archiveされた開発者向けドキュメントではありますが、以下のような公式の記述も確認できます。

For the development language, it is common to use the same string for both the key and value, but doing so is not required.
訳: 開発言語では、キーと値の両方に同じ文字列を使うのが一般的だが、必ずしもそうする必要はない。

KeyとValueが同じであることに対する懸念点

しかしながら、KeyとValueが同じであることには以下のようなデメリットがあります。

複数の意味を持つ単語の衝突

単語は、使われる文脈によって複数の異なる意味を持つことがあります。例えば、「Book」という単語は、印刷された本を指す名詞になることもあれば、予約をする動作を表す動詞になることもあります。

KeyとValueを同じにしてしまうと、この二つの意味を一つのKey/Valueとして登録することになり、他言語で意味ごとに翻訳することができなくなります。

// en
"Book" = "Book";

// ja
"Book" = "本";
"Book" = "予約する"; // ❌

カスタムビューの翻訳漏れに気づきづらい

カスタムビューを作り引数で文字列を受け取る場合は、その文字列をString Catalogsへ認識させるために LocalizedStringResource 型を明示する必要があります。

struct CardView: View {
    let title: LocalizedStringResource // ✅
    let subtitle: LocalizedStringResource // ✅

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 10.0)
            VStack {
                Text(title)
                Text(subtitle)
            }
            .padding()
        }
    }
}

// 🙆 ローカライズされる
CardView(
    title: "Recent Purchases",
    subtitle: "Items you’ve ordered in the past week."
)

KeyとValueを同じにしてしまうと、LocalizedStringResource型の明示を忘れてString型を定義してしまっていても、開発環境ではそのことに気づくことが難しく、ローカライズ後に他言語へ切り替えて動作確認をしてみて初めて気づくこととなるでしょう。

struct CardView: View {
    let title: String // ❌
    let subtitle: String // ❌
    ...
}

// 🙅 ローカライズされない
// 💭 KeyとValueが同じなので開発言語では翻訳漏れに気づきづらい
CardView(
    title: "Recent Purchases",
    subtitle: "Items you’ve ordered in the past week."
)

解決策: 任意のKey名を付与する!

このような懸念点から、自分はKeyとValueに同じ文字列を設定する方針に反対です。
コンテキストがわかるような任意のKey名を設定するのが良いでしょう。

"login-button-title" = "Login";

実際これまでも、 Localizable.strings にて任意のKey名を設定してR.swiftSwiftGenのようなライブラリで翻訳文字列に対応するコードを自動生成して参照するようにしてきた人は多いように思います。

しかし、String Catalogsを活用するためにはソースコードへKeyを書き込む必要があります。

一度Keyを書いたのちにビルドして抽出されたString Catalogsの該当行にてValueを設定しにいくというのは手間のかかる作業です。
また、String Interpolationによる変数の埋め込みも考慮すると、 login-button-title-\(parameter) のような不自然なKey名となってしまいます。

String Catalogsと任意のKey名の運用方針

String Catalogsと任意のKey名を運用するためには、 String.init(localized:defaultValue:options:table:bundle:locale:comment:including:)defaultValue を使用してKeyとともにValueもソースコード側で設定するのが良いでしょう。

Text(String(
    localized: "login-button-title",
    defaultValue: "Login"
))

この状態でビルドをするとString Catalogsには以下のように文字列が抽出されます。

この defaultValue の型は LocalizationValue となっており、String Interpolationによる変数埋め込みにも対応しています。

例えば、以下のように2変数をString Interpolationで埋め込んだ文字列について見てみましょう。

Text(String(
    localized: "string-interpolation-example", 
    defaultValue: "You have \(birdCount) birds in \(backyardCount) backyards"
))

ビルドするとString Catalogsには以下のように変数も適切に抽出されています。

String Catalogsの強みである複数形対応も問題なく行うことができます。

まとめ

  • String Catalogsを何も考えずに導入しようとするとKeyとValueで同じ値を設定しがち
  • KeyとValueが同じ値であることにはいくつか懸念点が存在する
  • Key名にはコンテキストに応じた任意の命名をするのが理想的
  • String Catalogsで任意のKey名を運用するためには String.init(localized:defaultValue:) を用いてKeyとValueを一緒に設定してあげるのが良い

本記事が、みなさんがString Catalogsを導入する上での参考になれば幸いです。

参考

https://developer.apple.com/videos/play/wwdc2023/10155/

Discussion