iOSの言語選択画面(設定アプリ)のクローン作成で苦労した点

## はじめに
本来はDeepLクライアントをiOS+SwiftUIで作っていたのですが、
翻訳先の言語選択を行うUIを考えていたときに「設定アプリのクローン」を作ったら面白いんじゃないかと思い立ってしまいました。
しかしながら、選択肢の言語リストを表示するだけで引っかかってしまったので共有したいと思います。


## 技術検討
設定アプリの言語選択画面を観察してみます。
各言語のリストは以下のように上下２行で構成されています。
1. 各言語名をその言語を使って(母国語で)表記。 (例：Deutsch)
1. 各言語名を端末の設定言語で表記。 (例：ドイツ語)
![0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/4c0bd296-2dde-cbd6-990e-a915a9c4f816.png)

### 指針
```Locale```を使用すれば、知らない言語や文字であってもほぼ対応できます。
```Localizable.strings```で頑張らなくても大丈夫です。:sunglasses:


### １行目
目的の言語の```Locale識別子```("JA"など)で```Locale```を作成し、・・・(1)
**同じ識別子で**```localizedString```を取得します。・・・(2)
```Swift
let identifier = "JA"
let lang = Locale(identifier: identifier) // (1)
             .localizedString(forIdentifier: identifier)  // (2)
print(lang)  // "日本語"
```

### ２行目
環境変数から現在の```Locale```を取得します。・・・(1)
その```Locale```に対して、**目的の言語の** ```Local識別子```で```localizedString```を取得します。・・・(2)
```Swift
@Environment(\.locale) var locale  // (1) 日本語設定と仮定
let identifier = "ES"  // スペイン語
let lang = locale.localizedString(forIdentifier: identifier)  // (2)
print(lang)  // "スペイン語"
```

## 作ってみた
| 自作アプリ |  設定アプリ |
|:----------:|:------------:|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/6aaa8b07-06ae-d998-caa9-fdf950f47ac6.png" width=250> |<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/025b791d-6524-6135-f773-e7c9d76b958c.png" width=250> |

うまくいったように見えますが、iOSの設定アプリと比較してみると、
ところどころ「おや？:rolling_eyes:」と思うところがあります。

１つ１つ見てみましょう。

### その１　頭文字が小文字の場合がある
頭文字が大文字だったり小文字だったりで統一感がありません。

**対応策**
```localizedString(forIdentifier:)```で取得した文字列に```.capitalized```を付けることで頭文字が大文字になります。(英語以外の文字でも！:relaxed:)
```Swift
@Environment(\.locale) var locale  // es_ES と仮定
let identifier = "ES"  // スペイン語
let lang = locale.localizedString(forIdentifier: identifier)
                 .capitalized  // 頭文字を大文字にする
print(lang)  // "Español"
```

### その２　言語の並び順が異なっている
設定アプリは、端末の「言語_地域」がリストの先頭になります。
(ja_US=日本語が先頭、en_GB=English(UK)が先頭)
残りは"地域"毎にApple社のマーケティングに基づいた順番になっているようです。
("地域"を変更すると言語の順番も変わる)
予想通りなら、めったに使わない地味な機能なのにすごい努力ですね。:clap:

**対応策**
"地域"を変更すると言語の順番も変わりますので単純なソートでは対応できません。
"地域"毎の言語の順番はApple社しか知りませんので、```enum```の定義をそれっぽい順序にしておくしかなさそうです。

現在のLocaleの言語を先頭にするためのコードを追加しました。
```Swift
import SwiftUI

enum TargetLang: String, CaseIterable {
    /// English (British)  英語 (イギリス)
    case EN_GB = "EN-GB"
    /// English (American)  英語 (アメリカ)
    case EN_US = "EN-US"
    /// Chinese (simplified)  中国語 (簡体字)
    case ZH
    /// Japanese  日本語
    case JA
    /// Spanish  スペイン語
    case ES
    /// French  フランス語
    case FR
}

@Environment(\.locale) var locale

/// 現在のLocaleの言語が先頭になるように並べ替える.
func getSortedLanguages() -> [TargetLang] {

    var languages: [TargetLang] = []
    let currentLocale = locale as NSLocale
    let currentLanguageCode = currentLocale.languageCode
    let currentCountryCode  = currentLocale.countryCode

    for lang in TargetLang.allCases {
        let aLocale = NSLocale(localeIdentifier: lang.rawValue)
        let aLanguageCode = aLocale.languageCode
        let aCountryCode  = aLocale.countryCode

        if aLanguageCode != currentLanguageCode {
            languages.append(lang)
            continue
        }

        if let aCountryCode, aCountryCode != currentCountryCode {
            languages.append(lang)
        } else {
            languages.insert(lang, at: 0)
        }
    }
    return languages
}
```


