iOSで文章の言語を判定する[NSLinguisticTagger]


# はじめに

NSLinguisticTaggerを使って、クライアントだけで、文章・文・節の言語を判定する方法を紹介します。（単語も場合により可能です）

# NSLinguisticTagger

NSLinguisticTaggerを使うと、形態素解析などを行うことができます。（日本語の精度は低いですが）
他の機能の一つとして、言語を判定する事ができます。

```swift
let text = "Rom ist nicht an einem Tag erbaut worden"
let tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagSchemeLanguage], options: 0)
tagger.string = text
let result = tagger.tag(at: 0, scheme: NSLinguisticTagSchemeLanguage, tokenRange: nil, sentenceRange: nil) ?? LanguageDetector.notDetermined

// de
print(result)
```

ここでde、はドイツ語を表す言語コードです。
言語コードは、[BCP-47](https://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0)に準拠した値が返ります。

判定不能な場合は　`"und"` (undetermined) が返されます。

displayNameは、

```swift
// ドイツ語
Locale.current.localizedString(forIdentifier: "de")
Locale.current.localizedString(forLanguageCode: "de")
```

のようにすると取得できます。
IdentifierとLanguageCodeはどちらを使ってもほぼ同じ値が返ってきますが、 `zh-Hans` や `zh-Hant` について、LanguageCodeだと「中国語」、Identifierを使うと「中国語（繁体字）」「中国語（簡体字）」のようになるという違いがあります。

# Gist

こんな感じでラップしました。MITライセンスです(gistにあります)

gist: [ha1f/LanguageDetector.swift](https://gist.github.com/ha1f/356e0ea9c27762873cbdc26f6cf691ce)

```swift:LanguageDetector.swift
import Foundation

struct LanguageDetector {
    
    static let notDetermined = "und"
    
    private let tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagSchemeLanguage], options: 0)
    
    // returns BCP-47 format
    func detect(_ text: String) -> String {
        guard !text.isEmpty else {
            return LanguageDetector.notDetermined
        }
        tagger.string = text
        return tagger.tag(at: 0, scheme: NSLinguisticTagSchemeLanguage, tokenRange: nil, sentenceRange: nil) ?? LanguageDetector.notDetermined
    }
}
```

Swift4版

```swift
public struct LanguageDetector {
    
    static let undetermined = "und"
    
    private let tagger = NSLinguisticTagger(tagSchemes: [.language], options: 0)
    
    // returns BCP-47 format
    func detect(_ text: String) -> String {
        guard !text.isEmpty else {
            return LanguageDetector.undetermined
        }
        tagger.string = text
        return tagger.tag(at: 0, scheme: .language, tokenRange: nil, sentenceRange: nil)?.rawValue ?? LanguageDetector.undetermined
    }
}
```

iOS 11では、dominantLanguageを使うことができます。

```swift
return tagger.dominantLanguage ?? tagger.tag(at: 0, scheme: .language, tokenRange: nil, sentenceRange: nil)?.rawValue ?? LanguageDetector.undetermined
```

# 動作

一文字、一単語などは、たとえひらがななど固有の文字であってもなかなか認識してくれません。
「サーターアンダギー」は正しくjaと判定できますが、「パソコン」はundになります。


逆に判定条件が厳しい分、ちゃんと値が返ってきた場合はだいたい正しい気がします。

以下テストです（タイ語のみ見つからなかったので似たような意味の文です）

```swift:LanguageDetectorTests.swift
func testDetect() {
    let de = "Rom ist nicht an einem Tag erbaut worden"
    let ja = "ローマは一日にして成らず"
    let fr = "Paris ne s'est pas fait en un jour"
    let en = "Rome was not built in a day"
    let it = "Roma non fu fatta in un giorno, Roma non è stata costruita in un giorno"
    let el = "Η Ρώμη δεν χτίστηκε σε μια μέρα"
    let ru = "Москва не сразу строилась"
    let zh_Hant = "羅馬非朝夕建成的"
    let zh_Hans = "罗马不是一日建成的"
    let ko = "로마는 하루아치메 이루어지지 아낟따"
    let th = "และจะมีคุณค่า'มากขึ้น'เมื่อเราทำความดีนั้นอย่างสม่ำเสมอ"
    
    XCTAssertEqual(detector.detect(de), "de")
    XCTAssertEqual(detector.detect(ja), "ja")
    XCTAssertEqual(detector.detect(en), "en")
    XCTAssertEqual(detector.detect(fr), "fr")
    XCTAssertEqual(detector.detect(it), "it")
    XCTAssertEqual(detector.detect(el), "el")
    XCTAssertEqual(detector.detect(ru), "ru")
    XCTAssertEqual(detector.detect(zh_Hant), "zh-Hant")
    XCTAssertEqual(detector.detect(zh_Hans), "zh-Hans")
    XCTAssertEqual(detector.detect(ko), "ko")
    XCTAssertEqual(detector.detect(th), "th")
}

func testDetectUnd() {
    XCTAssertEqual(detector.detect(""), LanguageDetector.notDetermined)
    XCTAssertEqual(detector.detect("a"), LanguageDetector.notDetermined)
    XCTAssertEqual(detector.detect("大"), LanguageDetector.notDetermined)
}
```