将语言代码三个字符(ISO 639-2)转换为两个字符代码(ISO 639-1)
我正在开发一个使用文本到语音(TTS)引擎的Android应用程序。TTS组件以Locale
对象列表的形式返回可用语言列表。
Locale
对象的Locale::getLanguage
和Locale::getISO3Language
两个方法返回相同的3字符代码(ISO 639-2)。通常getLanguage()
以2字符格式(ISO 639-1)返回语言代码,但对于特定设备,代码为3个字符。国家代码也是如此。但是,我需要两个字符格式的语言和国家代码(ISO 639-1)。
有人知道进行转换的方法吗?请注意,我需要一个具有两个字母格式的语言和国家/地区代码的相应Locale
对象。
解决方案
tl;dr
作为一种解决办法,请根据ISO 639-1将每个已知的Locale
映射到其两个字母的语言代码。
new LocaleLookup().lookupTwoLetterLanguageCode( Locale.CANADA_FRENCH )
fr
或可能只分析Locale::toString
的文本。
Locale
.CANADA_FRENCH
.toString() // fr_CA
.split( "_" ) // Array: { "fr" , "CA" }
[ 0 ] // Grab first element in array, "fr".
fr
对于两个字母的国家/地区代码,请使用拆分字符串的第二部分。使用1
而不是0
的索引。
Locale
.CANADA_FRENCH
.toString() // fr_CA
.split( "_" ) // Array: { "fr" , "CA" }
[ 1 ] // Grab first element in array, "CA".
CA
错误?
Locale::getLanguage
将返回3个字母的代码似乎是一个错误。Javadoc在其代码示例中使用了两个字母的代码。但不幸的是,Javadoc没有明确指定2个或3个字母。我建议您向OpenJDK项目提交一个请求,以澄清此Java代码。
解决方法
作为一种解决办法,您或许可以调用Locale.getISOLanguages
来获取所有已知语言的两个字母代码的数组。然后把那些循环起来。对于每个对象,使用在Javadoc中看到的代码,传递两个字母的代码来限制Locale
对象进行比较:
if (locale.getLanguage().equals(new Locale("he").getLanguage()))
从此版本中,您可以在区域设置和两个字母的代码之间使用您自己的Map
。
示例类
这是我第一次尝试这样的变通地图。
在构造函数中,我们获得所有已知区域设置和所有已知2字母ISO 639-1语言代码的列表。
接下来,我们执行嵌套循环。对于每个地区,我们循环所有两个字母的语言代码,直到找到匹配项。注意,我们做的是而不是进行字符串匹配。Javadoc警告我们ISO 639标准不是稳定的;代码正在更改。报价:
注意:ISO 639不是一个稳定的标准-某些语言的代码已更改。Locale的构造函数可以识别代码已更改的语言的新代码和旧代码,但此函数始终返回旧代码。如果要检查代码已更改的特定语言,请不要执行以下操作
if (locale.getLanguage().equals("he")) // BAD!
相反,请执行
if (locale.getLanguage().equals(new Locale("he").getLanguage())) // GOOD.
因此,我们的内部循环查看每个已知的两个字母的语言代码,并获取该语言的Locale
对象。然后,if
语句比较getLanguage
的输出(A)外部循环的Locale
和(B)内部循环生成的仅语言的Locale
(由两个字母的代码生成)。在您情况下,您声称某个设备正在为我们对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预发布版本中生成的映射。注意可能是不正确的,因为我在预发布版本中看到了一些关于区域设置的错误。
相关文章