2009-07-28 16 views
10

Devo tradurre un Microsoft locale ID, ad esempio 1033 (per l'inglese americano) in uno ISO 639 language code o direttamente in un'istanza Java Locale. (Modifica: o anche semplicemente nella "Lingua - Paese/Regione" nella tabella di Microsoft.)Come convertire Microsoft Locale ID (LCID) nel codice lingua o Locale oggetto in Java

È possibile, e qual è il modo più semplice? Preferibilmente utilizzando solo le librerie standard JDK, ovviamente, ma se ciò non è possibile, con una libreria di terze parti.

risposta

5

All'inizio sembra che non ci sia una soluzione Java pronta per eseguire questa mappatura, abbiamo impiegato circa 20 minuti per lanciare qualcosa di nostro, almeno per ora.

Abbiamo preso le informazioni dalla bocca del cavallo, cioè http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx, e copia-incollato (tramite Excel) in un .properties file in questo modo:

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

(È possibile scaricare il file here se avete necessità analoghe.)

Quindi c'è una classe molto semplice che legge le informazioni dal file .properties in una mappa e ha un metodo per eseguire la conversione.

Map<String, String> lcidToDescription; 

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

E sì, questo in realtà non la mappa di codice della lingua o Locale oggetto (che è quello che originariamente chiesto), ma a Microsoft "Lingua - Paese/Regione" descrizione. Si è scoperto che questo era sufficiente per il nostro attuale bisogno.

Disclaimer: questo è un modo minimalista, "fittizio" di farlo da soli in Java, e ovviamente mantenere (e mantenere) una copia delle informazioni di mappatura LCID nella propria base di codice non è molto elegante. (D'altra parte, non vorrei nemmeno includere un enorme contenitore di libreria o fare qualcosa di troppo complicato solo per questa semplice mappatura.) Quindi, nonostante questa risposta, sentiti libero di pubblicare soluzioni più eleganti o librerie esistenti se sai qualcosa come quello.

+0

Il mapping utilizzando nomi solo in inglese può causare problemi. Vedi questa risposta: http://stackoverflow.com/questions/958178/in-java-is-there-any-way-to-get-a-locale-given-its-display-name/958600#958600 – McDowell

+0

In generale è vero, ma nel nostro caso particolare quei nomi di lingua/paese in inglese vanno bene (stiamo estraendo alcune informazioni software da un database SCCM e vogliamo semplicemente qualcosa di più leggibile dei codici numerici) – Jonik

0

The was the first hit on google per "Java LCID" è questo javadoc:

gnu.java.awt.font.opentype.NameDecoder 

java.util.Locale private static 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. 

non sono sicuro dove andare a scaricare questa libreria, ma è GNU, quindi non dovrebbe essere troppo difficile da trovare.

+0

Abbiamo trovato anche questo, ma sembra una soluzione piuttosto subottimale per questo - è una "utility class che aiuta a decodificare i nomi dei caratteri * OpenType e TrueType *". E guardando la fonte, i metodi di conversione sembrano essere piuttosto carenti - sa solo come mappare un paio dei locali più comuni! – Jonik

+2

Ecco la fonte: http://classpath.sourcearchive.com/documentation/0.91/NameDecoder_8java-source.html Vedere il metodo "Esegui il mapping di un LCID di Windows in un ambiente Java" e notare il commento "FIXME: Questo è grossolanamente incompleto ". : P – Jonik

+0

Gah, non buono ... GNU in "crappy java implementation" shocka. – skaffman

4

È possibile utilizzare GetLocaleInfo per eseguire questa operazione (presupponendo che si stia utilizzando Windows (win2k +)).

Questo codice C++ viene illustrato come utilizzare la funzione:

#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; 
} 

Non ci vuole molto lavoro per trasformare questo in una chiamata JNA. (Suggerimento: emetti costanti come inte per trovare i loro valori.)

Esempio codice JNA:

Utilizzando JNI è un po 'più complicato, ma è gestibile per un compito relativamente banale.

Per lo meno, vorrei esaminare l'utilizzo di chiamate native per creare il database di conversione. Non sono sicuro che Windows abbia un modo per enumerare gli LCID, ma ci sarà sicuramente qualcosa in .Net. Come una cosa a livello di costruzione, questo non è un onere enorme. Vorrei evitare la manutenzione manuale della lista.

+0

Grazie! Nel nostro caso il codice deve essere eseguito su altre piattaforme (ad es. Linux) anche se le informazioni che gestiamo sono incentrate su Windows e provengono da un database SCCM. Ma forse in alcuni casi questa è l'opzione migliore: sono d'accordo sul fatto che non è carino dover mantenere i mapping in un file (anche se cambiano raramente). A proposito, se qualcuno ritiene di farlo utilizzando l'API di Windows, questo potrebbe essere d'aiuto: http://stackoverflow.com/questions/1000723/what-is-the-easiest-way-to-call-a-windows-kernel- funzione-da-java – Jonik

2

Il seguente codice di programmazione di creare una mappatura tra codici LCID Microsoft e locali Java, rendendo più facile mantenere la mappatura up-to-date:

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 dipendenze:

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

Utilizzo:

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

stamperà "Lingua: inglese".

Significato, mappe LCID 1033 alla lingua inglese.

NOTA: questo genera solo mapping per le locale disponibili sulla JVM di runtime. Significa che otterrai solo un sottoinsieme di tutti i Local possibili. Detto questo, non penso sia tecnicamente possibile creare Locales che la tua JVM non supporta, quindi questo è probabilmente il meglio che possiamo fare ...

Problemi correlati