Jak używać UTF-8 we właściwościach zasobów z ResourceBundle

259

Muszę użyć UTF-8 we właściwościach zasobów za pomocą Java ResourceBundle. Kiedy wprowadzam tekst bezpośrednio do pliku właściwości, wyświetla się on jako mojibake.

Moja aplikacja działa na Google App Engine.

Czy ktoś może dać mi przykład? Nie mogę dostać tej pracy.

nacho
źródło
1
Java 1.6 Naprawiono to, ponieważ można przekazać w czytniku. Zobacz odpowiedź @Chinaxing poniżej
Czy
1
@Will: pytanie dotyczy przede wszystkim ich czytania java.util.ResourceBundle, a nie java.util.Properties.
BalusC
1
Sprawdź pytanie, na które odpowiedziano ,,, mam nadzieję, że to ci pomoże [ stackoverflow.com/questions/863838/… [1]: stackoverflow.com/questions/863838/…
Majdy programista Bboy
6
JDK9 powinien wspierać UTF-8 natywnie, patrz JEP 226
Paolo Fulgoni

Odpowiedzi:

374

Do ResourceBundle#getBundle()zastosowania pod kołdrą PropertyResourceBundle, gdy .propertiesplik jest określona. To z kolei domyślnie używa Properties#load(InputStream)do ładowania tych plików właściwości. Zgodnie z javadoc są one domyślnie czytane jako ISO-8859-1.

public void load(InputStream inStream) throws IOException

Odczytuje listę właściwości (pary kluczy i elementów) z wejściowego strumienia bajtów. Strumień wejściowy jest w prostym formacie zorientowanym liniowo, określonym w load (Reader) i zakłada się, że wykorzystuje kodowanie znaków ISO 8859-1 ; to znaczy, że każdy bajt to jeden znak Latin1. Znaki spoza alfabetu łacińskiego 1 oraz niektóre znaki specjalne są reprezentowane w kluczach i elementach za pomocą znaków ucieczki Unicode zgodnie z definicją w sekcji 3.3 specyfikacji języka Java ™.

Musisz więc zapisać je jako ISO-8859-1. Jeśli masz jakieś znaki poza zakresem ISO-8859-1 i nie możesz użyć \uXXXXczubka głowy, a zatem jesteś zmuszony zapisać plik jako UTF-8, musisz użyć narzędzia native2ascii do konwersji Plik właściwości zapisanych w UTF-8 do pliku właściwości zapisanych w ISO-8859-1, w którym wszystkie odkryte znaki są konwertowane na \uXXXXformat. Poniższy przykład konwertuje plik właściwości zakodowany w UTF-8 text_utf8.propertiesna prawidłowy plik właściwości zakodowany w standardzie ISO-8859-1 text.properties.

native2ascii -encoding UTF-8 text_utf8.properties text.properties

Gdy używasz rozsądnego IDE, takiego jak Eclipse, jest to już automatycznie wykonywane, gdy tworzysz .propertiesplik w projekcie opartym na Javie i używasz własnego edytora Eclipse. Eclipse w sposób przezroczysty konwertuje znaki spoza zakresu ISO-8859-1 na \uXXXXformat. Zobacz także poniższe zrzuty ekranu (zwróć uwagę na zakładki „Właściwości” i „Źródło” u dołu, kliknij, aby wyświetlić duże):

Karta „Właściwości” Karta „Źródło”

Alternatywnie, możesz również utworzyć niestandardową ResourceBundle.Controlimplementację, w której bezpośrednio odczytujesz pliki właściwości jako UTF-8 InputStreamReader, dzięki czemu możesz po prostu zapisać je jako UTF-8 bez konieczności kłopotania się native2ascii. Oto przykład:

public class UTF8Control extends Control {
    public ResourceBundle newBundle
        (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException
    {
        // The below is a copy of the default implementation.
        String bundleName = toBundleName(baseName, locale);
        String resourceName = toResourceName(bundleName, "properties");
        ResourceBundle bundle = null;
        InputStream stream = null;
        if (reload) {
            URL url = loader.getResource(resourceName);
            if (url != null) {
                URLConnection connection = url.openConnection();
                if (connection != null) {
                    connection.setUseCaches(false);
                    stream = connection.getInputStream();
                }
            }
        } else {
            stream = loader.getResourceAsStream(resourceName);
        }
        if (stream != null) {
            try {
                // Only this line is changed to make it to read properties files as UTF-8.
                bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
            } finally {
                stream.close();
            }
        }
        return bundle;
    }
}

Można to wykorzystać w następujący sposób:

ResourceBundle bundle = ResourceBundle.getBundle("com.example.i18n.text", new UTF8Control());

Zobacz też:

BalusC
źródło
Dzięki. BTW wydaje się, że dobrym pomysłem jest przesłonięcie getFormats, aby zwrócić FORMAT_PROPERTIES.
Flávio Etrusco,
Czy możesz rozwinąć tę sugestię, aby zastąpić getFormats ()?
Mark Roper
1
@ imgx64: Dziękujemy za powiadomienie. Odpowiedź została naprawiona.
BalusC,
10
Nie wahaj się, StandardCharsets.UTF_8jeśli używasz Java 7+
Niks
1
@Nyerguds: jeśli widzisz powody, aby kiedykolwiek programowo to zmienić (nie mogę sobie tego wyobrazić), możesz to zrobić. Wszystkie fragmenty kodu, które publikuję, są w końcu tylko przykładami.
BalusC
131

Biorąc pod uwagę, że masz instancję ResourceBundle i możesz uzyskać ciąg znaków przez:

String val = bundle.getString(key); 

Rozwiązałem mój japoński problem z wyświetlaniem poprzez:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");
Pręt
źródło
36
Do wszystkich naiwnych upvoters / komentujących tutaj: to nie jest rozwiązanie, ale obejście. Prawdziwy problem leży nadal i wymaga rozwiązania.
BalusC,
2
To naprawiło moją sytuację. Rozwiązaniem byłoby, aby Java zaczęła obsługiwać UTF-8 natywnie w pakietach zasobów i plikach właściwości. Do tego czasu skorzystam z obejścia.
JohnRDOrazio
@BalusC; jaka jest wada tego podejścia? (inne niż tworzenie dodatkowego ciągu?)
Paaske,
8
@Paaske: to obejście, a nie rozwiązanie. Będziesz musiał ponownie zastosować obejście dla całego miejsca dla wszystkich zmiennych łańcuchowych w całej bazie kodu. To czysty nonsens. Po prostu napraw to w jednym miejscu, we właściwym miejscu, aby zmienne łańcuchowe natychmiast zawierały odpowiednią wartość. Nie powinno być absolutnie żadnej potrzeby modyfikowania klienta.
BalusC,
3
Tak, jeśli musisz zmodyfikować całą aplikację, to oczywiście źle. Ale jeśli już używasz ResourceBundle jako singletonu, musisz go naprawić tylko raz. Miałem wrażenie, że podejście singletonowe było najczęstszym sposobem korzystania z ResourceBundle.
Paaske
50

Spójrz na to : http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)

właściwości akceptują Czytnika obiekt jako argumenty, które można utworzyć z InputStream.

w czasie tworzenia możesz określić kodowanie Czytnika:

InputStreamReader isr = new InputStreamReader(stream, "UTF-8");

następnie zastosuj ten czytnik do metody ładowania:

prop.load(isr);

BTW: pobierz strumień z pliku .properties :

 InputStream stream = this.class.getClassLoader().getResourceAsStream("a.properties");

BTW: pobierz pakiet zasobów od InputStreamReader:

ResourceBundle rb = new PropertyResourceBundle(isr);

mam nadzieję, że to może ci pomóc!

Chinaxing
źródło
3
Właściwe pytanie tutaj dotyczy ResourceBundle.
Nyerguds
1
To prawda, że ​​należy zaakceptować odpowiedź, jeśli używasz Propertiesi chcesz odzyskać UTF-8String, to działa jak urok. Jednak w przypadku ResourceBundletakich zasobów językowych akceptowana odpowiedź jest elegancka. Niemniej jednak głosował odpowiedź.
Ilgıt Yıldırım
ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
dedek
22

ResourceBundle.Control z UTF-8 i nowymi metodami String nie działają, jeśli na przykład plik właściwości używa zestawu znaków cp1251.

Dlatego polecam, używając wspólnej metody: pisz w symbolach Unicode . Dla tego:

IDEA - posiada specjalną opcję Przezroczysta konwersja natywnego na ASCII (Ustawienia> Kodowanie pliku).

Eclipse - ma wtyczkęEdytor właściwości . Może działać jako osobna aplikacja.

Kinjeiro
źródło
3
W IntelliJ IDEA 14 znajduje się w Ustawienia -> Edytor -> Kodowanie plików. Musiałem także usunąć wszystkie istniejące pliki właściwości i ponownie je utworzyć, aby ta opcja zaczęła obowiązywać.
Cypher
IDE nie są szczególnie istotne dla odpowiedzi, ale tylko narzędzia, które tak naprawdę nie rozwiązują podstawowego problemu polegającego na nieprzechowywaniu zawartości w zestawie znaków UTF-8 .... które rozwiązałyby problem natychmiast bez konwersji lub włamań, takich jak pisanie właściwości w symbolach Unicode w pliku zdefiniowanym za pomocą innego zestawu znaków.
Darrell Teague,
21

Ten problem został wreszcie rozwiązany w Javie 9: https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9

Domyślne kodowanie plików właściwości to teraz UTF-8.

Nie powinno to mieć wpływu na większość istniejących plików właściwości: UTF-8 i ISO-8859-1 mają to samo kodowanie znaków ASCII, a kodowanie ISO-8859-1 czytelne dla człowieka nie jest prawidłowe UTF-8. W przypadku wykrycia nieprawidłowej sekwencji bajtów UTF-8 środowisko wykonawcze Java automatycznie ponownie odczytuje plik w ISO-8859-1.

feniks
źródło
19

Tworzymy plik resources.utf8, który zawiera zasoby w UTF-8 i mamy regułę do uruchamiania następujących czynności:

native2ascii -encoding utf8 resources.utf8 resources.properties
andykellr
źródło
Skąd my czerpiemy native2ascii? Właśnie to zrobiłem find / -name native2ascii*i nie otrzymałem żadnych wyników, więc zakładam, że to nie tylko część JDK ...
ArtOfWarfare
Hm Nie jest częścią pakietu IBM JDK, ale wydaje się, że jest zawarty w pakiecie Oracle JDK, w jdk1.*.0_*/bin.
ArtOfWarfare
Wygląda na to, że jest częścią IBM JDK, przynajmniej w JDK 6.
Eric Finn
19
package com.varaneckas.utils;  

import java.io.UnsupportedEncodingException;  
import java.util.Enumeration;  
import java.util.PropertyResourceBundle;  
import java.util.ResourceBundle;  

/** 
 * UTF-8 friendly ResourceBundle support 
 *  
 * Utility that allows having multi-byte characters inside java .property files. 
 * It removes the need for Sun's native2ascii application, you can simply have 
 * UTF-8 encoded editable .property files. 
 *  
 * Use:  
 * ResourceBundle bundle = Utf8ResourceBundle.getBundle("bundle_name"); 
 *  
 * @author Tomas Varaneckas <[email protected]> 
 */  
public abstract class Utf8ResourceBundle {  

    /** 
     * Gets the unicode friendly resource bundle 
     *  
     * @param baseName 
     * @see ResourceBundle#getBundle(String) 
     * @return Unicode friendly resource bundle 
     */  
    public static final ResourceBundle getBundle(final String baseName) {  
        return createUtf8PropertyResourceBundle(  
                ResourceBundle.getBundle(baseName));  
    }  

    /** 
     * Creates unicode friendly {@link PropertyResourceBundle} if possible. 
     *  
     * @param bundle  
     * @return Unicode friendly property resource bundle 
     */  
    private static ResourceBundle createUtf8PropertyResourceBundle(  
            final ResourceBundle bundle) {  
        if (!(bundle instanceof PropertyResourceBundle)) {  
            return bundle;  
        }  
        return new Utf8PropertyResourceBundle((PropertyResourceBundle) bundle);  
    }  

    /** 
     * Resource Bundle that does the hard work 
     */  
    private static class Utf8PropertyResourceBundle extends ResourceBundle {  

        /** 
         * Bundle with unicode data 
         */  
        private final PropertyResourceBundle bundle;  

        /** 
         * Initializing constructor 
         *  
         * @param bundle 
         */  
        private Utf8PropertyResourceBundle(final PropertyResourceBundle bundle) {  
            this.bundle = bundle;  
        }  

        @Override  
        @SuppressWarnings("unchecked")  
        public Enumeration getKeys() {  
            return bundle.getKeys();  
        }  

        @Override  
        protected Object handleGetObject(final String key) {  
            final String value = bundle.getString(key);  
            if (value == null)  
                return null;  
            try {  
                return new String(value.getBytes("ISO-8859-1"), "UTF-8");  
            } catch (final UnsupportedEncodingException e) {  
                throw new RuntimeException("Encoding not supported", e);  
            }  
        }  
    }  
}  
marcolopy
źródło
1
Lubię to rozwiązanie i umieszczam je jak Gist gist.github.com/enginer/3168dd4a374994718f0e
Sllouyssgort
To działa bardzo dobrze. Właśnie dodałem plik właściwości tłumaczenia chińskiego w UTF8 i ładuje się bez żadnych problemów.
tresf
9

Uwaga: pliki właściwości java powinny być zakodowane w ISO 8859-1!

Kodowanie znaków ISO 8859-1. Znaki, które nie mogą być bezpośrednio reprezentowane w tym kodowaniu, mogą być pisane przy pomocy znaków ucieczki Unicode; tylko jeden znak „u” jest dozwolony w sekwencji ucieczki.

@ patrz Właściwości Java Doc

Jeśli nadal naprawdę chcesz to zrobić: spójrz na: Właściwości Java Kodowanie UTF-8 w Eclipse - istnieje kilka przykładów kodu

Ralph
źródło
1
Java! = Eclipse ... ten ostatni jest IDE. Dalsze dane! = Java. Java obsługuje przetwarzanie strumieniowe przy użyciu szerokiej gamy zestawów znaków, które do internacjonalizacji (w końcu chodzi o ResourceBundles) ... postanawia użyć UTF-8 jako najprostszej odpowiedzi. Zapisywanie plików właściwości w zestawie znaków nieobsługiwanym przez język docelowy niepotrzebnie komplikuje problem.
Darrell Teague,
@Darell Teague: „Podpowiedź”, że powinien być plik właściwości załadowany dla ResouceBundle, to ISO 8859-1 to oświadczenie java: docs.oracle.com/javase/8/docs/api/java/util/… .. Druga część mojej odpowiedzi to tylko „wskazówka”, jak poradzić sobie z problemem kapelusza.
Ralph
3

Oto rozwiązanie Java 7, które wykorzystuje doskonałą bibliotekę wsparcia Guava i konstrukcję try-with-resources. Odczytuje i zapisuje pliki właściwości za pomocą UTF-8 dla najprostszego ogólnego doświadczenia.

Aby odczytać plik właściwości jako UTF-8:

File file =  new File("/path/to/example.properties");

// Create an empty set of properties
Properties properties = new Properties();

if (file.exists()) {

  // Use a UTF-8 reader from Guava
  try (Reader reader = Files.newReader(file, Charsets.UTF_8)) {
    properties.load(reader);
  } catch (IOException e) {
    // Do something
  }
}

Aby zapisać plik właściwości jako UTF-8:

File file =  new File("/path/to/example.properties");

// Use a UTF-8 writer from Guava
try (Writer writer = Files.newWriter(file, Charsets.UTF_8)) {
  properties.store(writer, "Your title here");
  writer.flush();
} catch (IOException e) {
  // Do something
}
Gary Rowe
źródło
Ta odpowiedź jest przydatna. Podstawowym problemem tutaj z różnymi odpowiedziami wydaje się być nieporozumienie dotyczące danych i zestawów znaków. Java może odczytać dowolne dane (poprawnie), po prostu określając zestaw znaków, w którym zostały zapisane, jak pokazano powyżej. UTF-8 jest powszechnie używany do obsługi większości, jeśli nie każdego języka na planecie, i dlatego ma bardzo duże zastosowanie do właściwości opartych na ResourceBundle.
Darrell Teague,
@DarrellTeague: Cóż, „UTF-8 jest powszechnie używany do obsługi ...” - powinno raczej być „ Unicode jest powszechnie używany do obsługi ...” :) ponieważ UTF-8 jest po prostu kodowaniem znaków Unicode ( en .wikipedia.org / wiki / UTF-8 ).
Honza Zidek
W rzeczywistości UTF-8 miał być konkretnie nazywany „zestawem znaków” (zamiast po prostu odwoływać się do „dowolnego zestawu znaków UniCode”), ponieważ UTF-8 w tym kontekście (dane) dominuje użycie w Internecie pod pewnymi miarami 67%. Ref: stackoverflow.com/questions/8509339/…
Darrell Teague
3

