【问题标题】:Convert language code three characters (ISO 639-2) to two-character code (ISO 639-1)将语言代码三个字符 (ISO 639-2) 转换为两个字符代码 (ISO 639-1)
【发布时间】:2020-07-19 15:18:26
【问题描述】:

我正在使用文本转语音 (TTS) 引擎开发一个 Android 应用程序。 TTS 组件将可用语言列表作为Locale 对象列表返回。

但是每个 Locale 对象的 Locale::getLanguageLocale::getISO3Language 方法都返回相同的 3 字符代码 (ISO 639-2)。通常getLanguage() 以 2 字符格式 (ISO 639-1) 返回语言代码,但对于特定设备,代码是三个字符。国家代码也一样。但是我需要两种字符格式(ISO 639-1)的语言和国家代码。

有人知道进行转换的方法吗?请注意,我需要一个对应的Locale 对象,其中包含两个字母格式的语言和国家代码。

【问题讨论】:

  • 我看到 Locale::getLanguage 奇怪地没有指定它是返回 2 个字符代码还是 3 个字符代码。它提到了 ISO 639,并使用了 2 个字母的代码示例,但实际上并没有说 2 或 3。但 Locale.getISOLanguages 确实指定它确实返回 2 个字母的代码。
  • 也许您应该编辑您的问题以指定有问题的设备及其 Android 版本。
  • 确实文档说 Locale::getLanguage 可以返回两种类型的代码而不是错误。通常我发现的所有设备都返回两个字母代码,而不是我没有注意到“问题”,但最近我发现三星设备的 Android 10 返回三个数字代码。
  • 不,Locale:getLanguage 没有说它可以返回 2 个或 3 个字母。它没有提及长度,也没有提及 ISO 639-1 与 639-2。你能引用你的参考吗?
  • 在 android 文档中 Locale#getLanguage() 似乎谈到了返回的通用 ISO 639 代码。目前我发现的所有 android 设备都返回了两位数代码,但几天前我发现了一个三星 s10e,它返回了三位数代码。

标签: java android locale


【解决方案1】:

tl;博士

作为一种解决方法,根据 ISO 639-1 创建自己的 Map< Locale , String> 将每个已知的 Locale 映射到其 2 字母语言代码。

new LocaleLookup().lookupTwoLetterLanguageCode( Locale.CANADA_FRENCH )

f

或者也许只是解析Locale::toString的文本。

Locale
.CANADA_FRENCH      
.toString()         // fr_CA
.split( "_" )       // Array: { "fr" , "CA" }
[ 0 ]               // Grab first element in array, "fr". 

f

对于双字母国家/地区代码,请使用该拆分字符串的第二部分。使用1 的索引而不是0

Locale
.CANADA_FRENCH      
.toString()         // fr_CA
.split( "_" )       // Array: { "fr" , "CA" }
[ 1 ]               // Grab first element in array, "CA". 

加拿大

错误?

Locale::getLanguage 会返回 3 个字母的代码似乎是一个错误。 Javadoc 在其代码示例中使用 2 个字母的代码。但不幸的是,Javadoc 未能明确指定 2 或 3 个字母。我建议您向 OpenJDK 项目提出请求,以澄清此 Javadoc。

解决方法

作为一种解决方法,也许您可​​以调用 Locale.getISOLanguages 以获取所有已知语言的 2 字母代码数组。然后循环那些。对于每个,使用 Javadoc 中的代码,传递 2 个字母的代码来压缩一个 Locale 对象以进行比较:

if (locale.getLanguage().equals(new Locale("he").getLanguage()))

由此在语言环境和两字母代码之间构建您自己的Map

示例类

这是我第一次尝试这样的解决方法。

在构造函数中,我们得到所有已知语言环境和所有已知 2 字母 ISO 639-1 语言代码的列表。

接下来我们做一个嵌套循环。对于每个语言环境,我们循环所有 2 个字母的语言代码,直到找到匹配项。请注意,我们 not 进行字符串匹配。 Javadoc 警告我们ISO 639 标准不稳定;代码正在改变。引用:

