知っておくと便利なローカライズ関連Tips

[iOS Second Stage Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/ios-2) ６日目です。

アプリを開発するようになってから、最初にやってしまったローカライズ系の失敗は、数字のケタ数区切りを,(カンマ)固定でコードに埋め込んでしまっていたことでした。

ドイツでは、"ケタ数区切りはカンマではなくピリオドだ" と知って衝撃を受けたのですが、「そいういう常識を知らなかったので...」と言い訳するのはとても苦しい。

どの部分がローカライズを意識しないといけないのか？ を最初に事前知識と知っておくのは大事。
そしてiOS SDKでは対応が楽になる機能をたくさん用意してくれているので、ちゃんと使わないともったいない。

ということで、ローカライズ関連のTips集をお送りしようと思います！
"お、これは知らなかったな" というものが一つでもあれば嬉しいな...

# 日付について

## 基本

日付表記に地域差があるのはだれもご存知ですよね！
年、月、日の並びがちがったり、カンマがあったりなかったりします。

日付はおなじみの[NSDateFormatter](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/)を使います。
結果は、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に設定します。

```swift

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](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/)のdoesRelativeDateFormattingをtrueにすると実現できます。

```swift

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](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSDateIntervalFormatter_class/)というのが用意されています。

```swift

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](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSDateComponentsFormatter_class/)が使えます。

```swift

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](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/) を使うのをお勧めします。面倒ですが(笑) 

```swift

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](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSLengthFormatter_Class/)が使えます。

func stringFromValue(value: Double, unit: NSLengthFormatterUnit) -> String
で、単位を任意に指定できますが、
func stringFromMeters(numberInMeters: Double) -> String
では、メートルから、端末で設定されている地域に合わせた単位に変換してくれます。

```swift

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](https://developer.apple.com/library/prerelease/ios/documentation/HealthKit/Reference/HKUnit_Class/)という単位オブジェクトを用いて、いろんな単位に変換できる仕組みがあります。

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](https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSLocale_Class/#//apple_ref/doc/constant_group/NSLocale_Component_Keys)を参照してください。

試しにQuoteを抜き出してみると...

```swift

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設定に、

![Screen Shot 2015-12-05 at 9.55.05 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1380/3c6e2f9d-a236-010a-bacb-59dc2fb7360d.png)

という設定があります。
localized.stringにないものをNSLocalizedStringで使った時に、コンソールに警告を表示出すことができます。

出力される警告メッセージ例

> Localizable string "aaa" not found in strings table "Localizable" of bundle CFBundle 0x7f828bf019d0


## Simulatorで言語、Localeを簡単に設定する

XCodeのScheme設定で、"Application Language", "Application Region"の設定ができます。

![Screen Shot 2015-12-05 at 9.59.06 PM.png](https://qiita-image-store.s3.amazonaws.com/0/1380/3aa3e774-fe84-31a1-560c-045ed9b3be93.png)

チェックが必要な言語,地域を別々にScheme設定しておくと便利です。
"Application Language"で選択できる言語は、プロジェクトのLocalizationに設定している言語のみのようです。(これは少し残念)


## Storyboard上で表示言語を切り替える

StorybardのPreviewの表示で言語を切り替えることができます。
XCodeのAssistantEditorからStoryboardのPreviewを表示します。

![storyboard.png](https://qiita-image-store.s3.amazonaws.com/0/1380/4c286473-4c80-977d-da1b-85bf793135a2.png)

右下に言語切替のメニューが表示されるので、この切替で各言語での配置を確認できます。
※ [FitPort](https://itunes.apple.com/jp/app/fitport-herusu-fittonesudasshubodo/id914413310)の画面を拝借

## Localized.stringに設定されている値を言語指定で取得する

2016/05/01 追記

```swift
//英語
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の機能を使いつつ世界にアプリを発信していきましょう〜！