Cómo usar UTF-8 en propiedades de recursos con ResourceBundle

259

Necesito usar UTF-8 en mis propiedades de recursos usando Java ResourceBundle. Cuando ingreso el texto directamente en el archivo de propiedades, se muestra como mojibake.

Mi aplicación se ejecuta en Google App Engine.

¿Alguien puede darme un ejemplo? No puedo conseguir este trabajo.

nacho
fuente
1
Java 1.6 Se corrigió esto, ya que puede pasar en un lector. Vea la respuesta de @Chinaxing más abajo
será
1
@Will: la pregunta es principalmente sobre leerlos vía java.util.ResourceBundle, no java.util.Properties.
BalusC
1
Compruebe esta pregunta respondida ,,, espero que le ayude [ stackoverflow.com/questions/863838/… [1]: stackoverflow.com/questions/863838/…
Majdy el programador Bboy
66
JDK9 debería admitir UTF-8 de forma nativa, ver JEP 226
Paolo Fulgoni

Respuestas:

375

Los ResourceBundle#getBundle()usos debajo de las cubiertas PropertyResourceBundlecuando .propertiesse especifica un archivo. Esto a su vez usa de forma predeterminada Properties#load(InputStream)para cargar esos archivos de propiedades. Según el javadoc , se leen por defecto como ISO-8859-1.

public void load(InputStream inStream) throws IOException

Lee una lista de propiedades (pares de claves y elementos) de la secuencia de bytes de entrada. El flujo de entrada está en un formato simple orientado a línea como se especifica en load (Reader) y se supone que usa la codificación de caracteres ISO 8859-1 ; es decir, cada byte es un carácter latino1. Los caracteres que no están en latín1, y ciertos caracteres especiales, se representan en claves y elementos utilizando escapes Unicode como se define en la sección 3.3 de La especificación del lenguaje Java ™.

Por lo tanto, deberá guardarlos como ISO-8859-1. Si tiene caracteres más allá del rango ISO-8859-1 y no puede usar \uXXXXla parte superior de la cabeza y, por lo tanto, se ve obligado a guardar el archivo como UTF-8, entonces necesitará usar la herramienta native2ascii para convertir un El archivo de propiedades guardadas UTF-8 en un archivo de propiedades guardadas ISO-8859-1 en el que todos los caracteres descubiertos se convierten en \uXXXXformato. El siguiente ejemplo convierte un archivo de propiedades codificado UTF-8 text_utf8.propertiesen un archivo de propiedades codificado ISO-8859-1 válido text.properties.

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

Cuando se usa un IDE sano como Eclipse, esto ya se hace automáticamente cuando se crea un .propertiesarchivo en un proyecto basado en Java y se usa el propio editor de Eclipse. Eclipse convertirá de forma transparente los caracteres más allá del rango ISO-8859-1 al \uXXXXformato. Vea también las capturas de pantalla a continuación (observe las pestañas "Propiedades" y "Fuente" en la parte inferior, haga clic para ampliar):

Pestaña "Propiedades" Pestaña "Fuente"

Alternativamente, también puede crear una ResourceBundle.Controlimplementación personalizada en la que lea explícitamente los archivos de propiedades como UTF-8 InputStreamReader, de modo que pueda guardarlos como UTF-8 sin la necesidad de molestarse native2ascii. Aquí hay un ejemplo inicial:

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

Esto se puede usar de la siguiente manera:

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

Ver también:

BalusC
fuente
Gracias. Por cierto, parece una buena idea anular getFormats para devolver FORMAT_PROPERTIES.
Flávio Etrusco
¿Podría elaborar esta sugerencia para anular getFormats ()?
Mark Roper
1
@ imgx64: Gracias por notificarme. La respuesta ha sido corregida.
BalusC
10
No dude en usarlo StandardCharsets.UTF_8si está usando Java 7+
Niks
1
@Nyerguds: si ve razones para cambiarlo programáticamente (aunque no puedo imaginarlo de por vida), siéntase libre de hacerlo. Todos los fragmentos de código que publico son solo ejemplos de inicio después de todo.
BalusC
131

Dado que tiene una instancia de ResourceBundle y puede obtener String:

String val = bundle.getString(key); 

Resolví mi problema de visualización en japonés de la siguiente manera:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");
varilla
fuente
37
Para todos los votantes / comentaristas ingenuos aquí: esto no es una solución, sino una solución alternativa. El verdadero problema subyacente sigue en pie y necesita solución.
BalusC
2
Esto solucionó mi situación. La solución sería que Java comenzara a manejar UTF-8 de forma nativa en paquetes de recursos y en archivos de propiedades. Hasta que eso suceda, usaré una solución alternativa.
JohnRDOrazio
@BalusC; ¿Cuál es la desventaja de este enfoque? (¿aparte de crear una cadena adicional?)
Paaske
8
@Paaske: es una solución, no una solución. Debería volver a aplicar la solución alternativa en todo lugar en todas las variables de cadena a lo largo de la base del código. Esto es pura tontería. Simplemente arréglelo en un solo lugar, en el lugar correcto, de modo que las variables de cadena contengan inmediatamente el valor correcto. No debería haber absolutamente ninguna necesidad de modificar el cliente.
BalusC
3
Sí, si tiene que modificar toda la aplicación, por supuesto, esto es malo. Pero si ya está utilizando ResourceBundle como un singleton, solo tiene que arreglarlo una vez. Tenía la impresión de que el enfoque singleton era la forma más común de usar ResourceBundle.
Paaske
51

mira esto: http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)

