iOS Second Stage Advent Calendar 2015 - Qiita 6日目です。
アプリを開発するようになってから、最初にやってしまったローカライズ系の失敗は、数字のケタ数区切りを,(カンマ)固定でコードに埋め込んでしまっていたことでした。
ドイツでは、"ケタ数区切りはカンマではなくピリオドだ" と知って衝撃を受けたのですが、「そいういう常識を知らなかったので...」と言い訳するのはとても苦しい。
どの部分がローカライズを意識しないといけないのか? を最初に事前知識と知っておくのは大事。
そしてiOS SDKでは対応が楽になる機能をたくさん用意してくれているので、ちゃんと使わないともったいない。
ということで、ローカライズ関連のTips集をお送りしようと思います!
"お、これは知らなかったな" というものが一つでもあれば嬉しいな...
日付について
基本
日付表記に地域差があるのはだれもご存知ですよね!
年、月、日の並びがちがったり、カンマがあったりなかったりします。
日付はおなじみのNSDateFormatterを使います。
結果は、iPhone端末の言語設定 と 地域設定に依存します。
年の部分は、和暦カレンダー/西暦カレンダー など地域カレンダーの設定により変わります。
用意されているスタイルは Full, Medium, Shortの3つがありますが、Fullは長すぎで使うタイミングがほとんどないので、Medium, Short みるとこんな感じ。
地域 | MediumStyle | ShortStyle |
---|---|---|
アメリカ | Dec 5, 2015 | 12/5/15 |
イギリス | 5 Dec 2015 | 05/12/2015 |
日本 | 2015/12/05 | 2015/12/05 |
ドイツ | 05.12.2015 | 05.12.15 |
フランス | 5 déc. 2015 | 05/12/2015 |
曜日もいれた日付を表示したい
曜日を追加したい など、フォーマットを変えたい場合は、dateFormatプロパティに任意に指定が可能です。
地域によって正しい表示になるようにdateFormatFromTemplateを使って変換してから、dateFormatに設定します。
let formatter = NSDateFormatter()
formatter.dateFormat = NSDateFormatter.dateFormatFromTemplate("EE MMM d", options: 0, locale: NSLocale.currentLocale())!
print(formatter.stringFromDate(NSDate()))
上記のコードで、以下のようになります。
地域 | EE MMM d |
---|---|
アメリカ | Sat, Dec 5 |
イギリス | Sat, 5 Dec |
日本 | 12月5日(土) |
ドイツ | Sa., 5. Dez. |
フランス | sam. 5 déc. |
曜日は、日本だと後ろにつくけど、他の言語はだいたい前につきます。
この、前につく、後ろにつく ということをiOSでは自動的に判断してフォーマットを用意してくれるので、とっても楽!
今日の日付だったら、"TODAY" と表示したい
NSDateFormatterのdoesRelativeDateFormattingをtrueにすると実現できます。
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
formatter.timeStyle = .NoStyle
formatter.doesRelativeDateFormatting = true
地域 | 2日前 | 1日前 | 今日 | 1日後 | 2日後 |
---|---|---|---|---|---|
アメリカ | Dec 3, 2015 | Yesterday | Today | Tomorrow | Dec 7, 2015 |
イギリス | 3 Dec 2015 | Yesterday | Today | Tomorrow | 7 Dec 2015 |
日本 | 一昨日 | 昨日 | 今日 | 明日 | 明後日 |
ドイツ | Vorgestern | Gestern | Heute | Morgen | Übermorgen |
フランス | avant-hier | hier | aujourd’hui | demain | après-demain |
ただし、dateFormatを使ったフォーマット任意指定する方法との併用はできません。
"2015/11/28~2015/12/05" という期間の表示
日本だと間に "~" を使いますが、他の言語だと "-" を使うのをよく見ます。
"-" は左右にスペース有が正しいのだろうか? 半角ハイフンでいいのか? と迷ったりしますが...
実は迷わなくて大丈夫で、専用にNSDateIntervalFormatterというのが用意されています。
let formatter = NSDateIntervalFormatter()
let fromDate:NSDate = ...
let toDate:NSDate = ...
print(formatter.stringFromDate(fromDate, toDate: toDate))
地域 | 期間が1日以内 | 1日以上 |
---|---|---|
アメリカ | 12/5/15, 12:00 AM - 8:00 PM | 12/5/15, 12:00 AM - 12/12/15, 12:00 AM |
イギリス | 05/12/2015, 00:00 - 20:00 | 05/12/2015, 00:00 - 12/12/2015, 00:00 |
日本 | 2015/12/05 0時00分~20時00分 | 2015/12/05 0:00~2015/12/12 0:00 |
ドイツ | 05.12.15, 00:00–20:00 | 05.12.15, 00:00 – 12.12.15, 00:00 |
フランス | 05/12/2015 00:00 – 20:00 | 05/12/2015 00:00 – 12/12/2015 00:00 |
期間が24時間以内だった場合は、2015/12/05 0時00分~20時05分のように、いい感じにフォーマットを整えてくれます。
NSDateFormatterと同じように、dateStyle, timeStyleなどスタイル指定も可能です。
dateStyle = .MediumStyle, timeStyle = .NoStyle にするとこんな感じに。
地域 | 日付のみの表記 |
---|---|
アメリカ | Dec 5 - 12, 2015 |
イギリス | 5 – 12 Dec 2015 |
日本 | 2015/12/05~2015/12/12 |
ドイツ | 05.12.2015 – 12.12.2015 |
フランス | 5–12 déc. 2015 |
地域によって、ほんと形式が違うものですね。
"1日2時間" という経過時間表示
こちらもFormatterが用意されていて、NSDateComponentsFormatterが使えます。
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
let components = NSDateComponents()
components.day = 1
components.hour = 2
print(formatter.stringFromDateComponents(components)!)
unitsStyleは以下の5種類用意されています。
地域 | Positional | Abbreviated | Short | Full | SpellOut |
---|---|---|---|---|---|
アメリカ | 1d 2 | 1d 2h | 1 day, 2 hr | 1 day, 2 hours | one day, two hours |
イギリス | 1d 2 | 1d 2h | 1 day, 2 hr | 1 day, 2 hours | one day, two hours |
日本 | 1日 2 | 1日2時間 | 1日 2時間 | 1日 2時間 | 一日 二時間 |
ドイツ | 1d 2 | 1d 2h | 1 d, 2 Std. | 1 Tag und 2 Stunden | eins Tag und zwei Stunden |
フランス | 1j 2 | 1j 2h | 1 j et 2 h | 1 jour et 2 heures | un jour et deux heures |
さらに、allowedUnits, maximumUnitCountなどのパラメータで少し制御ができるようになっています。
formatter.allowedUnits = [NSCalendarUnit.Hour , NSCalendarUnit.Minute]
とすると、秒数は無視して切り捨てされた値になります。
formatter.allowedUnits = [NSCalendarUnit.Hour , NSCalendarUnit.Minute, NSCalendarUnit.Second]
とすると、秒数まで表記されるようになり、さらに
formatter.maximumUnitCount = 2
とを追加すると、最大単位2つまで表示するという設定にでき、丸めが入ります。
5:28:41 → 5h 29m
1:00:00 → 1h
0:05:10 → 5m 10s
1:00:05 → 1h 5s
0:00:10 → 10s
数字について
基本
冒頭にかきましたが、ケタ数区切り文字などが地域によって異なります。
NSNumberFormatter を使うのをお勧めします。面倒ですが(笑)
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
print(formatter.stringFromNumber(12345.67)!)
地域 | DecimalStyle | CurrencyStyle |
---|---|---|
アメリカ | 12,345.67 | $12,345.67 |
イギリス | 12,345.67 | £12,345.67 |
日本 | 12.345,67 | ¥12,346 |
ドイツ | 12.345,67 | 12.345,67 € |
フランス | 12 345,67 | 12 345,67 € |
ドイツは、カンマとピリオドの扱いが日本とは反対。フランスはケタ数区切りがスペースになります。
長さの表示
日本ではメートルですが、他の国だったらヤードだったり...いろいろある長さ。
長さの変換にはNSLengthFormatterが使えます。
func stringFromValue(value: Double, unit: NSLengthFormatterUnit) -> String
で、単位を任意に指定できますが、
func stringFromMeters(numberInMeters: Double) -> String
では、メートルから、端末で設定されている地域に合わせた単位に変換してくれます。
let formatter = NSLengthFormatter()
print(formatter.stringFromMeters(100))
地域 | stringFromMeters(100) |
---|---|
アメリカ | 109.36 yd |
イギリス | 100 m |
日本 | 100 m |
ドイツ | 100 m |
フランス | 100 m |
他に、ジュール/カロリーなどの表記のための NSEnergyFormatter
kg/lbなどの重量表記のための NSMassFormatter もあります。
余談ですが、HealthKitフレームワークではHKUnitという単位オブジェクトを用いて、いろんな単位に変換できる仕組みがあります。
HealthKitを使用するためには、entitlementsに設定が必要で、PrivacyPolicyもしっかりしないといけないので、単位の変換のためだけに使用するものではありませんが、HKUnitのソースコードのコメントに
// [Mass]
// oz (ounces) = 28.3495 g
// lb (pounds) = 453.592 g
// st (stones) = 6350.0 g
//
// [Length]
// in (inches) = 0.0254 m
// ft (feet) = 0.3048 m
// mi (miles) = 1609.34 m
..のように各単位での値が載っていますので、知りたくなったら覗いてみるといいかもです!
その他
地域に差がある項目を取得する
地域ごとの内容はNSLocaleに基本的に入っています。
objectForKeyを使って、以下の情報などを得ることができます。
NSLocaleGroupingSeparator : ケタ数区切り文字
NSLocaleDecimalSeparator : 小数点区切り文字
NSLocaleQuotationBeginDelimiterKey : Quotesの開始区切り文字
NSLocaleQuotationEndDelimiterKey : Quotesの終了区切り文字
使用できるKeyの一覧は、NSLocaleのComponentKeysを参照してください。
試しにQuoteを抜き出してみると...
let locale = NSLocale.currentLocale()
let startQuote = locale.objectForKey(NSLocaleQuotationBeginDelimiterKey)!
let endQuote = locale.objectForKey(NSLocaleQuotationEndDelimiterKey)!
print("\(startQuote)iOS\(endQuote)")
地域 | |
---|---|
アメリカ | “iOS” |
イギリス | “iOS” |
日本 | 「iOS」 |
ドイツ | „iOS“ |
フランス | «iOS» |
ドイツ、フランス...そ、そうなんだ!という感じです(笑
localized.stringに設定がないものをチェックする
XCodeのScheme設定に、
という設定があります。
localized.stringにないものをNSLocalizedStringで使った時に、コンソールに警告を表示出すことができます。
出力される警告メッセージ例
Localizable string "aaa" not found in strings table "Localizable" of bundle CFBundle 0x7f828bf019d0
Simulatorで言語、Localeを簡単に設定する
XCodeのScheme設定で、"Application Language", "Application Region"の設定ができます。
チェックが必要な言語,地域を別々にScheme設定しておくと便利です。
"Application Language"で選択できる言語は、プロジェクトのLocalizationに設定している言語のみのようです。(これは少し残念)
Storyboard上で表示言語を切り替える
StorybardのPreviewの表示で言語を切り替えることができます。
XCodeのAssistantEditorからStoryboardのPreviewを表示します。
右下に言語切替のメニューが表示されるので、この切替で各言語での配置を確認できます。
※ FitPortの画面を拝借
Localized.stringに設定されている値を言語指定で取得する
2016/05/01 追記
//英語
let enPath = NSBundle.mainBundle().pathForResource("en", ofType: "lproj")!
let enValue = NSLocalizedString(value, tableName: nil, bundle: NSBundle(path:enPath)! , value: "", comment: "")
//日本語
let jaPath = NSBundle.mainBundle().pathForResource("ja", ofType: "lproj")!
let jaValue = NSLocalizedString(value, tableName: nil, bundle: NSBundle(path:jaPath)! , value: "", comment: "")
最後に...
世界中のStoreにアプリを出すと、いろんな国の人からフィードバックがあって嬉しいです。
比較的ですが...日本人は厳しくてたまに辛くなるけど、他の国の人は優しいです。
あなたのアプリが大好き!ってだけのフィードバックもあったりしてほんと、嬉しいです。
うまくiOSの機能を使いつつ世界にアプリを発信していきましょう〜!