NumberFormatterで数値を国際化

NumberFormatterで数値を国際化
Photo by Mailchimp / Unsplash

個人アプリで風水アプリを作っています。佐藤タケシです。

今このアプリの国際化対応をしている最中です。

玄空飛星風水は端末の方位磁石の値を読み取り、風水を判定してくれるアプリです。

方位をアプリで表示

こちらのキャプチャのように画面に方位磁石の値を表示しています。
値は小数点第一位まで表示しています。
この値も国際化が必要と気づいたので、そのやり方をご紹介します。

検証環境

  • Xcode 14.1
  • Swift 5.7.1
  • iOS 16.0
    • ただし、この記事で紹介するNumberFormatterはiOS 2から利用できるものです。

小数点の国際化

数値に国際化なんて必要なの?と疑問に思った方もいるかと思います。
アラビア数字を使っている限りどの国でも通用するのでは?と思う人もいるでしょう。

今回は小数点に着目しますが、実は小数点に「.(ピリオド)」を表記するのはどの国でも行われているものではありません。

世界を見渡すと、小数点の記号は主に3つの表記があります。

  • イギリス式
    • 小数点に「.(ピリオド)」を使う
    • 例: 123.456
      • 百二十三点四五六を表す
    • 主な採用国
      • オーストラリア、香港、インド、日本、韓国、マレーシア、中国、フィリピン、シンガポール、台湾、タイ、イギリス、米国
  • フランス式
    • 小数点に「,(コンマ)」を使う
    • 例: 123,456
      • 百二十三点四五六を表す
      • 整数部の3桁ごとの区切りと紛らわしい
    • 主な採用国
      • オーストリア、アゼルバイジャン、ベラルーシ、ベルギー、ブラジル、ブルガリア、カメルーン、カナダ(フランス語を使う場合)、チリ、コロンビア、フィンランド、フランス、ドイツ、ギリシャ、ハンガリー、インドネシア、イタリア、マカオ(ポルトガル語を使う場合)、モンゴル、オランダ、ノルウェー、ポーランド、ポルトガル、ルーマニア、ロシア、セルビア、スロバキア、南アフリカ(公式。ただしビジネス上はピリオドが一般的)スペイン、スウェーデン、チュニジア、トルコ、ウクライナ、ウルグアイ、ベネズエラ、ベトナム
      • 国数としては多いですね
  • Momayyez
    • アラビア語圏で使われるMomayyezという記号を使う。「 ٫ 」 (U+066B)
    • 例:999.99は۹۹۹٫۹۹と表す
    • 主な採用国
      • バーレーン、イラン、イラク、クウェート、オマーン、カタール、サウジアラビア、シリア、UAE

詳しくはwikipedia 小数点を参照してください。

NumberFormatterを使って数値の国際化

数値を国際化する場合、FoundationNumberFormatterクラスを利用するとうまく表現できます。
試しに、「小数点第一位までの数値を各言語で表示する」コードはこのように書きます。

// インスタンス化
var numberFormatter = NumberFormatter()

// 数値のスタイル。decimalは小数点を表す
numberFormatter.numberStyle = .decimal

// localの設定。日本を指定
numberFormatter.locale = Locale(identifier: "ja_JP")

// 最低限の小数点の桁数。1を指定して小数点第一位まで表示
numberFormatter.minimumFractionDigits = 1

// 最大限の小数点の桁数。1を指定して小数点第一位まで表示
numberFormatter.maximumFractionDigits = 1

// 360.0という数値をフォーマット
var f = numberFormatter.string(from: 360.0)
print(f) // Optional("360.0")

// フランスを指定
numberFormatter.locale = Locale(identifier: "fr_FR")
f = numberFormatter.string(from: 360.0)
print(f) // Optional("360,0")

// サウジアラビアを指定
numberFormatter.locale = Locale(identifier: "ar-SA")
f = numberFormatter.string(from: 360.0)
print(f) // Optional("٣٦٠٫٠")
  • NumberFormatterのプロパティ
    • numberStyle: 数値スタイル
      • decimalは小数点を表す。その他にcurrencyの通貨やpercentのパーセント表示、scientificの科学表記、spellOutのアラビア数字ではなくその地域の言語表記などがある
      • 公式ドキュメントにどの地域がどう表示されるか説明がありました
    • locale: 地域を指定
    • minimumFractionDigits: 最低限表示するの小数点の桁数
      • 指定された数値よりも桁数が足りない場合は0で埋められる
    • maximumFractionDigits: 最大限表示する小数点の桁数
      • 指定された数値よりも桁数が多い場合に切られる

minimumFractionDigitsの例

var numberFormatter = NumberFormatter()
numberFormatter.minimumFractionDigits = 5
numberFormatter.string(from: 123.456) // 123.45600

maximumFractionDigitsの例

var numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 3
numberFormatter.string(from: 123.456789) // 123.457

string(from: ) メソッドに変換したい数値を入れるとフォーマットされたStringが手に入ります。

実際のコード

今回のアプリの要件はこのようになります。

  • 入力される数値はDouble
  • 入力値の範囲は 0.0〜360.0
    • 方位の値なので。負の値は取らない
  • 入力される数値の小数点に制限なし
  • Localeによって数値を表現する文字列を変える
  • 表示する文字列の桁数は小数点第一位まで表示
  • 数値のスタイルで表示

使い勝手を考えて、今回はDouble型のextensionを作ることにしました。

import Foundation
extension Double {
    func formatted(locale: Locale) -> String? {
        let numberFormatter = NumberFormatter()
        numberFormatter.locale = locale
        numberFormatter.minimumFractionDigits = 1
        numberFormatter.maximumFractionDigits = 1
        numberFormatter.numberStyle = .decimal
        numberFormatter.roundingMode = .halfUp
        return numberFormatter.string(from: self as NSNumber)
    }
}

numberFormatterminimumFractionDigitsmaximumFractionDigitsをどちらも1を指定することで、表示する文字列は小数点1桁まで表示を表しています。
新しくroundingModeを追加しました。
表示しない小数点以下(この場合は小数点2桁以降)をどのように丸めるかの指定です。
halfUpで四捨五入の挙動になるようにしています。

string(from:) メソッドの引数はNSNumber型が必要なのでasでキャストしています。

テストも書いてみました。

func testDoubleFormatted() {
    let jpFormatted = 123.456789.formatted(locale: Locale(identifier: "ja_JP"))
    XCTAssertEqual(jpFormatted, "123.5")

    let frFormatted = 123.456789.formatted(locale: Locale(identifier: "fr_FR"))
    XCTAssertEqual(frFormatted, "123,5")

    let arabiaFormatted = 123.456789.formatted(locale: Locale(identifier: "ar_SA"))
    XCTAssertEqual(arabiaFormatted, "١٢٣٫٥")
        
    let halfup = 123.123.formatted(locale: Locale(identifier: "ja_JP"))
    XCTAssertEqual(halfup, "123.1")
}

まとめ

国際化する際、小数点にまで意識がいかないかもしれません。
しかし、世界には小数点の表示に「.(ピリオド)」を使わない国もいます。
今回はNumberFormatterを使って小数点第一位まで表示するコードを紹介しました。
皆さんも国際化する際はぜひこの記事を参考にしてください。

参照

宣伝

BOOTHより、同人版「Swift Concurrency入門」発売中です。
Swift Concurrencyを網羅的に学べ、さらに既存アプリへの適応方法も解説しています。
日本語で体系的に学べる解説本は他になかなかありません。
1章、2章が立ち読みできるおためし版もありますので、ぜひチェックしてください!



Swift Concurrency入門

https://personal-factory.booth.pm/items/3888109