1
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-06

はじめに

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

技術検討

設定アプリの言語選択画面を観察してみます。
各言語のリストは以下のように上下2行で構成されています。

  1. 各言語名をその言語を使って(母国語で)表記。 (例:Deutsch)
  2. 各言語名を端末の設定言語で表記。 (例:ドイツ語)
    0001.png

指針

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

1行目

目的の言語のLocale識別子("JA"など)でLocaleを作成し、・・・(1)
同じ識別子でlocalizedStringを取得します。・・・(2)

let identifier = "JA"
let lang = Locale(identifier: identifier) // (1)
             .localizedString(forIdentifier: identifier)  // (2)
print(lang)  // "日本語"

2行目

環境変数から現在のLocaleを取得します。・・・(1)
そのLocaleに対して、目的の言語の Local識別子localizedStringを取得します。・・・(2)

@Environment(\.locale) var locale  // (1) 日本語設定と仮定
let identifier = "ES"  // スペイン語
let lang = locale.localizedString(forIdentifier: identifier)  // (2)
print(lang)  // "スペイン語"

作ってみた

自作アプリ 設定アプリ

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

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

その1 頭文字が小文字の場合がある

頭文字が大文字だったり小文字だったりで統一感がありません。

対応策
localizedString(forIdentifier:)で取得した文字列に.capitalizedを付けることで頭文字が大文字になります。(英語以外の文字でも!:relaxed:)

@Environment(\.locale) var locale  // es_ES と仮定
let identifier = "ES"  // スペイン語
let lang = locale.localizedString(forIdentifier: identifier)
                 .capitalized  // 頭文字を大文字にする
print(lang)  // "Español"

その2 言語の並び順が異なっている

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

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

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

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
}

その3 中国語の文言が異なっている

自作アプリ 設定アプリ
1行目:中文
2行目:中国語
1行目:簡体中文
2行目:簡体中国語

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

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

その4 English の (国名)が省略形ではない

自作アプリ 設定アプリ
(United Kingdom)
(United States)
(UK)
(US)

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

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

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

不本意ながら、特定のLocal識別子が入力されたら特定の文字列を出力する方法を採用しました。

入力 出力
EN-GB "English (UK)"
EN-US "English (US)"

その5 "アメリカ"が"アメリカ合衆国"で表示されている

Localeが"ja"の時localizedString(forIdentifier: "en_US")
「英語 (アメリカ合衆国)」を返します。
設定アプリは「英語 (アメリカ)」になっています。

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

その6 ブルガリア語が"伝統式フォント"で表示されている

自作アプリ 設定アプリ
伝統式フォント 現代式フォント

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

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

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

対策してみた

自作アプリ (ja_JP) 自作アプリ (en_US) 設定アプリ

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

Playgorundでlocaleを検討する方法

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

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

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)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0