Como usar UTF-8 em propriedades de recurso com ResourceBundle

259

Preciso usar UTF-8 nas propriedades de meus recursos usando Java ResourceBundle. Quando insiro o texto diretamente no arquivo de propriedades, ele é exibido como mojibake.

Meu aplicativo é executado no Google App Engine.

Alguém pode me dar um exemplo? Eu não consigo esse trabalho.

nacho
fonte
1
Java 1.6 Corrigido isso como você pode passar em um Reader. Veja a @Chinaxing caminho resposta abaixo
Will
1
@ Will: pergunta é principalmente sobre lê-los via java.util.ResourceBundle, não java.util.Properties.
BalusC
1
Marque esta pergunta respondida ,, espero que ajude você [ stackoverflow.com/questions/863838/… [1]: stackoverflow.com/questions/863838/…
Majdy, o programador Bboy
6
O JDK9 deve suportar UTF-8 nativamente, consulte JEP 226
Paolo Fulgoni

Respostas:

375

Os ResourceBundle#getBundle()usos ocultos PropertyResourceBundlequando um .propertiesarquivo é especificado. Por sua vez, isso é usado por padrão Properties#load(InputStream)para carregar esses arquivos de propriedades. De acordo com o javadoc , eles são lidos por padrão como ISO-8859-1.

public void load(InputStream inStream) throws IOException

Lê uma lista de propriedades (pares de chave e elemento) do fluxo de bytes de entrada. O fluxo de entrada está em um formato orientado a linha simples, conforme especificado em load (Reader) e é assumido que ele usa a codificação de caracteres ISO 8859-1 ; cada byte é um caractere Latin1. Caracteres que não estão no Latin1, e certos caracteres especiais, são representados em chaves e elementos usando escapes Unicode, conforme definido na seção 3.3 da Especificação da linguagem Java ™.

Portanto, você precisará salvá-los como ISO-8859-1. Se você tiver algum caractere além do intervalo ISO-8859-1 e não puder usá- \uXXXXlo em cima da cabeça e for forçado a salvar o arquivo como UTF-8, precisará usar a ferramenta native2ascii para converter um arquivo. Arquivo de propriedades salvas UTF-8 em um arquivo de propriedades salvas ISO-8859-1, em que todos os caracteres descobertos são convertidos em \uXXXXformato. O exemplo abaixo converte um arquivo de propriedades codificado em UTF-8 text_utf8.propertiesem um arquivo de propriedades codificado em ISO-8859-1 válido text.properties.

native2ascii - codificação UTF-8 text_utf8.properties text.properties

Ao usar um IDE sensato como o Eclipse, isso já é feito automaticamente quando você cria um .propertiesarquivo em um projeto baseado em Java e usa o próprio editor do Eclipse. O Eclipse converterá de forma transparente os caracteres além do intervalo ISO-8859-1 em \uXXXXformato. Veja também as capturas de tela abaixo (observe as guias "Propriedades" e "Origem" na parte inferior, clique para ampliar):

Guia "Propriedades" Guia "Origem"

Como alternativa, você também pode criar uma ResourceBundle.Controlimplementação customizada na qual você lê explicitamente os arquivos de propriedades como UTF-8 InputStreamReader, para que você possa salvá-los como UTF-8 sem a necessidade de se preocupar com isso native2ascii. Aqui está um exemplo de kickoff:

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

Isso pode ser usado da seguinte maneira:

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

Veja também:

BalusC
fonte
Obrigado. BTW, parece ser uma boa idéia substituir getFormats para retornar FORMAT_PROPERTIES.
Flávio Etrusco
Você poderia elaborar essa sugestão para substituir getFormats ()?
Mark Roper
1
@ imgx64: Obrigado por notificar. A resposta foi corrigida.
BalusC
10
Não hesite em usar StandardCharsets.UTF_8se estiver usando Java 7+
Niks
1
@ Nyerguds: se você vir motivos para mudar programaticamente (embora eu não possa imaginar o resto da vida), fique à vontade para fazê-lo. Afinal, todos os trechos de código que eu posto são apenas exemplos de kickoff.
precisa saber é o seguinte
131

Dado que você possui uma instância do ResourceBundle e pode obter String:

String val = bundle.getString(key); 

Resolvi meu problema de exibição em japonês:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");
Cajado
fonte
37
Para todos os promotores / comentadores ingênuos aqui: esta não é uma solução, mas uma solução alternativa. O verdadeiro problema subjacente ainda permanece e precisa ser resolvido.
BalusC
2
Isso corrigiu minha situação. A solução seria o Java começar a manipular o UTF-8 nativamente em pacotes de recursos e em arquivos de propriedades. Até que isso aconteça, usarei uma solução alternativa.
precisa saber é o seguinte
@BalusC; qual é a desvantagem dessa abordagem? (excepto a criação de uma corda extra?)
Paaske
8
@ Paaske: é uma solução alternativa, não uma solução. Você precisaria aplicar novamente a solução alternativa em todos os lugares em todas as variáveis ​​de seqüência de caracteres ao longo da base de código. Isso é pura bobagem. Apenas conserte-o em um único lugar, no lugar certo, para que as variáveis ​​de string contenham imediatamente o valor correto. Não deve haver absolutamente nenhuma necessidade de modificar o cliente.
BalusC
3
Sim, se você precisar modificar o aplicativo inteiro, é claro que isso é ruim. Mas se você já estiver usando o ResourceBundle como um singleton, precisará corrigi-lo apenas uma vez. Fiquei com a impressão de que a abordagem singleton era a maneira mais comum de usar o ResourceBundle.
Paaske 18/11/2015
51

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

as propriedades aceitam um objeto Reader como argumentos, que você pode criar a partir de um InputStream.

no momento da criação, você pode especificar a codificação do Reader:

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

aplique este Reader ao método de carregamento:

prop.load(isr);

BTW: obtenha o fluxo do arquivo .properties :

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

BTW: obtenha o pacote de recursos de InputStreamReader:

ResourceBundle rb = new PropertyResourceBundle(isr);

espero que isso possa ajudá-lo!

Chinaxing
fonte
3
A questão real aqui é sobre ResourceBundle, no entanto.
Nyerguds
1
É verdade que essa resposta deve ser aceita se você estiver usando Propertiese quiser recuperar UTF-8String, pois isso funciona como um encanto. No entanto, para ResourceBundlerecursos como o idioma, a resposta aceita é elegante. No entanto, votou a resposta.
Ilgıt Yıldırım
ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
dedek
23

ResourceBundle.Control com UTF-8 e novos métodos String não funcionam, se o arquivo de propriedades usa cp1251 charset, por exemplo.

Por isso, recomendei usando um método comum: escreva em símbolos unicode . Por esta:

IDEA - possui uma opção especial " Conversão transparente de nativo para ASCII " (Configurações> Codificação de arquivo).

Eclipse - possui um plugin " Editor de Propriedades " . Pode funcionar como aplicativo separado.

Kinjeiro
fonte
4
No IntelliJ IDEA 14, ele está localizado em Configurações -> Editor -> Codificações de arquivo. Também tive que excluir todos os arquivos de propriedades existentes e recriá-los para que esta opção entre em vigor.
Cypher
Os IDEs não são particularmente relevantes para a resposta, mas apenas ferramentas que realmente não abordam o problema subjacente de não armazenar conteúdo no conjunto de caracteres UTF-8 ... em símbolos unicode dentro de um arquivo definido com um conjunto de caracteres diferente.
Darrell Teague
21

Este problema foi finalmente corrigido no Java 9: https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9

A codificação padrão para arquivos de propriedades agora é UTF-8.

A maioria dos arquivos de propriedades existentes não deve ser afetada: UTF-8 e ISO-8859-1 têm a mesma codificação para caracteres ASCII, e a codificação não ASCII ISO-8859-1 legível por humanos não é UTF-8 válida. Se uma sequência de bytes UTF-8 inválida for detectada, o tempo de execução Java relerá automaticamente o arquivo em ISO-8859-1.

stenix
fonte
19

Criamos um arquivo resources.utf8 que contém os recursos em UTF-8 e temos uma regra para executar o seguinte:

native2ascii -encoding utf8 resources.utf8 resources.properties
andykellr
fonte
De onde chegamos native2ascii? Eu apenas fiz find / -name native2ascii*e consegui nenhum resultado, então eu assumir que não é apenas uma parte do JDK ...
ArtOfWarfare
Hum. Não faz parte do IBM JDK, mas parece estar incluído no Oracle JDK, no jdk1.*.0_*/bin.
ArtOfWarfare 26/01
Parece fazer parte do IBM JDK, pelo menos no 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
fonte
1
Eu gosto desta solução e a publico
Sllouyssgort
Isso funciona muito bem. Acabei de adicionar um arquivo de propriedades de tradução para chinês no UTF8 e ele é carregado sem problemas.
tresf
9

Atenção: os arquivos de propriedade java devem ser codificados na ISO 8859-1!

Codificação de caracteres ISO 8859-1. Os caracteres que não podem ser representados diretamente nessa codificação podem ser gravados usando escapes Unicode; somente um único caractere 'u' é permitido em uma sequência de escape.

Propriedades do Java Document

Se você realmente deseja fazer isso: dê uma olhada em: Propriedades Java Codificação UTF-8 no Eclipse - existem algumas amostras de código

Ralph
fonte
1
Java! = Eclipse ... o último é um IDE. Dados adicionais! = Java. Java suporta o processamento de fluxo usando uma vasta gama de conjuntos de caracteres, que para internacionalização (afinal, a questão é sobre ResourceBundles) ... resolve usar o UTF-8 como a resposta mais direta. A gravação de arquivos de propriedades em um conjunto de caracteres não suportado pelo idioma de destino complica desnecessariamente o problema.
Darrell Teague
@ Darell Teague: A "dica" que um arquivo de propriedade carregado para um ResouceBundle deve ter é o ISO 8859-1 é uma instrução java: docs.oracle.com/javase/8/docs/api/java/util/… .. A segunda parte da minha resposta é apenas uma "dica" de como lidar com o problema do chapéu.
Ralph
3

Aqui está uma solução Java 7 que usa a excelente biblioteca de suporte do Guava e a construção try-with-resources. Ele lê e grava arquivos de propriedades usando UTF-8 para obter a experiência geral mais simples.

Para ler um arquivo de propriedades 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 escrever um arquivo de propriedades 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
fonte
Esta resposta é útil. O principal problema aqui, com várias respostas, parece ser um mal-entendido sobre dados e conjuntos de caracteres. Java pode ler qualquer dado (corretamente), simplesmente especificando o conjunto de caracteres em que foi armazenado, como mostrado acima. O UTF-8 é comumente usado para suportar a maioria, se não todos os idiomas do planeta, e, portanto, é muito aplicável às propriedades baseadas no ResourceBundle.
Darrell Teague
@DarrellTeague: Bem, "UTF-8 é comumente usado para oferecer suporte ..." - deveria haver " Unicode é comumente usado para oferecer suporte ..." :) como UTF-8 é apenas uma codificação de caracteres do Unicode ( pt .wikipedia.org / wiki / UTF-8 ).
Honza Zidek
Na verdade, o UTF-8 deveria ser especificamente chamado de "o conjunto de caracteres" (em vez de apenas referenciar 'qualquer conjunto de caracteres UniCode') como UTF-8 nesse contexto (dados) tem uso predominante na Internet em algumas medidas tão altas quanto 67%. Ref: stackoverflow.com/questions/8509339/...
Darrell Teague
3

