Comment utiliser UTF-8 dans les propriétés de ressource avec ResourceBundle

259

J'ai besoin d'utiliser UTF-8 dans mes propriétés de ressource en utilisant Java ResourceBundle. Lorsque j'entre le texte directement dans le fichier de propriétés, il s'affiche sous forme de mojibake.

Mon application s'exécute sur Google App Engine.

Quelqu'un peut-il me donner un exemple? Je ne peux pas obtenir ce travail.

nacho
la source
1
Java 1.6 Correction de cela car vous pouvez passer dans un lecteur. Voir la façon dont la réponse @Chinaxing bas
Will
1
@Will: la question est principalement de les lire via java.util.ResourceBundle, non java.util.Properties.
BalusC
1
Cochez cette question répondue ,,, j'espère que cela vous aidera [ stackoverflow.com/questions/863838/… [1]: stackoverflow.com/questions/863838/…
Majdy le programmeur Bboy
6
JDK9 devrait prendre en charge UTF-8 nativement, voir JEP 226
Paolo Fulgoni

Réponses:

375

Les ResourceBundle#getBundle()utilisations sous les couvertures PropertyResourceBundlelorsqu'un .propertiesfichier est spécifié. Ceci à son tour utilise par défaut Properties#load(InputStream)pour charger ces fichiers de propriétés. Selon le javadoc , ils sont par défaut lus comme ISO-8859-1.

public void load(InputStream inStream) throws IOException

Lit une liste de propriétés (paires de clés et d'éléments) dans le flux d'octets d'entrée. Le flux d'entrée est dans un format simple orienté ligne comme spécifié dans la charge (Reader) et est supposé utiliser le codage de caractères ISO 8859-1 ; c'est-à-dire que chaque octet est un caractère Latin1. Les caractères qui ne sont pas en Latin1 et certains caractères spéciaux sont représentés dans des clés et des éléments utilisant des échappements Unicode comme défini dans la section 3.3 de la spécification du langage Java ™.

Vous devez donc les enregistrer au format ISO-8859-1. Si vous avez des caractères au-delà de la plage ISO-8859-1 et que vous ne pouvez pas les utiliser \uXXXXde haut en bas et que vous êtes donc obligé d'enregistrer le fichier au format UTF-8, vous devrez utiliser l' outil native2ascii pour convertir un Fichier de propriétés enregistrées UTF-8 dans un fichier de propriétés enregistrées ISO-8859-1 dans lequel tous les caractères découverts sont convertis au \uXXXXformat. L'exemple ci-dessous convertit un fichier de propriétés codées UTF-8 text_utf8.propertiesen un fichier de propriétés codées ISO-8859-1 valide text.properties.

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

Lorsque vous utilisez un IDE sain comme Eclipse, cela se fait déjà automatiquement lorsque vous créez un .propertiesfichier dans un projet basé sur Java et utilisez le propre éditeur d'Eclipse. Eclipse convertira de manière transparente les caractères au-delà de la plage ISO-8859-1 au \uXXXXformat. Voir également les captures d'écran ci-dessous (notez les onglets "Propriétés" et "Source" en bas, cliquez pour agrandir):

Onglet "Propriétés" Onglet "Source"

Alternativement, vous pouvez également créer une ResourceBundle.Controlimplémentation personnalisée dans laquelle vous lisez explicitement les fichiers de propriétés au format UTF-8 InputStreamReader, afin de pouvoir simplement les enregistrer au format UTF-8 sans avoir à vous encombrer native2ascii. Voici un exemple de lancement:

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

Cela peut être utilisé comme suit:

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

Voir également:

BalusC
la source
Merci. BTW, il semble que ce soit une bonne idée de remplacer getFormats pour renvoyer FORMAT_PROPERTIES.
Flávio Etrusco
Pourriez-vous développer cette suggestion pour remplacer getFormats ()?
Mark Roper
1
@ imgx64: Merci d'avoir notifié. La réponse a été corrigée.
BalusC
10
N'hésitez pas à utiliser StandardCharsets.UTF_8si vous utilisez Java 7+
Niks
1
@Nyerguds: si vous voyez des raisons de le changer par programmation (je ne peux pas en imaginer une à vie cependant), n'hésitez pas à le faire. Après tout, tous les extraits de code que je publie ne sont que des exemples de lancement.
BalusC
131

Étant donné que vous disposez d'une instance de ResourceBundle et que vous pouvez obtenir String par:

String val = bundle.getString(key); 

J'ai résolu mon problème d'affichage japonais en:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");
Barre
la source
37
À tous les votants / commentateurs naïfs ici: ce n'est pas une solution, mais une solution de contournement. Le vrai problème sous-jacent est toujours d'actualité et doit être résolu.
BalusC
2
Cela a corrigé ma situation. La solution serait que Java commence à gérer UTF-8 en mode natif dans les ensembles de ressources et dans les fichiers de propriétés. Jusqu'à ce que cela se produise, j'utiliserai une solution de contournement.
JohnRDOrazio
@BalusC; quel est l'inconvénient de cette approche? (autre que la création d'une chaîne supplémentaire?)
Paaske
8
@Paaske: c'est une solution de contournement, pas une solution. Vous devez réappliquer la solution de contournement sur tous les emplacements de toutes les variables de chaîne dans la base de code. C'est un pur non-sens. Il suffit de le corriger en un seul endroit, au bon endroit pour que les variables de chaîne contiennent immédiatement la bonne valeur. Il ne devrait absolument pas être nécessaire de modifier le client.
BalusC
3
Oui, si vous devez modifier l'ensemble de l'application, c'est bien sûr mauvais. Mais si vous utilisez déjà le ResourceBundle comme singleton, vous ne devez le réparer qu'une seule fois. J'avais l'impression que l'approche singleton était la manière la plus courante d'utiliser le ResourceBundle.
Paaske
51

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

