85672


複数言語に対応したiOSアプリを開発しております．  
このiOSアプリで， **iOSが表示する文言と同じ文言を複数の言語で表示させたい** と考えています．

例えば，写真へのアクセスを求める場合は，iOSが以下の文言を表示します．

[![画像の説明をここに入力](https://i.stack.imgur.com/KBbbt.jpg)](https://i.stack.imgur.com/KBbbt.jpg)

以下の文言を **複数の言語でどのように表示されるか知る方法** はないでしょうか．

  * "[アプリ名]"から"写真"にアクセスしようとしています
  * 写真を選択...
  * すべての写真へのアクセスを許可
  * 許可しない

iPhoneの言語設定を変更しながら一つ一つ確認するのを避けられないかと考えています．

よろしくお願い致します．


CC BY-SA 4.0
2022-01-11T05:51:46.380
85612
2022-01-13T18:13:05.167
2022-01-11T05:59:38.990
9103
9103
question
ios string
iOSが表示する文言を調べる方法はありますでしょうか

[code]

    // 該当のメッセージを出しているBundleを取得
    guard let bundle = Bundle(path: "/System/Library/PrivateFrameworks/TCC.framework") else {
      return
    }
    
    // Bundleに含まれる各言語を取得
    for localization in bundle.localizations {
      print("===== \(localization)")
    
      // 各言語のLocalizable.stringsファイルのパスを取得
      let fileUrl = bundle.url(
        forResource: "Localizable",
        withExtension: "strings",
        subdirectory: nil,
        localization: localization
      )
      guard let fileUrl = fileUrl, let data = try? Data(contentsOf: fileUrl) else {
        continue
      }
    
      // Localizable.stringsをプロパティリストからDictionaryにして
      // 読みやすく出力
      let decoder = PropertyListDecoder()
      guard let plist = try? decoder.decode(Dictionary<String, String>.self, from: data) else {
        continue
      }
      print(plist.keys.map { #""\#($0)" = "\#(plist[$0]!);"# }.joined(separator: "\n"))
    }
    
    
[/code]

上記のコードで、

[code]

    ...
    "REQUEST_ACCESS_SERVICE_kTCCServiceCamera" = "“%@”がカメラへのアクセスを求めています;
    "REQUEST_ACCESS_ALLOW_kTCCServiceFallDetection" = "データを共有;
    "REQUEST_ACCESS_SERVICE_kTCCServiceMediaLibrary" = "“%@”がApple Music、ミュージックとビデオの履歴、およびメディアライブラリにアクセスを求めています;
    "REQUEST_ACCESS_ALLOW_kTCCServicePhotos" = "すべての写真へのアクセスを許可;
    "REQUEST_ACCESS_DENY_kTCCServiceExposureNotification" = "有効にしない;
    "REQUEST_ACCESS_DENY_kTCCServiceFocusStatus" = "許可しない;
    ...
    "REQUEST_ACCESS_ALLOW_kTCCServicePhotos" = "Allow Access to All Photos;
    "REQUEST_ACCESS_DENY" = "Don’t Allow;
    "REQUEST_ACCESS_ALLOW_kTCCServiceFocusStatus" = "OK;
    "REQUEST_ACCESS_SERVICE_kTCCServiceContactsFull" = "“%@” Would Like to Access All Your Contacts Information;
    "REQUEST_ACCESS_LIMITED_kTCCServicePhotos" = "Select Photos…;
    "REQUEST_ACCESS_INFO_SERVICE_kTCCServiceExposureNotification" = "Your iPhone can securely collect and share random IDs with nearby devices. The app can use these IDs to notify you if you may have been exposed to COVID-19. The date, duration and signal strength of an exposure will be shared with “%@”.;
    "REQUEST_ACCESS_INFO_SERVICE_kTCCServiceFallDetection" = "If you choose to share, “%@” can receive data from Apple Watch if a fall is detected and follow-up in case help is needed.;
    "REQUEST_ACCESS_SERVICE_kTCCServiceBluetoothAlways" = "“%@” Would Like to Use Bluetooth;
    "REQUEST_ACCESS_DENY_kTCCServiceFocusStatus" = "Don’t Allow;
    ...
    
[/code]

このような出力が得られます。これでOSが表示する各言語ごとのテキストがわかります。

問題は目的のテキストがどのBundle（＝Framework）に入っているのかというところですが、それは

`/System/Library/Frameworks/`と`/System/Library/PrivateFrameworks/`の中に含まれる各Frameworkに対してLocalizable.stringsがあるかどうかを調べて、すべてのFrameworkの`Localizable.strings`を出力するようなコードを書くと調べられます。

下記のコードは上記のコードに特定のディレクトリ以下のFrameworkをすべて処理するコードを追加したものです。  
このメソッドを

[code]

    dumpStrings(in: "/System/Library/Frameworks")
    dumpStrings(in: "/System/Library/PrivateFrameworks")
    
[/code]

のように呼び出すとOSが出力するほぼすべてのメッセージテキストを調べることができます。  
（すべてのテキストを出力するのは非常に時間がかかるので、日本語だけ、などのように適当な条件でフィルタするとよい。まずは該当のメッセージテキストが格納されているFrameworkがわかればいいので。）

[code]

    func dumpStrings(in frameworkPath: String) {
      let fm = FileManager()
      guard let frameworks = try? fm.contentsOfDirectory(atPath: frameworkPath) else {
        return
      }
      for framework in frameworks {
        guard let bundle = Bundle(path: "\(frameworkPath)/\(framework)") else {
          continue
        }
    
        for localization in bundle.localizations {
          // 非常に時間がかかるのでこのコメントアウトしたコードのように
          // ひとまず日本語の翻訳だけ出力する、のようにするとよい
          // guard localization == "ja" else {
          //   continue
          // }
          guard
            let localizedFiles = try? fm.contentsOfDirectory(
              atPath: "\(bundle.bundlePath)/\(localization).lproj")
          else {
            continue
          }
    
          for localizedFile in localizedFiles {
            guard localizedFile.hasSuffix("strings") else {
              continue
            }
            print("===== \(localization)/\(localizedFile) in \(bundle.bundlePath)")
            let fileUrl = bundle.url(
              forResource: localizedFile,
              withExtension: nil,
              subdirectory: nil,
              localization: localization
            )
            guard let fileUrl = fileUrl, let data = try? Data(contentsOf: fileUrl) else {
              continue
            }
    
            let decoder = PropertyListDecoder()
            guard let plist = try? decoder.decode(Dictionary<String, String>.self, from: data) else {
              continue
            }
            print(plist.keys.map { #""\#($0)" = "\#(plist[$0]!);"# }.joined(separator: "\n"))
          }
        }
      }
    }
    
[/code]


CC BY-SA 4.0
2022-01-13T14:15:08.550
85672
2022-01-13T18:13:05.167
2022-01-13T18:13:05.167
5519
5519
85612
answer