Jak sugerowano, przeszedłem implementację pakietu zasobów ... ale to nie pomogło .. ponieważ pakiet zawsze był wywoływany w ustawieniach regionalnych en_US ... próbowałem ustawić moje domyślne ustawienia regionalne na inny język, a mimo to moją implementację pakietu zasobów kontrola była wywoływana za pomocą en_US ... próbowałem umieścić komunikaty w dzienniku i zrobić krok przez debugowanie i sprawdzić, czy po zmianie ustawień regionalnych w czasie wykonywania przez xhtml i JSF wykonano inne lokalne połączenie ... to się nie stało ... potem próbowałem zrobić zestaw systemowy ustawiony domyślnie na utf8 do odczytu plików przez mój serwer (serwer tomcat) .. ale to spowodowało zaimek, ponieważ wszystkie moje biblioteki klas nie zostały skompilowane pod utf8 i tomcat zaczął czytać w formacie utf8 a serwer nie działał poprawnie ... skończyłem z implementacją metody w moim kontrolerze Java, która ma być wywoływana z plików xhtml ..w tej metodzie wykonałem następujące czynności:

        public String message(String key, boolean toUTF8) throws Throwable{
            String result = "";
            try{
                FacesContext context = FacesContext.getCurrentInstance();
                String message = context.getApplication().getResourceBundle(context, "messages").getString(key);

                result = message==null ? "" : toUTF8 ? new String(message.getBytes("iso8859-1"), "utf-8") : message;
            }catch(Throwable t){}
            return result;
        }