les propriétés acceptent un objet Reader comme arguments, que vous pouvez créer à partir d'un InputStream.

au moment de la création, vous pouvez spécifier l'encodage du Reader:

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

puis appliquez ce Reader à la méthode de chargement:

prop.load(isr);

BTW: récupérez le flux depuis le fichier .properties :

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

BTW: obtenez un ensemble de ressources à partir de InputStreamReader:

ResourceBundle rb = new PropertyResourceBundle(isr);

j'espère que cela peut vous aider!

Chinaxing
la source
3
La vraie question ici concerne ResourceBundlecependant.
Nyerguds
1
Certes, cette réponse devrait être acceptée si vous utilisez Propertieset que vous souhaitez récupérer UTF-8String alors cela fonctionne comme un charme. Cependant, pour une ResourceBundleressource telle que la langue, la réponse acceptée est élégante. Néanmoins, up a voté la réponse.
Ilgıt Yıldırım
ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
dedek
23

ResourceBundle.Control avec UTF-8 et les nouvelles méthodes String ne fonctionnent pas, si le fichier de propriétés utilise le jeu de caractères cp1251, par exemple.

J'ai donc recommandé d'utiliser une méthode commune: écrire en symboles unicode . Pour ça:

IDEA - dispose d'une option spéciale " Conversion native native vers ASCII transparente " (Paramètres> Encodage de fichier).

Eclipse - possède un plugin " Propriétés Editor " . Il peut fonctionner comme une application distincte.

Kinjeiro
la source
4
Dans IntelliJ IDEA 14, cela se trouve dans Paramètres -> Éditeur -> Encodages de fichiers. J'ai également dû supprimer tous les fichiers de propriétés existants et les recréer pour que cette option prenne effet.
Cypher
Les IDE ne sont pas particulièrement pertinents pour la réponse, mais seulement des outils qui ne résolvent vraiment pas le problème sous-jacent de ne pas stocker de contenu dans le jeu de caractères UTF-8 .... ce qui résoudrait le problème immédiatement sans conversion ou piratage comme l'écriture des propriétés en symboles unicode à l'intérieur d'un fichier défini avec un jeu de caractères différent.
Darrell Teague
21

