【问题标题】:How to convert Microsoft Locale ID (LCID) into language code or Locale object in Java如何在 Java 中将 Microsoft Locale ID (LCID) 转换为语言代码或 Locale 对象
【发布时间】:2009-07-28 07:19:50
【问题描述】:

我需要将 Microsoft locale ID(例如 1033(美国英语))翻译成 ISO 639 language code 或直接翻译成 Java Locale 实例。 (编辑:甚至直接进入微软表格中的“语言 - 国家/地区”。)

这可能吗?最简单的方法是什么?当然,最好只使用 JDK 标准库,但如果不可能,请使用 3rd 方库。

【问题讨论】:

    标签: java windows locale lcid


    【解决方案1】:

    您可以使用GetLocaleInfo 来执行此操作(假设您在 Windows (win2k+) 上运行)。

    此 C++ 代码演示了如何使用该函数:

    #include "windows.h"
    
    int main()
    {
      HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
      if(INVALID_HANDLE_VALUE == stdout) return 1;
    
      LCID Locale = 0x0c01; //Arabic - Egypt
      int nchars = GetLocaleInfoW(Locale, LOCALE_SISO639LANGNAME, NULL, 0);
      wchar_t* LanguageCode = new wchar_t[nchars];
      GetLocaleInfoW(Locale, LOCALE_SISO639LANGNAME, LanguageCode, nchars);
    
      WriteConsoleW(stdout, LanguageCode, nchars, NULL, NULL);
      delete[] LanguageCode;
      return 0;
    }
    

    将其转换为JNA 调用并不需要太多工作。 (提示:将常量作为整数发出以查找它们的值。)

    示例 JNA 代码:

    使用 JNI 有点复杂,但对于相对琐碎的任务来说是可以管理的。

    至少,我会考虑使用本机调用来构建您的转换数据库。我不确定 Windows 是否有办法枚举 LCID,但 .Net 中肯定会有一些东西。作为构建级别的东西,这不是一个巨大的负担。我想避免手动维护列表。

    【讨论】:

    • 谢谢!在我们的例子中,代码也需要在其他平台(例如 Linux)上运行,即使我们正在处理的信息是以 Windows 为中心并且来自 SCCM 数据库。但也许在某些情况下这是最好的选择——我同意必须在文件中维护映射并不好(即使它们很少更改)。顺便说一句,如果有人考虑使用 Windows API 执行此操作,这可能会有所帮助:stackoverflow.com/questions/1000723/…
    【解决方案2】:

    由于看起来似乎没有现成的 Java 解决方案来执行此映射,因此我们花了大约 20 分钟来推出我们自己的东西,至少目前是这样。

    我们从马的嘴里取出信息,即http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx,然后(通过 Excel)将它复制粘贴到一个 .properties 文件中,如下所示:

    1078 = Afrikaans - South Africa
    1052 = Albanian - Albania
    1118 = Amharic - Ethiopia
    1025 = Arabic - Saudi Arabia
    5121 = Arabic - Algeria 
    ...
    

    (有类似需求的可以下载文件here

    然后有一个非常简单的类,可以将 .properties 文件中的信息读取到地图中,并且有一个进行转换的方法。

    Map<String, String> lcidToDescription;
    
    public String getDescription(String lcid) { ... }
    

    是的,这实际上并不映射到 语言代码Locale 对象(这是我最初要求的),而是映射到 Microsoft 的“语言 - 国家/地区“ 描述。事实证明,这足以满足我们当前的需求。

    免责声明:这确实是在 Java 中自己进行的一种简约的“虚拟”方式,显然在您自己的代码库中保留(和维护)LCID 映射信息的副本并不是很优雅。 (另一方面,我也不想包含一个巨大的库 jar 或仅仅为了这个简单的映射做任何过于复杂的事情。)所以尽管有这个答案,请随意发布更优雅的解决方案或现有的库如果你知道类似的事情。

    【讨论】:

    • 使用纯英文名称进行映射可能会导致问题。看到这个答案:stackoverflow.com/questions/958178/…
    • 一般来说这是对的 - 但对于我们的特殊情况,那些英语语言/国家名称很好(我们正在从 SCCM 数据库中提取一些软件信息,并且只是想要一些比人类更易读的东西)数字代码)
    【解决方案3】:

    以下代码将以编程方式创建 Microsoft LCID 代码和 Java 区域设置之间的映射,从而更轻松地保持映射最新:

    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    
    /**
     * @author Gili Tzabari
     */
    public final class Locales
    {
        /**
         * Maps a Microsoft LCID to a Java Locale.
         */
        private final Map<Integer, Locale> lcidToLocale = new HashMap<>(LcidToLocaleMapping.NUM_LOCALES);
    
        public Locales()
        {
            // Try loading the mapping from cache
            File file = new File("lcid-to-locale.properties");
            Properties properties = new Properties();
            try (FileInputStream in = new FileInputStream(file))
            {
                properties.load(in);
                for (Object key: properties.keySet())
                {
                    String keyString = key.toString();
                    Integer lcid = Integer.parseInt(keyString);
                    String languageTag = properties.getProperty(keyString);
                    lcidToLocale.put(lcid, Locale.forLanguageTag(languageTag));
                }
                return;
            }
            catch (IOException unused)
            {
                // Cache does not exist or is invalid, regenerate...
                lcidToLocale.clear();
            }
    
            LcidToLocaleMapping mapping;
            try
            {
                mapping = new LcidToLocaleMapping();
            }
            catch (IOException e)
            {
                // Unrecoverable runtime failure
                throw new AssertionError(e);
            }
            for (Locale locale: Locale.getAvailableLocales())
            {
                if (locale == Locale.ROOT)
                {
                    // Special case that doesn't map to a real locale
                    continue;
                }
                String language = locale.getDisplayLanguage(Locale.ENGLISH);
                String country = locale.getDisplayCountry(Locale.ENGLISH);
                country = mapping.getCountryAlias(country);
                String script = locale.getDisplayScript();
                for (Integer lcid: mapping.listLcidFor(language, country, script))
                {
                    lcidToLocale.put(lcid, locale);
                    properties.put(lcid.toString(), locale.toLanguageTag());
                }
            }
    
            // Cache the mapping
            try (FileOutputStream out = new FileOutputStream(file))
            {
                properties.store(out, "LCID to Locale mapping");
            }
            catch (IOException e)
            {
                // Unrecoverable runtime failure
                throw new AssertionError(e);
            }
        }
    
        /**
         * @param lcid a Microsoft LCID code
         * @return a Java locale
         * @see https://msdn.microsoft.com/en-us/library/cc223140.aspx
         */
        public Locale fromLcid(int lcid)
        {
            return lcidToLocale.get(lcid);
        }
    }
    
    import com.google.common.collect.HashMultimap;
    import com.google.common.collect.ImmutableList;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.SetMultimap;
    import com.google.common.collect.Sets;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;
    import org.bitbucket.cowwoc.preconditions.Preconditions;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * Generates a mapping between Microsoft LCIDs and Java Locales.
     * <p>
     * @see http://stackoverflow.com/a/32324060/14731
     * @author Gili Tzabari
     */
    final class LcidToLocaleMapping
    {
        private static final int NUM_COUNTRIES = 194;
        private static final int NUM_LANGUAGES = 13;
        private static final int NUM_SCRIPTS = 5;
        /**
         * The number of locales we are expecting. This value is only used for performance optimization.
         */
        public static final int NUM_LOCALES = 238;
        private static final List<String> EXPECTED_HEADERS = ImmutableList.of("lcid", "language", "location");
        // [language] - [comment] ([script])
        private static final Pattern languagePattern = Pattern.compile("^(.+?)(?: - (.*?))?(?: \\((.+)\\))?$");
        /**
         * Maps a country to a list of entries.
         */
        private static final SetMultimap<String, Mapping> COUNTRY_TO_ENTRIES = HashMultimap.create(NUM_COUNTRIES,
            NUM_LOCALES / NUM_COUNTRIES);
        /**
         * Maps a language to a list of entries.
         */
        private static final SetMultimap<String, Mapping> LANGUAGE_TO_ENTRIES = HashMultimap.create(NUM_LANGUAGES,
            NUM_LOCALES / NUM_LANGUAGES);
        /**
         * Maps a language script to a list of entries.
         */
        private static final SetMultimap<String, Mapping> SCRIPT_TO_ENTRIES = HashMultimap.create(NUM_SCRIPTS,
            NUM_LOCALES / NUM_SCRIPTS);
        /**
         * Maps a Locale country name to a LCID country name.
         */
        private static final Map<String, String> countryAlias = ImmutableMap.<String, String>builder().
            put("United Arab Emirates", "U.A.E.").
            build();
    
        /**
         * A mapping between a country, language, script and LCID.
         */
        private static final class Mapping
        {
            public final String country;
            public final String language;
            public final String script;
            public final int lcid;
    
            Mapping(String country, String language, String script, int lcid)
            {
                Preconditions.requireThat(country, "country").isNotNull();
                Preconditions.requireThat(language, "language").isNotNull().isNotEmpty();
                Preconditions.requireThat(script, "script").isNotNull();
                this.country = country;
                this.language = language;
                this.script = script;
                this.lcid = lcid;
            }
    
            @Override
            public int hashCode()
            {
                return country.hashCode() + language.hashCode() + script.hashCode() + lcid;
            }
    
            @Override
            public boolean equals(Object obj)
            {
                if (!(obj instanceof Locales))
                    return false;
                Mapping other = (Mapping) obj;
                return country.equals(other.country) && language.equals(other.language) && script.equals(other.script) &&
                    lcid == other.lcid;
            }
        }
        private final Logger log = LoggerFactory.getLogger(LcidToLocaleMapping.class);
    
        /**
         * Creates a new LCID to Locale mapping.
         * <p>
         * @throws IOException if an I/O error occurs while reading the LCID table
         */
        LcidToLocaleMapping() throws IOException
        {
            Document doc = Jsoup.connect("https://msdn.microsoft.com/en-us/library/cc223140.aspx").get();
            Element mainBody = doc.getElementById("mainBody");
            Elements elements = mainBody.select("table");
            assert (elements.size() == 1): elements;
            for (Element table: elements)
            {
                boolean firstRow = true;
                for (Element row: table.select("tr"))
                {
                    if (firstRow)
                    {
                        // Make sure that columns are ordered as expected
                        List<String> headers = new ArrayList<>(3);
                        Elements columns = row.select("th");
                        for (Element column: columns)
                            headers.add(column.text().toLowerCase());
                        assert (headers.equals(EXPECTED_HEADERS)): headers;
                        firstRow = false;
                        continue;
                    }
                    Elements columns = row.select("td");
                    assert (columns.size() == 3): columns;
                    Integer lcid = Integer.parseInt(columns.get(0).text(), 16);
                    Matcher languageMatcher = languagePattern.matcher(columns.get(1).text());
                    if (!languageMatcher.find())
                        throw new AssertionError();
                    String language = languageMatcher.group(1);
                    String script = languageMatcher.group(2);
                    if (script == null)
                        script = "";
                    String country = columns.get(2).text();
                    Mapping mapping = new Mapping(country, language, script, lcid);
                    COUNTRY_TO_ENTRIES.put(country, mapping);
                    LANGUAGE_TO_ENTRIES.put(language, mapping);
                    if (!script.isEmpty())
                        SCRIPT_TO_ENTRIES.put(script, mapping);
                }
            }
        }
    
        /**
         * Returns the LCID codes associated with a [country, language, script] combination.
         * <p>
         * @param language a language
         * @param country  a country (empty string if any country should match)
         * @param script   a language script (empty string if any script should match)
         * @return an empty list if no matches are found
         * @throws NullPointerException     if any of the arguments are null
         * @throws IllegalArgumentException if language is empty
         */
        public Collection<Integer> listLcidFor(String language, String country, String script)
            throws NullPointerException, IllegalArgumentException
        {
            Preconditions.requireThat(language, "language").isNotNull().isNotEmpty();
            Preconditions.requireThat(country, "country").isNotNull();
            Preconditions.requireThat(script, "script").isNotNull();
            Set<Mapping> result = LANGUAGE_TO_ENTRIES.get(language);
            if (result == null)
            {
                log.warn("Language '" + language + "' had no corresponding LCID");
                return Collections.emptyList();
            }
            if (!country.isEmpty())
            {
                Set<Mapping> entries = COUNTRY_TO_ENTRIES.get(country);
                result = Sets.intersection(result, entries);
            }
    
            if (!script.isEmpty())
            {
                Set<Mapping> entries = SCRIPT_TO_ENTRIES.get(script);
                result = Sets.intersection(result, entries);
            }
            return result.stream().map(entry -> entry.lcid).collect(Collectors.toList());
        }
    
        /**
         * @param name the locale country name
         * @return the LCID country name
         */
        public String getCountryAlias(String name)
        {
            String result = countryAlias.get(name);
            if (result == null)
                return name;
            return result;
        }
    }
    

    Maven 依赖项:

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>
        <dependency>
            <groupId>org.bitbucket.cowwoc</groupId>
            <artifactId>preconditions</artifactId>
            <version>1.25</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.8.3</version>
        </dependency>
    

    用法:

    System.out.println("Language: " + new Locales().fromLcid(1033).getDisplayLanguage());
    

    将打印“语言:英语”。

    意思是,LCID 1033 映射到英语。

    注意:这只会为您的运行时 JVM 上可用的语言环境生成映射。这意味着,您只会获得所有可能语言环境的一个子集。也就是说,我认为在技术上实例化你的 JVM 不支持的语言环境是不可能的,所以这可能是我们能做的最好的事情......

    【讨论】:

      【解决方案4】:

      The was the first hit on google for "Java LCID" 是这个 javadoc:

      gnu.java.awt.font.opentype.NameDecoder
      

      私有静态 java.util.Locale getWindowsLocale(int lcid)

      Maps a Windows LCID into a Java Locale.
      
      Parameters:
          lcid - the Windows language ID whose Java locale is to be retrieved. 
      Returns:
          an suitable Locale, or null if the mapping cannot be performed.
      

      我不知道去哪里下载这个库,但它是 GNU,所以应该不难找到。

      【讨论】:

      • 我们也发现了这一点,但它看起来不是最理想的解决方案——它是一个“有助于解码 OpenType 和 TrueType 字体名称的实用程序类”。从源码看,转换方法似乎很缺乏——它只知道如何映射几个最常见的语言环境!
      • 这里是源代码:classpath.sourcearchive.com/documentation/0.91/… 请参阅“将 Windows LCID 映射到 Java 语言环境”的方法,并注意注释“FIXME:这非常不完整”。 :P
      【解决方案5】:

      这是一个粘贴到 F12 控制台并将当前 273 种语言的映射提取到其 lcid 的脚本(用于https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c):

      // extract data from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
      const locales = {}, dataTable = document.querySelector('div.table-scroll-wrapper:nth-of-type(2)>table.protocol-table'); 
      for (let i=1, l=dataTable.rows.length; i<l; i++) {
          const row = dataTable.rows[i]; 
          let locale = Number(row.cells[2].textContent.trim());  // hex to decimal
          let name = row.cells[3].textContent.trim();  // cc-LL
          if ((locale > 1024) && (name.indexOf('-') > 0))  // only cc-LL (languages, not countries)
              locales[locale] = name; 
      } 
      console.table(locales);  // 273 entries
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-04
        • 2015-08-12
        • 1970-01-01
        • 1970-01-01
        • 2012-10-06
        • 2015-01-08
        • 1970-01-01
        相关资源
        最近更新 更多