C#
中国語
Chinese

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

前提

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

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

繁体字とは

むかしっからある中国の文字(雑w)
(くわしくは 繁体字 - Wikipedia をどうぞ)

簡体字とは

↑繁体字むずいから簡単にしたやつ(雑w)(簡体字 - Wikipedia
でも全部の繁体字が簡化されてるわけじゃない(←じゅうよう)

日本の漢字との関係

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

日本語 繁体字 簡体字
三者三様
三者同一
日本語だけ違う
繁体字だけ違う
簡体字だけ違う

しかも

日本語 繁体字 簡体字

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

Unicode における漢字

CJK統合漢字 - Wikipedia っていうやつで、要するに「中国語、日本語、朝鮮語で使われている漢字をひとまとめにした」やつ。同じみための漢字は言語問わずおなじコードポイント。ここからここまでのコードが日本語で、ここからここまでのコードが繁体字で、みたいなことにはなってない。↑の表の「作」はどの言語の文章にでてきても U+4F5C というコード。

判別するには

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

Unihan_Variants.txt

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

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

Unihan Database っていう漢字データベースの奥の方にある 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 ですよ、と。

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

# 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# で使う正規表現用。

ようやく判定用のコード

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");

}

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