Ce problème a finalement été corrigé dans Java 9: https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9

Le codage par défaut des fichiers de propriétés est désormais UTF-8.

La plupart des fichiers de propriétés existants ne devraient pas être affectés: UTF-8 et ISO-8859-1 ont le même codage pour les caractères ASCII, et le codage ISO-8859-1 non ASCII lisible par l'homme n'est pas UTF-8 valide. Si une séquence d'octets UTF-8 non valide est détectée, le runtime Java relit automatiquement le fichier dans ISO-8859-1.

sténix
la source
19

Nous créons un fichier resources.utf8 qui contient les ressources en UTF-8 et avons une règle pour exécuter ce qui suit:

native2ascii -encoding utf8 resources.utf8 resources.properties
andykellr
la source
D'où venons-nous native2ascii? Je viens de le faire find / -name native2ascii*et je n'ai obtenu aucun résultat, donc je suppose que cela ne fait pas seulement partie du JDK ...
ArtOfWarfare
Hm. Il ne fait pas partie du JDK IBM, mais il semble être inclus dans le JDK Oracle, en jdk1.*.0_*/bin.
ArtOfWarfare
Il semble faire partie du JDK IBM, au moins dans le 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
la source
1
J'aime cette solution et je la poste comme Gist gist.github.com/enginer/3168dd4a374994718f0e
Sllouyssgort
Cela fonctionne très bien. Vous venez d'ajouter un fichier de propriétés de traduction chinoise en UTF8 et il se charge sans aucun problème.
tresf
9

Attention: les fichiers de propriétés java doivent être encodés en ISO 8859-1!

Codage de caractères ISO 8859-1. Les caractères qui ne peuvent pas être directement représentés dans cet encodage peuvent être écrits en utilisant des échappements Unicode; un seul caractère «u» est autorisé dans une séquence d'échappement.

@see Properties Java Doc

Si vous voulez toujours vraiment le faire: jetez un œil à: Propriétés Java Encodage UTF-8 dans Eclipse - il y a quelques exemples de code

Ralph
la source
1
Java! = Eclipse ... ce dernier est un IDE. Autres données! = Java. Java prend en charge le traitement de flux à l'aide d'un vaste éventail de jeux de caractères, qui pour l'internationalisation (la question concerne les ResourceBundles après tout) ... résout l'utilisation de l'UTF-8 comme réponse la plus simple. L'écriture de fichiers de propriétés dans un jeu de caractères non pris en charge par la langue cible complique inutilement le problème.
Darrell Teague
@Darell Teague: Le "conseil" qu'un fichier de propriété chargé pour un ResouceBundle doit être est ISO 8859-1 est une déclaration java: docs.oracle.com/javase/8/docs/api/java/util/… .. La deuxième partie de ma réponse est juste un "indice" sur la façon de traiter le problème du chapeau.
Ralph
3

Voici une solution Java 7 qui utilise l'excellente bibliothèque de support de Guava et la construction try-with-resources. Il lit et écrit des fichiers de propriétés en utilisant UTF-8 pour l'expérience globale la plus simple.

Pour lire un fichier de propriétés au format 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
  }
}

Pour écrire un fichier de propriétés au format 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
la source
Cette réponse est utile. Le problème central ici avec diverses réponses semble être un malentendu sur les données et les jeux de caractères. Java peut lire toutes les données (correctement) en spécifiant simplement le jeu de caractères dans lequel elles ont été stockées comme indiqué ci-dessus. UTF-8 est couramment utilisé pour prendre en charge la plupart sinon la totalité des langues de la planète et est donc très applicable aux propriétés basées sur ResourceBundle.
Darrell Teague
@DarrellTeague: Eh bien, "UTF-8 est couramment utilisé pour prendre en charge ..." - il devrait plutôt y avoir " Unicode est couramment utilisé pour prendre en charge ..." :) car UTF-8 n'est qu'un codage de caractères de l'Unicode ( en .wikipedia.org / wiki / UTF-8 ).
Honza Zidek
En fait, UTF-8 était censé être spécifiquement appelé «le jeu de caractères» (par opposition à simplement référencer «tout jeu de caractères UniCode»), car UTF-8 dans ce contexte (les données) a prédominé l'utilisation sur Internet par certaines mesures aussi élevées que 67%. Ref: stackoverflow.com/questions/8509339/…
Darrell Teague
3