Como sugerido, eu passei pela implementação do pacote de recursos .. mas isso não ajudou .. como o pacote sempre foi chamado em localidade en_US ... tentei definir meu local padrão por outro idioma e ainda assim minha implementação do pacote de recursos controle estava sendo chamado com en_US ... eu tentei colocar mensagens de log e fazer uma etapa através da depuração e ver se uma chamada local diferente estava sendo feita depois que eu mudei a localidade em tempo de execução através das chamadas xhtml e JSF ... que não aconteceram ... então eu tentei fazer um sistema definido como padrão para utf8 para ler arquivos pelo meu servidor (servidor tomcat) .. mas isso causou irregularidade porque todas as minhas bibliotecas de classes não foram compiladas em utf8 e o tomcat começou a ler em formato utf8 e o servidor não estava funcionando corretamente ... então acabei implementando um método no meu controlador java para ser chamado de arquivos xhtml ..Nesse método, fiz o seguinte:

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

Eu estava particularmente nervoso, pois isso poderia diminuir o desempenho do meu aplicativo ... no entanto, depois de implementá-lo, parece que meu aplicativo está mais rápido agora .. acho que é porque agora estou acessando diretamente as propriedades em vez de permitir O JSF analisa seu caminho para acessar propriedades ... eu passo especificamente o argumento booleano nesta chamada porque sei que algumas das propriedades não seriam traduzidas e não precisam estar no formato utf8 ...

