windows 如何在Java中将Microsoft Locale ID(LCID)转换为语言代码或Locale对象

vfhzx4xs  于 2023-03-19  发布在  Windows
关注(0)|答案(6)|浏览(475)

我需要将一个Microsoft locale ID,比如1033(美国英语),翻译成ISO 639 language code或者直接翻译成Java Locale示例。或者甚至简单地输入Microsoft表中的“语言-国家/地区”。)
这可能吗?最简单的方法是什么?当然,最好只使用JDK标准库,但如果不可能,也可以使用第三方库。

63lcw9qa

63lcw9qa1#

您可以使用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中肯定有。作为构建级的东西,这不是一个巨大的负担。我希望避免手动维护列表。

1aaf6o9v

1aaf6o9v2#

由于开始看起来没有现成的Java解决方案来完成这种Map,我们花了大约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 
...

(You如果您有类似的需求,可以下载文件here。)
然后是一个非常简单的类,它将.properties文件中的信息读入Map,并有一个进行转换的方法。

Map<String, String> lcidToDescription;

public String getDescription(String lcid) { ... }

是的,这实际上并没有Map到 * 语言代码 * 或 *Locale对象 *(这是我最初问的),而是Map到微软的“语言-国家/地区”描述。
免责声明:这确实是一种最小化的、“虚拟的”方法,可以自己在Java中完成,而且显然在您自己的代码库中(和维护)LCIDMap信息的副本并不是很优雅。(另一方面,我也不想仅仅为了这个简单的Map而包含一个巨大的库jar或做任何过于复杂的事情。)所以尽管有这个答案,如果您知道类似的内容,请随时发布更优雅的解决方案或现有库

tquggr8v

tquggr8v3#

以下代码将以编程方式创建Microsoft LCID代码和Java语言环境之间的Map,从而更容易保持Map为最新:

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 1033Map到英语。

注意:这只会为运行时JVM上可用的语言环境生成Map。也就是说,您只会得到所有可能的语言环境的子集。也就是说,我认为示例化JVM不支持的语言环境在技术上是不可能的,所以这可能是我们所能做的最好的...

stszievb

stszievb4#

The was the first hit on google表示“Java LCID”,它是以下javadoc:

gnu.java.awt.font.opentype.NameDecoder

私有静态java.util.Locale获取Windows区域设置(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,所以应该不难找到。

o0lyfsai

o0lyfsai5#

下面是一个脚本,可以粘贴到F12控制台中,并提取当前273种语言到其lcid的Map(将在https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c上使用):

// extract data from https://learn.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
sbdsn5lh

sbdsn5lh6#

使用org.apache.poi.util.LocaleID类的Apache POI很容易做到这一点。
Java区域设置到LCID:

LocaleID.lookupByLanguageTag(new Locale("en", "US").toLanguageTag()).getLcid()

LCID到Java区域设置:

new Locale(LocaleID.lookupByLcid(1033).getLanguageTag())

相关问题