注意:ISO 639 不是一个稳定的标准——一些语言的代码已经改变。 Locale 的构造函数识别代码已更改的语言的新代码和旧代码,但此函数始终返回旧代码。如果您想检查代码已更改的特定语言,请不要这样做

if (locale.getLanguage().equals("he")) // BAD!

相反,做

if (locale.getLanguage().equals(new Locale("he").getLanguage())) // GOOD.

因此,我们的内部循环查看每个已知的 2 字母语言代码,并获取该语言的 Locale 对象。然后我们的if 语句比较getLanguage 的输出(a)我们的外循环的Locale,和(b)我们的内循环生成的纯语言Locale(由我们的2 字母代码生成)。在您的情况下,您声称某些设备正在为我们对getLanguage 的调用输出 3 个字母的代码值。但无论是 2 个还是 3 个字母,都无所谓。我们只是在寻找一场比赛。

实例化后,我们可以通过调用lookupTwoLetterLanguageCode 方法向LocaleLookup 实例询问与特定Locale 匹配的两字母代码。

LocaleLookup localeLookup = new LocaleLookup();
Locale locale = Locale.CANADA_FRENCH;
String code = localeLookup.lookupTwoLetterLanguageCode( locale );

System.out.println( "Locale: " + locale.toString() + " " + locale.getDisplayName( Locale.getDefault() ) + " | ISO 639-1 code: " + code );

语言环境:fr_CA 法语(加拿大)| ISO 639-1 代码:fr

我只是在猜测这一切。我没有考虑过,也没有测试过这些。所以买家要小心,这个解决方案值得你付出的每一分钱。祝你好运。

这是整个课程,带有public static void main 用作演示。

package work.basil.example;

import java.util.*;

public class LocaleLookup
{
    private Map < Locale, String > mapLocaleToTwoLetterLangCode;

    public LocaleLookup ( )
    {
        this.mapLocaleToTwoLetterLangCode = new HashMap <>( Locale.getAvailableLocales().length );
        this.makeMaps();
        System.out.println( "mapLocaleToTwoLetterLangCode = " + mapLocaleToTwoLetterLangCode );
    }

    private void makeMaps ( )
    {
        // Get all locales.
        Set < Locale > locales = Set.of( Locale.getAvailableLocales() );


        // Get all languages, per 2-letter code.
        Set < String > twoLetterLanguageCodes = Set.of( Locale.getISOLanguages() ); // Returns: An array of ISO 639 two-letter language codes.

        for ( Locale locale : locales )
        {
            for ( String twoLetterLanguageCode : twoLetterLanguageCodes )
            {
                if ( locale.getLanguage().equals( new Locale( twoLetterLanguageCode ).getLanguage() ) )
                {
                    this.mapLocaleToTwoLetterLangCode.put( locale , twoLetterLanguageCode );
                    break;
                }
            }
        }
//        System.out.println( "locales = " + locales );
//        System.out.println( "twoLetterLanguageCodes = " + twoLetterLanguageCodes );
    }

    public String lookupTwoLetterLanguageCode ( final Locale locale )
    {
        String code = this.mapLocaleToTwoLetterLangCode.get( locale );
        Objects.requireNonNull( code );
        return code;
    }


    public static void main ( String[] args )
    {
        LocaleLookup localeLookup = new LocaleLookup();
        Locale locale = Locale.CANADA_FRENCH;
        String code = localeLookup.lookupTwoLetterLanguageCode( locale );

        System.out.println( "Locale: " + locale.toString() + " " + locale.getDisplayName( Locale.getDefault() ) + " | ISO 639-1 code: " + code );
    }
}

这是我在 Java 15 的预发布版本中制作的地图。请注意,这可能不正确,因为我看到预发布版本中的语言环境有些愚蠢。