Byłem szczególnie zdenerwowany, ponieważ może to spowolnić działanie mojej aplikacji ... jednak po jej wdrożeniu wygląda to tak, jakby moja aplikacja była teraz szybsza. Myślę, że dzieje się tak, ponieważ teraz bezpośrednio uzyskuję dostęp do właściwości, a nie pozwalam JSF analizuje sposób uzyskiwania dostępu do właściwości ... w tym wywołaniu przekazuję argument boolowski, ponieważ wiem, że niektóre właściwości nie zostałyby przetłumaczone i nie muszą być w formacie utf8 ...

Teraz zapisałem plik właściwości w formacie UTF8 i działa dobrze, ponieważ każdy użytkownik w mojej aplikacji ma preferencyjne ustawienia regionalne.

Masoud
źródło
2
Properties prop = new Properties();
String fileName = "./src/test/resources/predefined.properties";
FileInputStream inputStream = new FileInputStream(fileName);
InputStreamReader reader = new InputStreamReader(inputStream,"UTF-8");
Вассесуарий Пупочкин
źródło
1

Warto zwrócić uwagę na to, że same pliki miały niewłaściwe kodowanie. Korzystanie z iconv działało dla mnie

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new
Zack Bartel
źródło
+1 za wzmiankę iconv. Nigdy wcześniej o nim nie słyszałem, ale wpisałem go w konsoli i oto, oto rzecz, która istnieje (w każdym razie w CentOS 6).
ArtOfWarfare
Teraz, gdy faktycznie próbowałem go użyć, nie zadziałało: zwymiotował na pierwszą postać, której nie można przekonwertować na ISO-8559-1.
ArtOfWarfare
1

