String Catalog って実際どうなの?
はじめに
こんにちは、tattsun です。
最近は、プライベートで学習することが増えてきたのでアウトプットとしてブログを書くことにしました 🙌
背景・経緯
レシチャレでは、画像や文字列などを型安全に利用するために SwiftGen を導入しています。
SwiftGen はコマンドを実行することで Localizable.strings から列挙型を自動生成することができ、タイポを防いだり、ローカライズに対応したりと導入されているプロジェクトも多いのではないでしょうか。
しかし、SwiftGen の更新は2年前から止まっており、Concurrency の対応が行われていません。新しく立ち上げたプロジェクトや私たちのようにすでに採用しているプロジェクトでは本当に SwiftGen を採用するか考える必要があります。
私自身も個人開発で文字列をどうやって扱うか考えていたところだったので、String Catalog の可能性を調査しようとなったわけです!
String Catalog について
String Catalog(.xcstrings)は、アプリの文言を表形式で一括管理できるXcodeの仕組みです(Xcode 15以降)。1つのファイルで各言語の文、複数形、差し込み変数をまとめて扱えます。画面幅/デバイス別の文言も用意でき、従来の .strings を置き換えやすいのがポイントです。
個人的におすすめしたい運用方法
実際にアプリを実装しながら試してみて、String(localized:) を利用する方法に落ち着きました。
具体的な実装をお見せします。
enum L10n {
static let title = String(
"title",
defaultValue: "タイトル",
bundle: .module
)
static func count(_ value: Int) -> String {
String(
"count",
defaultValue: "\(value)個",
bundle: .module
)
}
}
/// 例: View での利用
struct SampleView: View {
let value: Int
var body: some View {
VStack {
Text(L10n.title)
Text(L10n.count(value))
}
}
}
上記のように定義し、ビルドすると Localizable.xcstrings に定義が追加されます。
定義した String を削除すれば、Localizable.xcstrings 側も削除されるので楽です!
Swift Macro による拡張
紹介した方法でも良いのですが、マルチモジュールを採用しているため bundle の指定をするのが面倒だったりするので Swift Macro を利用して省略しています。
enum L10n {
static let title = #L10n(
"title",
defaultValue: "タイトル"
)
static func count(_ value: Int) -> String {
#L10n(
"count",
defaultValue: "\(value)個"
)
}
}
内部で bundle を .module に固定し、省略出来るようにしています。Bundle.main を利用する場合はそもそも bundle の指定が不要なのでマクロは不要かもしれません。(一応、マクロ側で切り替えることが出来るようにしています)
こちらは GitHub で公開していますので、実装についてはリンクから参照ください 🙇
まとめ
Namespace の定義が少し面倒だったりと多少の課題はありますが、基本的には String Catalog の方が良さそうに感じました。まだまだ改善の余地があるので、自分でも色々試しながら良さそうな実装があれば共有したいと思います。
Concurreny の対応が SwiftGen の影響で止まっている部分もあるので移行も考えようと思います!
参考URL
Discussion