繁体字と簡体字と日本語を区別する

# 前提
- 繁体字と簡体字と日本語、それぞれ用のフォントがある。
- 元の文章の言語はわからない。
- でも適切なフォントを割り当てないといけない。
- 見た目が変じゃなければオッケー（じゅうよう）

という状況のときにうまくやる方法。

## 繁体字とは
むかしっからある中国の文字（雑ｗ）
（くわしくは [繁体字 \- Wikipedia](https://ja.wikipedia.org/wiki/%E7%B9%81%E4%BD%93%E5%AD%97) をどうぞ）

## 簡体字とは
↑繁体字むずいから簡単にしたやつ（雑ｗ）（[簡体字 \- Wikipedia](https://ja.wikipedia.org/wiki/%E7%B0%A1%E4%BD%93%E5%AD%97)）
でも全部の繁体字が簡化されてるわけじゃない（←じゅうよう）

## 日本の漢字との関係
>中国語の学習には繁体字も簡体字も欠かすことができない - GIGAZINE
https://gigazine.net/news/20171022-chinese-traditional-simplified-characters/

>| 日本語 | 繁体字 | 簡体字 | |
|:--:|:--:|:--:|:--:|
| 図 | 圖 | 图 | 三者三様 |
| 作 | 作 | 作 | 三者同一 |
| 黒 | 黑 | 黑 | 日本語だけ違う |
| 痴 | 癡 | 痴 | 繁体字だけ違う |
| 義 | 義 | 义 | 簡体字だけ違う |

しかも

>| 日本語 | 繁体字 | 簡体字 |
|:--:|:--:|:--:|
| 瀋 | 瀋 | **沈** |
| **沈** | 沉 | 沉 |

**沈** は言語間で対応してない。


## Unicode における漢字
[CJK統合漢字 \- Wikipedia](https://ja.wikipedia.org/wiki/CJK%E7%B5%B1%E5%90%88%E6%BC%A2%E5%AD%97) っていうやつで、要するに「中国語、日本語、朝鮮語で使われている漢字をひとまとめにした」やつ。同じみための漢字は言語問わずおなじコードポイント。ここからここまでのコードが日本語で、ここからここまでのコードが繁体字で、みたいなことにはなってない。↑の表の「作」はどの言語の文章にでてきても [U\+4F5C](http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=4F5C) というコード。


# 判別するには

1. 日本語にしかない文字（ひらがな・カタカナ）が含まれてたら → もちろん日本語
1. 繁体字にしかない文字が含まれてたら → もちろん繁体字（↑の表の「繁体字だけ違う」パターン）
1. 簡体字にしかない文字が含まれてたら → もちろん簡体字（↑の表の「簡体字だけ違う」パターン）


## Unihan_Variants.txt

繁体字・簡体字にしかない文字のデータベースどっかにないの？ → あります。

>cjk - Simplified Chinese Unicode table - Stack Overflow https://stackoverflow.com/questions/4596576/simplified-chinese-unicode-table/4596760#4596760

[Unihan Database](http://unicode.org/charts/unihan.html) っていう漢字データベースの奥の方にある [`Unihan.zip`](https://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip) のなかにある `Unihan_Variants.txt` というやつが異体字のリストになってて、繁体字←→簡体字のペアもここに書いてある。

```
U+3469	kTraditionalVariant	U+5138
U+346F	kSimplifiedVariant	U+3454
U+3473	kSimplifiedVariant	U+3447
U+3487	kSemanticVariant	U+511B<kMatthews
```

こんな感じになっていて、`U+3469` の繁体字バージョンは `U+5138` ですよ、と。

つーことで、↓みたいなスクリプトを書いて抽出すると、繁体字・簡体字にしかない文字リストができあがる。

```python
# filter = 'kTraditionalVariant'
filter = 'kSimplifiedVariant' # 簡体字版がある文字＝繁体字

codes = []
with open('Unihan_Variants.txt') as f:
    for line in f:
        line = line.strip()
        tokens = line.split('\t')
        if len(tokens) < 3:
            continue
        if tokens[1] == filter and tokens[0] != tokens[2]:
            codes.append(tokens[0].replace('U+', '\u'))
print('|'.join(codes))
```

最後の `print` は C# で使う正規表現用。


## ようやく判定用のコード

```csharp
using System.Text.RegularExpressions;


public class LanguageDetector
{

    public static string Detect(string text)
    {
        // ほんとはここでひらがなカタカナだけじゃなくて、日本語にしか使われてない漢字も含めたほうが精度あがる
        if (JapaneseKana.IsMatch(text))
        {
            return "ja";
        }

        var sc = SimplifiedChinese.IsMatch(text);
        var tc = TraditionalChinese.IsMatch(text);
        if (sc && tc)
        {
            // たまに繁体字・簡体字両方含んでるやつがあるので、そのときは多い方にする
            var scount = 0;
            var tcount = 0;
            var n = System.Math.Min(text.Length, 100);
            for (var i = 0; i < n; i++)
            {
                var t = text.Substring(i, 1);
                if (SimplifiedChinese.IsMatch(t))
                {
                    scount++;
                }
                else if (TraditionalChinese.IsMatch(t))
                {
                    tcount++;
                }
            }
            if (scount > tcount)
            {
                return "zh-cn";
            }
            else
            {
                return "zh-tw";
            }
        }
        else if (sc)
        {
            return "zh-cn";
        }
        else if (tc)
        {
            return "zh-tw";
        }

        // ここに来た時点ではまだ日本語・繁体字・簡体字どれも可能性あるんだけど、
        // どれにも含まれる漢字しか使われてないので、日本語フォントでも問題なく表示される。
        if (Kanji.IsMatch(text))
        {
            return "ja";
        }

        return "en";
    }


    static Regex JapaneseKana = new Regex(@"\p{IsHiragana}|\p{IsKatakana}");
    static Regex Kanji = new Regex(@"\p{IsCJKUnifiedIdeographs}");
    // ↓Pythonスクリプトで生成したやつをコピペ
    static Regex SimplifiedChinese = new Regex(@"\u343D|\u3447|\u3448...{省略}...\u2B6F6|\u2B6F8|\u2C88D");
    static Regex TraditionalChinese = new Regex(@"\u346F|\u3473|\u3493...{省略}...\u2A535|\u2A600|\u2A62F");

}
```

つー感じ。すげえでっかい正規表現になっているのでパフォーマンスが多少気にはなるのだけど、まー、たぶんだいじょぶ。