### その３　中国語の文言が異なっている
| 自作アプリ |  設定アプリ |
|:----------|:------------|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/597f3f68-d549-a268-0ae2-fd39f0d3173f.png" width=180>|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/1bf1943a-9e11-525a-4049-7eeda0fea3a8.png" width=180>|
|１行目：中文<br>２行目：中国語 |１行目：簡体中文<br>２行目：簡体中国語|

**対応策**
指定している```Local識別子```が```ZH```なのが原因でした。
**簡体**を明示するためには```ZH-HANS```を指定する必要がありました。

入力された```Local識別子```が```ZH```だったら```ZH-HANS```に置換する方法を採用しました。

### その４　English の (国名)が省略形ではない
| 自作アプリ |  設定アプリ |
|:----------|:------------|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/3022f994-7fa9-3f60-9bf3-1951769ae779.png" width=180>|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/20f0cd54-61ee-15b1-e03a-541af075bcc5.png" width=180> |
| (United Kingdom)<br>(United States)| (UK)<br>(US) |

Localeが"en"の時：
```localizedString(forIdentifier: "en_GB")```は「English (United Kingdom)」を、
```localizedString(forIdentifier: "en_US")```は「English (United States)」を返します。
設定アプリはそれぞれ「English (UK)」「English (US)」になっています。

**対応策**
```NSLocale```の```countryCode```が使えると思ったのですが、
イギリスは"**Great Britain**"なので、```countryCode```は"**GB**"なのです。:cry:
イギリスのLocale情報から"**UK**"を取得する方法がわかりませんでした。

また、英語で国名が省略形なのはアメリカとイギリスだけで、オーストラリアなどはフルネームなので単純に```countryCode```を使えば良いというわけでもありません。



不本意ながら、特定の```Local識別子```が入力されたら特定の文字列を出力する方法を採用しました。
| 入力 | 出力 |
|:----|:----|
| EN-GB | "English (UK)" |
| EN-US | "English (US)" |

### その５　"アメリカ"が"アメリカ合衆国"で表示されている
Localeが"ja"の時```localizedString(forIdentifier: "en_US")```は
「英語 (アメリカ合衆国)」を返します。
設定アプリは「英語 (アメリカ)」になっています。

**対応策**
アメリカのLocale情報から"**アメリカ**"を取得する方法がわかりませんでした。:cry:
不本意ながら、入力された```Local識別子```が```EN-US```だったら"**アメリカ合衆国**"を"**アメリカ**"に置換する方法を採用しました。



### その６　ブルガリア語が"伝統式フォント"で表示されている
| 自作アプリ |  設定アプリ |
|:----------|:------------|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/18e16468-a3d2-63f1-a520-48b8495970fb.png" width=180>|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/bccbfb37-a761-ca91-4360-4705e7270ba1.png" width=180> |
| 伝統式フォント| 現代式フォント |

ブルガリア語の表記には```伝統式フォント```と```現代式フォント```の２種類があります。[^1]
設定アプリは```現代式フォント```になっています。

**対応策**
ブルガリアの人、すみません。:bow_tone1:
フォント設定で解決するんでしょうか？？:fearful:
フォントが違うとどれ位のインパクトがあるのか? すらわからず。。
（日本語の ゐ・ゑ・ヰ・ヱ みたいなもの？）

不本意ながら、このままにしておくことにしました。


### 対策してみた
| 自作アプリ (ja_JP) | 自作アプリ (en_US) |  設定アプリ |
|:----------:|:------------:|:------------:|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/dcd37714-541e-6e9a-65a3-366cce2f857f.png" width=250> |<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/93643870-b87c-a5e0-cd8e-0c410611b6fa.png" width=250> |<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/95033/025b791d-6524-6135-f773-e7c9d76b958c.png" width=250> |


力業の対策ばかりなのがアレですが、かなり近くなった！:tada:
検索窓は気が向いたら考える。。
もっといい方法があったら教えてください。:pray:

## Playgorundでlocaleを検討する方法
Playgroundで検討する際には以下の記事を参考にしました。

https://qiita.com/mono0926/items/89ffb537b8eefaa02902

↑を**Swift 5**にアレンジしたコードを載せます。
```Swift
import Foundation

extension NSLocale {
    @objc
    class var swizzled_currentLocale: NSLocale {
        NSLocale(localeIdentifier: "fr") // ここは適当に変えましょう
    }
}

let method = class_getClassMethod(NSLocale.self, #selector(getter: NSLocale.current))
let swizzledMethod = class_getClassMethod(NSLocale.self, #selector(getter: NSLocale.swizzled_currentLocale))
method_exchangeImplementations(method!, swizzledMethod!)
print(NSLocale.current.identifier) // -> "fr"
```



## 開発環境
Xcode 14.2
iOS 16.2

GitHubにソースコードを上げたら更新します。

[^1]: [ブルガリア語アルファベット　(Wikipedia)](https://ja.wikipedia.org/wiki/ブルガリア語アルファベット)