las propiedades aceptan un objeto Reader como argumentos, que puede crear desde un InputStream.

en el momento de la creación, puede especificar la codificación del lector:

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

luego aplique este lector al método de carga:

prop.load(isr);

Por cierto: obtener la secuencia del archivo .properties :

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

Por cierto: obtenga el paquete de recursos de InputStreamReader:

ResourceBundle rb = new PropertyResourceBundle(isr);

Espero que esto le pueda ayudar !

Chinaxing
fuente
3
Sin ResourceBundleembargo, la pregunta real aquí es sobre .
Nyerguds
1
Es cierto que esta respuesta debe aceptarse si está utilizando Propertiesy desea recuperar UTF-8String, entonces esto funciona como un encanto. Sin embargo, para ResourceBundlerecursos lingüísticos, la respuesta aceptada es elegante. Sin embargo, votó la respuesta.
Ilgıt Yıldırım
ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
dedek
23

ResourceBundle.Control con UTF-8 y los nuevos métodos de cadena no funcionan, si el archivo de propiedades usa el juego de caracteres cp1251, por ejemplo.

Así que recomendé usar un método común: escribir en símbolos unicode . Para esto:

IDEA - tiene una especial " Transparente conversión nativa a ASCII " opción (Ajustes> Codificación de archivos).

Eclipse : tiene un complemento " Editor de propiedades " . Puede funcionar como aplicación separada.

Kinjeiro
fuente
44
En IntelliJ IDEA 14, esto se encuentra en Configuración -> Editor -> Codificación de archivos. También tuve que eliminar los archivos de propiedades existentes y volver a crearlos para que esta opción surta efecto.
Cypher
Los IDE no son particularmente relevantes para la respuesta, sino solo herramientas que realmente no abordan el problema subyacente de no almacenar contenido en el conjunto de caracteres UTF-8 ... lo que resolvería el problema de inmediato sin conversión o piratería como escribir propiedades en símbolos unicode dentro de un archivo definido con un conjunto de caracteres diferente.
Darrell Teague el
21

Este problema finalmente se ha solucionado en Java 9: https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9

La codificación predeterminada para los archivos de propiedades ahora es UTF-8.

La mayoría de los archivos de propiedades existentes no deberían verse afectados: UTF-8 e ISO-8859-1 tienen la misma codificación para caracteres ASCII, y la codificación ISO-8859-1 no ASCII legible por humanos no es UTF-8 válida. Si se detecta una secuencia de bytes UTF-8 no válida, el tiempo de ejecución de Java vuelve a leer automáticamente el archivo en ISO-8859-1.

stenix
fuente
19

Creamos un archivo resources.utf8 que contiene los recursos en UTF-8 y tenemos una regla para ejecutar lo siguiente:

native2ascii -encoding utf8 resources.utf8 resources.properties
andykellr
fuente
¿De dónde venimos native2ascii? Simplemente lo hice find / -name native2ascii*y no obtuve resultados, así que supongo que no es solo parte del JDK ...
ArtOfWarfare
Hm. No es parte de IBM JDK, pero parece estar incluido en Oracle JDK, en jdk1.*.0_*/bin.
ArtOfWarfare
Parece ser parte de IBM JDK, al menos en 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);  
            }  
        }  
    }  
}  
marcolopes
fuente
1
Me gusta esta solución y la publico
Sllouyssgort
Esto funciona muy bien Acabo de agregar un archivo de propiedades de traducción al chino en UTF8 y se carga sin ningún problema.
tresf
9

Atención: ¡los archivos de propiedades de Java deben estar codificados en ISO 8859-1!

Codificación de caracteres ISO 8859-1. Los caracteres que no se pueden representar directamente en esta codificación se pueden escribir utilizando escapes Unicode; solo se permite un solo carácter 'u' en una secuencia de escape.

@ver propiedades Java Doc

Si todavía desea hacer esto: eche un vistazo a: Propiedades de Java UTF-8 codificación en Eclipse : hay algunos ejemplos de código

