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

9
我希望能够识别中文、日文和韩文的书写字符,既作为一个整体组,又作为细分语言。以下是原因:
  • 将CJK视为整体组:我正在制作一个垂直蒙古文TextView。为此,我需要将文本行旋转90度,因为字形在字体中是水平存储的。然而,对于CJK语言,我需要将它们旋转回来,以便它们以正确的方向书写,但仍然沿着一行堆叠。
  • 将CJK区分为特定语言:我还制作了一个蒙古语词典,当用户输入CJK字符进行查找时,我想自动识别语言。因为汉字也被日本人和韩国人使用,我猜想我可能无法完全实现这一点,但我希望尽可能地利用编码的最大限度。

在语言学上,我知道的子类别有:

为了完整起见,在越南中也使用汉字(因此CJK也称为CJKV)。对于我的当前目的,我不需要担心它,但这可能是未来的考虑。我还忽略了像中文拼音或日本罗马字这样的罗马化脚本。它们将与TextView中的英语和蒙古语处理方式相同(即与其余行一起旋转90度)。台湾使用的注音符号ㄅㄆㄇㄈ可能也是未来的考虑,但现在我会忽略它。有关语言示例,请参见此处此处

我看到了许多相关的问题,通常是涉及Java或Android中的一种特定语言,但没有一个总体问题有一个规范答案。其他问题更为普遍,涉及Unicode,但不告诉如何在Java和Android中实现。以下是一些具体的问题。

所以我的问题是,使用Unicode代码点我可以区分CJK语言的程度有多大,我如何在Android上进行测试?我看到了一些Java和Android上的新测试,虽然这些测试很有用,但我也需要支持旧的Android设备。
1个回答

21

Unicode

Unicode中的CJK(和CJKV)指汉字,即在中文、日文、韩文和越南文中使用的汉字。对于Unicode脚本命名,它不是指象日语片假名和平假名或韩语谚文等音标书写的文字。汉字被称为统一的,这意味着每个表意字符只有一个Unicode代码点,无论用于哪种语言。

这意味着Unicode(反过来也是Android / Java)不能基于单个表意字符来确定语言。甚至简体/繁体中文字符也不能轻松区分编码。这与无法知道字符“a”属于英语、法语还是西班牙语的想法相同,需要更多上下文来确定。

但是,您可以使用Unicode编码来确定日文平假名/片假名和韩文谚文。这些字符的存在将是附近汉字属于同一语言的良好指示。

Android

您可以在某个索引处找到代码点。

int codepoint = Character.codePointAt(myString, offset)

如果您想要遍历字符串中的代码点

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

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

CJK

通过扫描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块范围进行比较。 这个网站是浏览它们的便利之处。 您也可以在 Unicode 网站上查看它们。
如果您不需要支持API 19以下的版本,则 isIdeographic 使测试非常容易(尽管我不知道它是否返回与上面的方法完全相同的匹配)。
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);
}

深入研究


嗨,看起来你的API 24+的isJapanese方法无法将ー识别为片假名。 - tObi
@tObi,感谢您留下关于此事的评论。由于我仍需要支持API 24之前的版本,因此在我的任何生产项目中都没有使用API 24+代码,所以我还没有遇到您提到的问题。如果您发现了问题所在或解决方法,请再次留言。 - Suragch
@tObi,为了我的测试目的,你能告诉我一个特定的片假名字符,它无法被识别吗? - Suragch
是的,就像我上面的评论所说的那样,"ー"字符 :) https://en.wiktionary.org/wiki/%E3%83%BC - tObi
@tObi,啊,我误解了你原来的评论。感谢澄清。 - Suragch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接