📖

SwiftPM(Package.swift)でString Catalogs(Localizable.xcstrings)を使う方法

に公開

String Catalogs(Localizable.xcstrings)

Xcodeで作成したApp.xcodeproj内でLocalizable.xcstringsを追加するとSwiftUI.Textで使われた文字列はLocalizedStringKeyとして認識され、ビルドすると自動でString Catalogsに追加されるため、自分で値を追加する必要がありません。

WWDC 2023で発表され、Xcode 15.0から利用可能です。

https://developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-string-catalog

SwiftPM(Package.swift)では一工夫必要

直接App.xcodeprojにLocalizable.xcstrings(リソース)を追加するとBundle.mainに追加されます。しかしSwiftPM(Package.swift)に追加するとBundle.moduleにリソースが追加されます。

SwiftUI.Textのinitにbundleを渡せるのですが、デフォルトでnilになっています。
nilだとBundle.mainを参照するため、SwiftPM(Bundle.module)内にあるLocalizable.xcstringsに自動で追加してくれません。

.init(
  _ key: LocalizedStringKey,
  tableName: String? = nil,
  bundle: Bundle? = nil,
  comment: StaticString? = nil
)

セットアップ

Package.swift

defaultLocalizationの設定が必要

// swift-tools-version: 6.0

import PackageDescription

let package = Package(
  name: "SampleApp",
  defaultLocalization: "en",
  platforms: [
    .iOS(.v18),
  ],
  products: [
    .library(
      name: "SampleApp",
      targets: ["SampleApp"]
    ),
  ],
  targets: [
    .target(
      name: "SampleApp"
    ),
  ]
)

Localizable.xcstrings

String Catalogs(Localizable.xcstrings)を追加

Text++.swift

デフォルトでBundle.moduleを参照するinitをextensionで作成して、元々あったinitをデフォルトで呼ばれないようにする。

import SwiftUI

extension Text {
  init(
    _ key: LocalizedStringKey,
    tableName: String? = nil,
    comment: StaticString? = nil
  ) {
    self.init(
      key,
      tableName: tableName,
      bundle: .module,
      comment: comment
    )
  }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
  var body: some View {
    Text("Hello!")
  }
}

#Preview {
  ContentView()
}

#Preview {
  ContentView()
    .environment(\.locale, .init(identifier: "ja_JP"))
}

ソースコードに直接指定

LocalizedStringKeyを直接持つことで、自動でString Catalogsに追加してくれます。

enum TabItem {
  case favarites
  case recents
  case contacts
  case keypad
  
  var title: LocalizedStringKey {
    switch self {
    case .favarites: "Favorites"
    case .recents: "Recents"
    case .contacts: "Contacts"
    case .keypad: "Keypad"
    }
  }
}

Discussion