mapLocaleToTwoLetterLangCode = {nn=nn, ar_JO=ar, bg=bg, zu=zu, am_ET=am, fr_DZ=fr, ti_ET=ti, bo_CN=bo, qu_EC=qu, ta_SG=ta, lv=lv, en_NU=en, en_MS=en, zh_SG_#Hans=zh, ff_LR_#Adlm=ff, en_GG=en, en_JM=en, vo=vo, sd__#Arab=sd, sv_SE=sv, sr_ME=sr, dz_BT=dz, es_BO=es, en_ZM=en, fr_ML=fr, br=br, ha_NG=ha, fa_AF=fa, ar_SA=ar, sk=sk, os_GE=os, ml=ml, en_MT=en, en_LR=en, ar_TD= ar, en_GH=en, en_IL=en, sv=sv, cs=cs, el=el, af=af, ff_MR_#Latn=ff, sw_UG=sw, tk_TM=tk, sr_ME_#Cyrl=sr, ar_EG=ar, sd__#Deva=sd, ji_001=yi, yo_NG=yo, se_NO=se, ku=ku, sw_CD=sw, vo_001=vo, en_PW=en, pl_PL=pl, ff_MR_#Adlm=ff, it_VA=it, sr_CS= sr, ne_IN=ne, es_PH=es, es_ES=es, es_CO=es, bg_BG=bg, ji=yi, ar_EH=ar, bs_BA_#Latn=bs, en_VC=en, nb_SJ=nb, es_US=es, en_US_POSIX= en, en_150=en, ar_SD=ar, en_KN=en, ha_NE=ha, pt_MO=pt, ro_RO=ro, zh__#Hans=zh, lb_LU=lb, sr_ME_#Latn=sr, es_GT=es, so_KE=so, ff_LR_#Latn=ff, ff_GH_#​​Latn=ff, fr_PM=fr, ar_KM=ar, no_NO_NY=no, fr_MG=fr, es_CL=es, mn=mn, tr_TR=tr, eu=eu, fa_IR=fa, en_MO=恩,我=我, en_BZ=en, sq_AL=sq, ar_MR=ar, es_DO=es, ru=ru, az=az, su__#Latn=su, fa=fa, kl_GL=kl, en_NR=en, nd=nd, kk=kk , en_MP=en, az__#Cyrl=az, en_GD=en, tk=tk, hy=hy, en_BW=en, en_AU=en, en_CY=en, ta_MY=ta, ti_ER=ti, en_RW=en, sv_FI=sv , nd_ZW=nd, lb=lb, ne=ne, su=su, zh_SG=zh, en_IE=en, ln_CD=ln, en_KI=en, om_ET=om, no=no, ja_JP=ja, my=my, ka =ka, ar_IL=ar, ff_GH_#​​Adlm=ff, or_IN=or, fr_MF=fr, ms_ID=ms, kl=kl, en_SZ=en, zh=zh, es_PE=es, ta=ta, az__#Latn=az , en_GB=en, zh_HK_#Hant=zh, ar_SY=ar, bo=bo, kk_KZ=kk, tt_RU=tt, es_PA=es, om_KE=om, ar_PS=ar, fr_VU=fr, en_AS=en, zh_TW=zh , sd_IN=sd, fr_MC=fr, kw=kw, fr_NE=fr, pt_MZ=pt, ur_IN=ur, ln=ln, en_JE=en, ln_CF=ln, en_CX=en, pt=pt, en_AT=en, gl =gl, sr__#Cyrl=sr, es_GQ=es, kn_IN=kn, ff__#Adlm=ff, ar_YE=ar, en_SX=en, to=to, ga=ga, qu=qu, ru_KZ=ru, en_TZ=en , et=et, en_PR=en, jv=jv, ko_KP=ko, in=in, sn=sn, ps=ps, nl_SR=nl, en_BS=en, km=km, fr_NC=fr, be=be, gv =gv, es=es, gd_GB=gd, nl_BQ=nl, ff_GN_#Adlm=ff, fr_CM=fr, uz_UZ_#Cyrl=uz, pa_IN_#Guru=pa, en_KE=e n, ja=ja, fr_SN=fr, or=or, fr_MA=fr, pt_LU=pt, ff_GM_#Adlm=ff, fr_BL=fr, en_NL=en, ln_CG=ln, te=te, sl=sl, ha= ha, mr_IN=mr, ko_KR=ko, el_CY=el, ku_TR=ku, es_MX=es, es_HN=es, hu_HU=hu, ff_SN=ff, sq_MK=sq, sr_BA_#Cyrl=sr, fi=fi, bs__# Cyrl=bs, uz=uz, et_EE=et, sr__#Latn=sr, en_SS=en, bo_IN=bo, sw=sw, fy_NL=fy, ar_OM=ar, tr_CY=tr, rm=rm, fr_BI=fr, en_MG=en, uz_UZ_#Latn=uz, bn=bn, de_IT=de, kn=kn, fr_TN=fr, sr_RS=sr, bn_BD=bn, de_CH=de, fr_PF=fr, gu=gu, pt_GQ=pt, en_ZA=en, en_TV=en, lo=lo, fr_FR=fr, en_PN=en, fr_BJ=fr, en_MH=en, zh__#Hant=zh, zh_HK_#Hans=zh, cu_RU=cu, nl_NL=nl, en_GY= en, ps_AF=ps, bs__#Latn=bs, ky=ky, os=os, bs_BA_#Cyrl=bs, nl_CW=nl, ar_DZ=ar, sk_SK=sk, pt_CH=pt, fr_GQ=fr, xh=xh, ki_KE=ki, am=am, fr_CI=fr, en_NG=en, ia_001=ia, en_PK=en, zh_CN=zh, en_LC=en, rw=rw, ff_BF_#Adlm=ff, wo_SN=wo, gv_IM=gv, iw=iw, en_TT=en, mk_MK=mk, sl_SI=sl, fr_HT=fr, te_IN=te, nl_SX=nl, ce=ce, fr_CG=fr, xh_ZA=xh, fr_BE=fr, ff_NE_#Adlm=ff, es_VE=es, mt_MT=mt, mr=mr, mg=mg, ko=ko, en_BM=en, nb_NO=n b, ak=ak, dz=dz, vi_VN=vi, en_VU=en, ia=ia, en_US=en, ff_SL_#Latn=ff, to_TO=to, ff_SN_#Adlm=ff, fr_BF=fr, pa__#Guru= pa, it_SM=it, su_ID=su, fr_YT=fr, gu_IN=gu, ii_CN=ii, ff_CM_#Latn=ff, pa_PK_#Arab=pa, fr_RE=fr, fi_FI=fi, ca_FR=ca, sr_BA_#Latn= sr, bn_IN=bn, fr_GP=fr, pa=pa, tg=tg, fr_DJ=fr, rn=rn, uk_UA=uk, ks__#Arab=ks, hu=hu, fr_CH=fr, en_NF=en, ff_GW_# Adlm=ff, ha_GH=ha, sr_XK_#Cyrl=sr, bm=bm, ar_SS=ar, en_GU=en, nl_AW=nl, de_BE=de, en_AI=en, en_CM=en, cs_CZ=cs, ca_ES=ca, tr=tr, ff_GW_#Latn=ff, rm_CH=rm, ru_MD=ru, ms_MY=ms, ta_LK=ta, en_TO=en, ff_SN_#Latn=ff, ff_SL_#Adlm=ff, cy=cy, en_PG=en, fr_CF=fr, pt_TL=pt, sq=sq, tg_TJ=tg, fr=fr, en_ER=en, qu_PE=qu, sr_BA=sr, es_PY=es, de=de, es_EC=es, ff_CM_#Adlm=ff, lg_UG=lg, ff_NE_#Latn=ff, zu_ZA=zu, fr_TG=fr, su_ID_#Latn=su, sr_XK_#Latn=sr, en_PH=en, ig_NG=ig, fr_GN=fr, zh_MO_#Hans=zh, lg= lg, ru_RU=ru, se_FI=se, ff=ff, en_DM=en, en_CK=en, sd=sd, ar_MA=ar, ga_IE=ga, en_BI=en, en_AG=en, fr_TD=fr, fr_LU=fr, en_WS=en, fr_CD=fr, so=so, rn_BI=rn, en_NA=en, mi_NZ=mi, ar_ER=ar, ms=ms, sn_ZW=sn, iw_IL=iw, ug=ug, es_EA=es, ga_GB=ga, th_TH_TH_#u-nu-thai=th, hi=hi, fr_SC=fr, ca_IT=ca, ff_NG_#Latn=ff, en_SL=en, no_NO=no, ca_AD=ca, ff_NG_#Adlm=ff, zh_MO_#Hant=zh, en_SH=en, qu_BO=qu, vi=vi, sd_PK_#Arab=sd, fr_CA=fr, de_LU=de, sq_XK=sq, en_KY=en, mi=mi, mt=mt, it_CH=it, de_DE=de, si_LK=si, en_AE=en, en_DK=en, so_DJ=so, eo=eo, lt_LT=lt, it_IT=it, en_ZW=en​​, ar_SO=ar, ro=ro, en_UM=en, ps_PK=ps, eo_001=eo, ee=ee, fr_MU=fr, nn_NO= nn, se_SE=se, pl=pl, en_TK=en, en_SI=en, ur=ur, uz__#Arab=uz, pt_GW=pt, se=se, lo_LA=lo, af_ZA=af, ar_LB=ar, ms_SG= ms, ee_TG=ee, ln_AO=ln, be_BY=be, ff_GN=ff, in_ID=in, es_BZ=es, ar_AE=ar, hr_HR=hr, as=as, it=it, pt_CV=pt, ks_IN=ks, uk=uk, my_MM=my, mn_MN=mn, ur_PK=ur, en_FM=en, da_DK=da, es_PR=es, en_BE=en, ii=ii, fr_WF=fr, tt=tt, ru_BY=ru, fo_DK= fo, ee_GH=ee, en_SG=en, ar_BH=ar, ff_GM_#Latn=ff, om=om, en_CH=en, hi_IN=hi, fo_FO=fo, yo_BJ=yo, fr_KM=fr, fr_MQ=fr, ff_GN_# Latn=ff, en_SD=en, es_AR=es, ff__#Latn=ff, en_MY =en, ja_JP_JP_#u-ca-japanese=ja, es_SV=es, pt_BR=pt, ml_IN=ml, en_FK=en, uz__#Cyrl=uz, is_IS=is, hy_AM=hy, en_GM=en, en_DG=en , fo=fo, ne_NP=ne, pt_ST=pt, hr=hr, ak_GH=ak, lt=lt, uz_AF_#Arab=uz, ta_IN=ta, fr_GF=fr, en_SE=en, zh_CN_#Hans=zh, es_419 =es, is=is, pt_AO=pt, si=si, en_001=en, jv_ID=jv, en=en, es_IC=es, fr_MR=fr, ca=ca, ru_KG=ru, ar_TN=ar, ks=ks , zh_TW_#Hant=zh, ff_BF_#Latn=ff, bm_ML=bm, kw_GB=kw, ug_CN=ug, as_IN=as, es_BR=es, zh_HK=zh, sw_KE=sw, en_SB=en, th_TH=th, rw_RW =rw, ar_IQ=ar, en_MW=en, mk=mk, en_IO=en, pa__#Arab=pa, en_DE=en, ar_QA=ar, en_CC=en, ro_MD=ro, en_FI=en, bs=bs, pt_PT =pt, fy=fy, az_AZ_#Cyrl=az, th=th, es_CU=es, ar=ar, en_SC=en, en_VI=en, eu_ES=eu, en_UG=en, en_NZ=en, es_UY=es, sg_CF =sg, ru_UA=ru, sg=sg, uz__#Latn=uz, el_GR=el, da_GL=da, en_FJ=en, de_LI=de, en_BB=en, km_KH=km, hr_BA=hr, de_AT=de, nl =nl, lu_CD=lu, ca_ES_VALENCIA=ca, ar_001=ar, so_SO=so, lv_LV=lv, sd_IN_#Deva=sd, es_CR=es, ar_KW=ar, fr_GA=fr, ar_LY=ar, sr=sr, sr_RS_ #Cyrl=sr, en_MU=en, da=da, gl _ES=gl, az_AZ_#Latn=az, en_IM=en, en_LS=en, ig=ig, en_HK=en, en_GI=en, ce_RU=ce, gd=gd, en_CA=en, ka_GE=ka, fr_SY=fr, sw_TZ=sw, so_ET=so, fr_RW=fr, nl_BE=nl, ar_DJ=ar, mg_MG=mg, en_VG=en, cy_GB=cy, cu=cu, sr_RS_#Latn=sr, os_RU=os, en_TC=en, sv_AX=sv, ky_KG=ky, af_NA=af, lu=lu, en_IN=en, yo=yo, ki=ki, es_NI=es, nb=nb, sd_PK=sd, ti=ti, ms_BN=ms, br_FR= br}

Locale.toString的子串?

现在,在完成所有这些工作之后,我注意到语言环境名称的 toString 表示以两个字母的语言代码开头! ?

如果所有Locale 对象都是这种情况,我们可以简单地解析该字符串。

String twoLetterLanguageCode = Locale.CANADA_FRENCH.toString().split( "_" )[ 0 ];

twoLetterCode = fr

对于国家/地区代码,执行相同操作,但拉出第二部分。使用10 的索引值。

String twoLetterCountryCode = Locale.CANADA_FRENCH.toString().split( "_" )[ 1 ];

对于我的预发布 Java 15 的快速检查,似乎每个 Locale 对象的 toString 文本都以 2 个字母的语言代码开头。但我不知道你是否可以指望过去和未来总是如此。

System.out.println(Locale.getAvailableLocales().length); ArrayList problemLocales = new ArrayList(Locale.getAvailableLocales().length); 对于(语言环境语言环境:Locale.getAvailableLocales()) { 字符串解析 = locale.toString().split("_")[0]; if (!parsed.equalsIgnoreCase(locale.getLanguage())) { questionLocales.add( 语言环境 ); } }

System.out.println("problemLocales = " + problemLocales);

problemLocales = []

或者,反之亦然:

System.out.println( "Locale.getAvailableLocales().length: " + Locale.getAvailableLocales().length );
ArrayList < Locale > matchingLocales = new ArrayList <>( Locale.getAvailableLocales().length );
for ( Locale locale : Locale.getAvailableLocales() )
{
    String parsed = locale.toString().split( "_" )[ 0 ];
    if ( parsed.equalsIgnoreCase( locale.getLanguage() ) )
    {
        matchingLocales.add( locale );
    }
}

System.out.println( "matchingLocales.size: " + matchingLocales.size() );
System.out.println( "matchingLocales = " + matchingLocales );

Locale.getAvailableLocales().length: 810

matchingLocales.size: 810

【讨论】:

  • 我也阅读了文档并注意到了这个例子,但我指定我需要一个相应的 Locale 对象,其中包含两个字母格式的语言和国家代码。您建议的循环允许仅使用语言代码生成 Locale 对象,相反我需要一个具有语言和国家/地区的 Locale 对象。这是因为,例如,英语可以来自美国或英国,它们是两个不同的 Locale 对象......
  • @Suppaman 您的评论似乎与您的问题相矛盾。在您的问题中,您说,“每个 Locale 对象的 Locale::getLanguage 和 Locale::getISO3Language 方法都返回相同的 3 字符代码(ISO 639-2)”,而您想“返回 2 个字符的语言代码格式 (ISO 639-1)" 用于特定语言环境。我在我的答案中添加了代码来做到这一点。但是现在在您的评论中,您在谈论国家/地区代码,所以我很困惑。
  • 首先感谢您花这么多时间写了这么长的回复。可能我没有清楚地写下我的第一条消息。在消息中,在解释了语言代码的问题后,我还写了“国家代码相同”,我的意思是国家代码也以三位数格式返回。但是,您的示例代码给了我一个可能的“转换”代码的想法,我将尝试开发。再次感谢您。