Agora salvei meu arquivo de propriedades no formato UTF8 e ele está funcionando bem, pois cada usuário no meu aplicativo tem uma preferência de localidade referente.

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

Pelo que vale a pena, meu problema foi que os arquivos em si estavam na codificação errada. Usando iconv funcionou para mim

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new
Zack Bartel
fonte
+1 por mencionar iconv. Eu nunca ouvi falar disso antes, mas eu digitei no console e eis que, é uma coisa que existe (em CentOS 6, de qualquer maneira.)
ArtOfWarfare
Agora que realmente tentei usá-lo, não funcionou: vomitou no primeiro caractere que não pôde ser convertido para ISO-8559-1.
ArtOfWarfare 26/01
1

Tentei usar a abordagem fornecida por Rod, mas levando em consideração a preocupação do BalusC em não repetir a mesma solução alternativa em todos os aplicativos e viemos com esta 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;
        }
    }
}

A maneira de usar isso seria muito semelhante ao uso regular do ResourceBundle:

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

Ou você pode usar o construtor alternativo que usa UTF-8 por padrão:

private MyResourceBundle labels = new MyResourceBundle("es");
Carlossierra
fonte
0

Abra a caixa de diálogo Configurações / Preferências ( Ctrl+ Alt+ S) e clique em Editor e codificações de arquivo.

Captura de tela da janela mostrada

Então, na parte inferior, você encontrará as codificações padrão para os arquivos de propriedades. Escolha o seu tipo de codificação.

Como alternativa, você pode usar símbolos unicode em vez de texto em seu pacote de recursos (por exemplo, "ів"igual a \u0456\u0432)

Юра Чорнота
fonte