Ralph
fuente
1
Java! = Eclipse ... este último es un IDE. Más datos! = Java. Java admite el procesamiento continuo utilizando una amplia gama de conjuntos de caracteres, que para la internacionalización (la pregunta es sobre ResourceBundles después de todo) ... resuelve usar UTF-8 como la respuesta más directa. Escribir archivos de propiedades en un juego de caracteres no admitido por el idioma de destino complica innecesariamente el problema.
Darrell Teague el
@Darell Teague: La "pista" de que un archivo de propiedad cargado para un ResouceBundle debe ser ISO 8859-1 es una declaración de Java: docs.oracle.com/javase/8/docs/api/java/util/… .. La segunda parte de mi respuesta es solo una "pista" de cómo lidiar con el problema del sombrero.
Ralph el
3

Aquí hay una solución Java 7 que utiliza la excelente biblioteca de soporte de Guava y la construcción de prueba con recursos. Lee y escribe archivos de propiedades utilizando UTF-8 para la experiencia general más simple.

Para leer un archivo de propiedades como 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
  }
}

Para escribir un archivo de propiedades como 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
fuente
Esta respuesta es útil. El problema central aquí con varias respuestas parece ser un malentendido acerca de los datos y los conjuntos de caracteres. Java puede leer cualquier información (correctamente) simplemente especificando el conjunto de caracteres en el que se almacenó como se muestra arriba. UTF-8 se usa comúnmente para admitir la mayoría de los idiomas del planeta, si no todos, y, por lo tanto, es muy aplicable a las propiedades basadas en ResourceBundle.
Darrell Teague el
@DarrellTeague: Bueno, "UTF-8 se usa comúnmente para soportar ..." - debería haber " Unicode se usa comúnmente para soportar ..." :) ya que UTF-8 es solo una codificación de caracteres de Unicode ( en .wikipedia.org / wiki / UTF-8 ).
Honza Zidek
En realidad, UTF-8 debía llamarse específicamente como "el conjunto de caracteres" (en lugar de hacer referencia a 'cualquier conjunto de caracteres UniCode') como UTF-8 en este contexto (datos) ha predominado el uso en Internet por algunas medidas tan altas como 67%. Ref: stackoverflow.com/questions/8509339/…
Darrell Teague el
3

Como uno sugirió, pasé por la implementación del paquete de recursos ... pero eso no ayudó ... ya que el paquete siempre se llamaba en en_US locale ... intenté establecer mi configuración regional predeterminada en un idioma diferente y aún así mi implementación del paquete de recursos Se estaba llamando al control con en_US ... intenté poner mensajes de registro y hacer un paso a través de la depuración y ver si se estaba haciendo una llamada local diferente después de cambiar la configuración regional en tiempo de ejecución a través de llamadas xhtml y JSF ... eso no sucedió ... luego traté de establecer un sistema predeterminado en utf8 para leer archivos de mi servidor (servidor tomcat) ... pero eso causó pronlem ya que todas mis bibliotecas de clases no se compilaron bajo utf8 y tomcat comenzó a leer en formato utf8. y el servidor no funcionaba correctamente ... luego terminé implementando un método en mi controlador java para que se llamara desde archivos xhtml ...en ese método hice lo siguiente:

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

Estaba particularmente nervioso ya que esto podría ralentizar el rendimiento de mi aplicación ... sin embargo, después de implementar esto, parece que mi aplicación es más rápida ahora ... creo que es porque ahora estoy accediendo directamente a las propiedades en lugar de permitir JSF analiza su camino para acceder a las propiedades ... específicamente paso el argumento booleano en esta llamada porque sé que algunas de las propiedades no se traducirán y no necesitan estar en formato utf8 ...

Ahora he guardado mi archivo de propiedades en formato UTF8 y funciona bien, ya que cada usuario en mi aplicación tiene una preferencia de referencia regional.

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

Por lo que vale la pena, mi problema fue que los archivos mismos estaban en la codificación incorrecta. Usar iconv funcionó para mí

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new
Zack Bartel
fuente
+1 por mencionar iconv. Nunca había oído hablar de él antes, pero lo escribí en la consola y he aquí que es algo que existe (en CentOS 6, de todos modos)
ArtOfWarfare
Sin embargo, ahora que he intentado usarlo, no funcionó: arrojó sobre el primer personaje que no se pudo convertir a ISO-8559-1.
ArtOfWarfare
1

Traté de usar el enfoque proporcionado por Rod, pero teniendo en cuenta la preocupación de BalusC por no repetir la misma solución en toda la aplicación, llegué a esta clase:

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

La forma de usar esto sería muy similar al uso regular de ResourceBundle:

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

O puede usar el constructor alternativo que usa UTF-8 por defecto:

private MyResourceBundle labels = new MyResourceBundle("es");
carlossierra
fuente
0

Abra el cuadro de diálogo Configuración / Preferencias ( Ctrl+ Alt+ S), luego haga clic en Editor y Codificación de archivos.

Captura de pantalla de la ventana mostrada

Luego, en la parte inferior, encontrará codificaciones predeterminadas para archivos de propiedades. Elige tu tipo de codificación.

Alternativamente, puede usar símbolos Unicode en lugar de texto en su paquete de recursos (por ejemplo, "ів"igual \u0456\u0432)

Юра Чорнота
fuente