在 Android 中区分 CJK 语言(中文、日语、韩语)

Differentiating CJK languages (Chinese, Japanese, Korean) in Android

我希望能够识别中文、日文和韩文的书面字符,既可以作为一般语言,也可以作为细分语言。这些是原因:

在语言方面,我知道的子类别是

为了完整起见,Vietnamese (so CJK is also called CJKV). For my current purposes I don't need to worry about it, but it could be a future consideration. I am also ignoring romanized scripts like Chinese pinyin or Japanese romaji. They will be handled the same as English and Mongolian in the TextView (ie, rotated 90 degrees with the rest of the line). Bopomofo used in Taiwan could also be a future consideration, but I will ignore it for now. See also here and here中也使用了汉字作为语言示例。

我已经看到许多相关问题通常涉及 Java 或 Android 中的一种特定语言,但没有包含规范答案的总体问题。其他问题对 Unicode 更通用,但在 Java 和 Android 中没有说明如何做。下面是一些具体的。

所以我的问题是,使用 Unicode 代码点可以在多大程度上区分 CJK 语言以及如何在 Android 中测试它们?我在 Java 和 Android 中看到了一些较新的测试,虽然了解这些很有用,但我还需要支持较旧的 Android 设备。

Unicode

Unicode中的CJK(和CJKV)是指Han Ideographs,即中文、日文、韩文、越南文使用的汉字。对于Unicode脚本命名,它不是指的是日语片假名和平假名或韩语韩文等注音文字。据说汉意图是统一的。他们的意思是每个表意文字只有一个 Unicode 代码点,无论它使用哪种语言。

这意味着 Unicode(反之 Android/Java)无法提供仅基于单个表意文字来确定语言的方法。即使是中文 Simplified/Traditional 字符也不容易从编码中区分出来。这与无法知道字符 "a" 属于英语、法语还是西班牙语是一样的。需要更多上下文才能确定。

但是,您可以使用 Unicode 编码来确定日文 Hiragana/Katakana 和韩文。并且这些字符的存在将很好地表明附近的汉表意文字属于同一语言。

Android

您可以通过

在某个索引处找到代码点
int codepoint = Character.codePointAt(myString, offset)

如果你想 iterate through the codepoints in a string:

final int length = myString.length();
for (int offset = 0; offset < length; ) {
    final int codepoint = Character.codePointAt(myString, offset);

    // use codepoint here

    offset += Character.charCount(codepoint);
}

获得代码点后,您可以使用

查找它所在的代码块
Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint);

然后您可以使用代码块来测试表意文字或语言。

中日韩文

扫描Unicode代码块,我认为这些涵盖了所有CJK表意文字。如果我遗漏了任何内容,请随时编辑我的答案或发表评论。

private boolean isCJK(int codepoint) {
    Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint);
    return (
            Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS.equals(block)||
            Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A.equals(block) ||
            Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B.equals(block) ||
            Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C.equals(block) || // api 19
            Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D.equals(block) || // api 19
            Character.UnicodeBlock.CJK_COMPATIBILITY.equals(block) ||
            Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS.equals(block) ||
            Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS.equals(block) ||
            Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT.equals(block) ||
            Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT.equals(block) ||
            Character.UnicodeBlock.CJK_STROKES.equals(block) ||                        // api 19
            Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION.equals(block) ||
            Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS.equals(block) ||
            Character.UnicodeBlock.ENCLOSED_IDEOGRAPHIC_SUPPLEMENT.equals(block) ||    // api 19
            Character.UnicodeBlock.KANGXI_RADICALS.equals(block) ||
            Character.UnicodeBlock.IDEOGRAPHIC_DESCRIPTION_CHARACTERS.equals(block));
}

只有 API 级别 19 才能使用带有注释(向右滚动)的那些。但是,如果您需要支持早期版本,则可以安全地删除这些,因为它们很少使用。此外,Unicode 定义了 CJK 扩展 E,但在撰写本文时,Android/Java 不支持它。如果您确实需要包含所有内容,那么您可以直接将代码点与 Unicode 块范围进行比较。 This site is a convenient place to browse them. You can also see them at the Unicode site.

如果API19以下不需要支持,那么isIdeographic测试起来很简单(虽然不知道returns是否完全一样匹配如上方法)。

private boolean isCJK(int codepoint) {
    return Character.isIdeographic(codepoint);
}

或者这个 API 24+:

private boolean isCJK(int codepoint) {
    return (Character.UnicodeScript.of(codepoint) == Character.UnicodeScript.HAN);
}

日语

对于测试平假名或片假名,这应该可以正常工作:

private boolean isJapaneseKana(int codepoint) {
    Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint);
    return (
            Character.UnicodeBlock.HIRAGANA.equals(block) ||
            Character.UnicodeBlock.KATAKANA.equals(block) ||
            Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS.equals(block));
}

或者如果您支持 API 24+:

(这需要更多测试。请参阅下面的评论。)

private boolean isJapaneseKana(int codepoint) {
    return (Character.UnicodeScript.of(codepoint) == Character.UnicodeScript.HIRAGANA || 
            Character.UnicodeScript.of(codepoint) == Character.UnicodeScript.KATAKANA);
}

韩语

要在较低的 API 上测试韩语,您可以使用

private boolean isKoreanHangul(int codepoint) {
    Character.UnicodeBlock block = Character.UnicodeBlock.of(codepoint);
    return (Character.UnicodeBlock.HANGUL_JAMO.equals(block) ||
            Character.UnicodeBlock.HANGUL_JAMO_EXTENDED_A.equals(block) || // api 19
            Character.UnicodeBlock.HANGUL_JAMO_EXTENDED_B.equals(block) || // api 19
            Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO.equals(block) ||
            Character.UnicodeBlock.HANGUL_SYLLABLES.equals(block));
}

如有必要,删除标记为 API 19 的行。

或 API 24 岁以上:

private boolean isKoreanHangul(int codepoint) {
    return (Character.UnicodeScript.of(codepoint) == Character.UnicodeScript.HANGUL);
}

进一步研究