Próbowałem zastosować podejście dostarczone przez Rod, ale biorąc pod uwagę obawy BalusC o to, że nie powtarza się tego samego obejścia we wszystkich aplikacjach, otrzymałem tę klasę:

import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.ResourceBundle;

public class MyResourceBundle {

    // feature variables
    private ResourceBundle bundle;
    private String fileEncoding;

    public MyResourceBundle(Locale locale, String fileEncoding){
        this.bundle = ResourceBundle.getBundle("com.app.Bundle", locale);
        this.fileEncoding = fileEncoding;
    }

    public MyResourceBundle(Locale locale){
        this(locale, "UTF-8");
    }

    public String getString(String key){
        String value = bundle.getString(key); 
        try {
            return new String(value.getBytes("ISO-8859-1"), fileEncoding);
        } catch (UnsupportedEncodingException e) {
            return value;
        }
    }
}

Sposób użycia tego byłby bardzo podobny do zwykłego użycia ResourceBundle:

private MyResourceBundle labels = new MyResourceBundle("es", "UTF-8");
String label = labels.getString(key)

Lub możesz użyć alternatywnego konstruktora, który domyślnie używa UTF-8:

private MyResourceBundle labels = new MyResourceBundle("es");
carlossierra
źródło
0

Otwórz okno dialogowe Ustawienia / Preferencje ( Ctrl+ Alt+ S), a następnie kliknij opcję Edytor i kodowanie plików.

Zrzut ekranu pokazanego okna

Następnie na dole wskażesz domyślne kodowanie plików właściwości. Wybierz typ kodowania.

Alternatywnie możesz użyć symboli Unicode zamiast tekstu w pakiecie zasobów (na przykład "ів"równa się \u0456\u0432)

Юра Чорнота
źródło