Comme on l'a suggéré, je suis passé par l'implémentation du bundle de ressources .. mais cela n'a pas aidé .. car le bundle était toujours appelé sous les paramètres régionaux en_US ... j'ai essayé de définir mes paramètres régionaux par défaut dans une langue différente et toujours mon implémentation du bundle de ressources le contrôle était appelé avec en_US ... j'ai essayé de mettre des messages de journal et de faire une étape de débogage et de voir si un appel local différent était en cours après avoir changé les paramètres régionaux au moment de l'exécution via des appels xhtml et JSF ... cela ne s'est pas produit ... puis j'ai essayé de faire un système défini par défaut sur un utf8 pour lire les fichiers par mon serveur (serveur tomcat) .. mais cela a provoqué un pronlem car toutes mes bibliothèques de classes n'étaient pas compilées sous utf8 et tomcat a commencé à lire ensuite au format utf8 et le serveur ne fonctionnait pas correctement ... alors j'ai fini par implémenter une méthode dans mon contrôleur java pour être appelée à partir de fichiers xhtml ..dans cette méthode, j'ai fait ce qui suit:

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

J'étais particulièrement nerveux car cela pourrait ralentir les performances de mon application ... cependant, après avoir implémenté cela, il semble que mon application est plus rapide maintenant .. je pense que c'est parce que, j'accède maintenant directement aux propriétés au lieu de laisser JSF analyse son chemin dans l'accès aux propriétés ... je passe spécifiquement l'argument booléen dans cet appel parce que je sais que certaines propriétés ne seraient pas traduites et n'ont pas besoin d'être au format utf8 ...

Maintenant, j'ai enregistré mon fichier de propriétés au format UTF8 et cela fonctionne bien car chaque utilisateur de mon application a une préférence locale de référence.

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

Pour ce que ça vaut mon problème, c'est que les fichiers eux-mêmes étaient mal encodés. Utiliser iconv a fonctionné pour moi

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new
Zack Bartel
la source
+1 pour mentionner iconv. Je n'en ai jamais entendu parler auparavant mais je l'ai tapé dans la console et voilà, c'est une chose qui existe (dans CentOS 6, en tout cas.)
ArtOfWarfare
Maintenant que j'ai essayé de l'utiliser, cela n'a pas fonctionné: il a provoqué le premier caractère qui n'a pas pu être converti en ISO-8559-1.
ArtOfWarfare
1

J'ai essayé d'utiliser l'approche fournie par Rod, mais en tenant compte de la préoccupation de BalusC de ne pas répéter la même solution de contournement dans toute l'application et je suis venu avec cette classe:

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 façon d'utiliser cela serait très similaire à l'utilisation régulière de ResourceBundle:

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

Ou vous pouvez utiliser le constructeur alternatif qui utilise UTF-8 par défaut:

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

Ouvrez la boîte de dialogue Paramètres / Préférences ( Ctrl+ Alt+ S), puis cliquez sur Éditeur et encodages de fichiers.

Capture d'écran de la fenêtre affichée

Ensuite, en bas, vous trouverez les encodages par défaut des fichiers de propriétés. Choisissez votre type d'encodage.

Vous pouvez également utiliser des symboles Unicode au lieu du texte dans votre ensemble de ressources (par exemple, "ів"égal \u0456\u0432)

Юра Чорнота
la source