ObjecTips

Swift & Objective-C で iOS とか macOS とか

iOS 13 で AVSpeechSynthesisVoice.currentLanguageCode() の挙動が変更に(多分バグ)

Xcode 11, iOS 12.2, iOS 13.1, iOS 13.1.3 で動作確認


デフォルトの読み上げ音声の言語の確認

AVSpeechSynthesisVoice.currentLanguageCode() 

参考に読み上げ確認の実装
AVSpeechSynthesisVoice を設定せずデフォルト状態での読み上げ)

        let synthesize = AVSpeechSynthesizer()
        let utterance = AVSpeechUtterance(string: "1 2 3")
        // utterance.voice = AVSpeechSynthesisVoice()
        synthesize.speak(utterance)

iOS 12

デバイスの言語設定の優先順序に基づいて LanguageCode が返される。
英語にしか対応していない未ローカライズのアプリでもデバイスの言語設定が 日本語(日本) になっていれば AVSpeechSynthesisVoice.currentLanguageCode()ja-JP を返しデフォルトで日本語で読み上げが行われる。

iOS 13

デバイスの言語設定ではなくアプリの表示言語が反映される様に挙動が変わっている。
例えば英語にしか対応していない未ローカライズではデバイスの言語設定が 日本語(日本) になっていても AVSpeechSynthesisVoice.currentLanguageCode()en-US を返す様になってしまっており、デバイスの設定言語で読み上げされないという現象が発生してしまっている。


日本語設定のデバイスが手元にあれば以下で簡単に挙動を確認できる

  • Xcode のテンプレートからアプリを作成して(ローカライズなど何もせず) AVSpeechSynthesisVoice.currentLanguageCode() を出力する。
  • iOS 12 では ja-JP、iOS 13 では en-US になる。
  • iOS 12 では AVSpeechSynthesizer はデフォルトでデバイス設定の日本語で読み上げする。
  • iOS 13 ではデバイス設定が日本語でもアプリが日本語ローカライズされていないと AVSpeechSynthesizer はデフォルトで英語で読み上げする。
iOS 12 iOS 13
Settings>General>Language & Region>iPhone Language 日本語(日本) 日本語(日本)
Locale.preferredLanguages.first ja-JP ja-JP
AVSpeechSynthesisVoice.currentLanguageCode() ja-JP en-US

f:id:Koze:20191022205827p:plain:w400f:id:Koze:20191022205841p:plain:w400

仕様変更かバグか

公式ドキュメント
currentLanguageCode() - AVSpeechSynthesisVoice | Apple Developer Documentation

Return Value
An NSString object containing the BCP 47 language and locale code for the user’s current locale.

Discussion
This code reflects the user’s language and region preferences as selected in the Settings app.

設定アプリで選択した言語と地域が反映されると書いてある。
よって iOS 13 での挙動は意図しないものなので多分バグ。*1*2

Workaround

iOS 13 では iOS 12 の処理を真似た独自実装で currentLanguageCode を生成して返す様に試みる。

Workaround for that AVSpeechSynthesisVoice current ...

実装内容1

AVSpeechSynthesisVoice の言語コードについては Objective-C ヘッダに以下のコメントの記述がある

Use a BCP-47 language tag to specify the desired language and region.

システムの優先言語設定を取得できる Locale.preferredLanguages の値は zh-Hant-HK の形式でこの場合

  • zhlanguageCode
  • HantscriptCode
  • HKregionCode

となる。
AVSpeechSynthesisVoice.speechVoices() で取得できる利用可能な音声の language の形式は zh-HK となっており scriptCode は存在しない。
このためわざわざ preferredLanguages から一度 Locale を作成して languageCoderegionCode を取り出して再構築するという手順を踏んでいる。

実装内容2

オリジナルの currentLanguageCode の挙動を確認したところ、speechVoices のリストに存在しない言語と地域がデバイスに設定されている時は en-US を返す様になっていたのでそこを踏襲しつつ、現在のデバイス設定の言語コードと地域コードから AVSpeechSynthesisVoice 用の言語コードを作成する様にしている。

例えば uk ウクライナ語、vi ベトナム語などOSで選択可能な言語で且つ読み上げ不可能な言語(speechVoices のリストに存在していない言語)を選択した場合、currentLanguageCodeen-US を返す。
また ja-US など地域が不一致のパターンにおいても en-US を返す。*3


macOS

追試で macOS でも調べてみたところ、Catalina では

  • languageCode はアプリの言語
  • regionCode はシステム環境設定の地域設定

となる様で日本語環境では AVSpeechSynthesisVoice.currentLanguageCodeen-JP を返した。
iOS の様に利用可能な音声リストが無い場合は en-US を返す様な処理が入っていないっぽい。
そして en-JP で読み上げ可能な言語が無いのでデフォルト設定のままでは読み上げされないという現象が起きていた。
よって en-US なり ja-JP なりの利用可能な AVSpeechSynthesisVoice を自分で設定してやる必要がある。*4

ちなみに Mojave ではそもそも AVSpeechSynthesizer AVSpeechSynthesisVoice AVSpeechUtterance あたりは import できてコンパイルもできるけど一切機能しないという問題があるらしいので挙動の検証以前の問題だった。
Mojave では旧来の NSSpeechSynthesizer を使うのが正解っぽい。

更新

Workaround を修正

Workaround for that AVSpeechSynthesisVoice current ...

Localeidentifier には regionCode が含まれていない場合があるので対応した。
具体的には en-US ja-JP 以外に en ja のケースがある。

また、前述のデバイス言語から読み上げ音声への変換の例外(zh-Hans から zh-CN といったケース)に対応するために、一旦 AVSpeechSynthesisVoice を作成して取得される language を戻り値として使用する様に変更した。

*1:仕様が正しくてドキュメントが更新されていないという事も有り得るっちゃ有り得るけど

*2:バグレポートしといた

*3:iOS 12の挙動を確認してみると厳密にはこの処理に当てはまらないケースもある。例えばデバイス設定で簡体字 Locale zh-Hans-US を指定すると currentLanguageCode は zh-CN 返す、ただし繁体字 zh-Hant-US では en-US を返すなどフォールバックの有無の例外が存在する。

*4:Apple 社内で macOS の AVSpeechSynthesizer 周りはあまり検証されていないのではないか、、