Cách sử dụng UTF-8 trong thuộc tính tài nguyên với ResourceBundle


259

Tôi cần sử dụng UTF-8 trong các thuộc tính tài nguyên của mình bằng Java ResourceBundle. Khi tôi nhập văn bản trực tiếp vào tệp thuộc tính, nó sẽ hiển thị dưới dạng mojibake.

Ứng dụng của tôi chạy trên Google App Engine.

Bất cứ ai có thể cho tôi một ví dụ? Tôi không thể có được công việc này.


1
Java 1.6 Đã sửa lỗi này khi bạn có thể chuyển qua Trình đọc. Xem cách trả lời @Chinaxing bên dưới
Sẽ có

1
@ Will: câu hỏi chủ yếu là về việc đọc chúng qua java.util.ResourceBundle, không phải java.util.Properties.
BalusC

1
Kiểm tra câu hỏi đã trả lời này ,,, hy vọng nó sẽ giúp bạn [ stackoverflow.com/questions/863838/... [1]: stackoverflow.com/questions/863838/...
Majdy các lập trình viên Bboy

6
JDK9 nên hỗ trợ UTF-8 nguyên bản, xem JEP 226
Paolo Fulgoni

Câu trả lời:


375

Việc ResourceBundle#getBundle()sử dụng dưới vỏ bọc PropertyResourceBundlekhi một .propertiestập tin được chỉ định. Điều này lần lượt sử dụng theo mặc định Properties#load(InputStream)để tải các tệp thuộc tính. Theo javadoc , theo mặc định, chúng được đọc là ISO-8859-1.

public void load(InputStream inStream) throws IOException

Đọc danh sách thuộc tính (cặp khóa và phần tử) từ luồng byte đầu vào. Luồng đầu vào có định dạng hướng dòng đơn giản như được chỉ định trong tải (Reader) và được giả định sử dụng mã hóa ký tự ISO 8859-1 ; đó là mỗi byte là một ký tự Latin1. Các ký tự không phải bằng tiếng Latin1 và một số ký tự đặc biệt nhất định, được biểu thị bằng các khóa và thành phần bằng cách sử dụng ký tự Unicode như được định nghĩa trong phần 3.3 của Đặc tả ngôn ngữ Java ™.

Vì vậy, bạn cần lưu chúng dưới dạng ISO-8859-1. Nếu bạn có bất kỳ ký tự nào ngoài phạm vi ISO-8859-1 và bạn không thể sử dụng \uXXXXđầu trên và do đó bạn buộc phải lưu tệp dưới dạng UTF-8, thì bạn cần sử dụng công cụ gốc2ascii để chuyển đổi Tệp thuộc tính đã lưu UTF-8 thành tệp thuộc tính đã lưu ISO-8859-1 trong đó tất cả các ký tự không được phát hiện được chuyển đổi thành \uXXXXđịnh dạng. Ví dụ dưới đây chuyển đổi tệp thuộc tính được mã hóa UTF-8 thành tệp thuộc tính được mã hóa text_utf8.propertiesISO-8859-1 hợp lệ text.properties.

local2ascii -encoding UTF-8 text_utf8.properies text.properies

Khi sử dụng một IDE lành mạnh như Eclipse, điều này đã được thực hiện tự động khi bạn tạo một .propertiestệp trong dự án dựa trên Java và sử dụng trình soạn thảo riêng của Eclipse. Eclipse sẽ chuyển đổi một cách trong suốt các ký tự ngoài phạm vi ISO-8859-1 sang \uXXXXđịnh dạng. Xem thêm bên dưới ảnh chụp màn hình (lưu ý các tab "Thuộc tính" và "Nguồn" ở dưới cùng, nhấp cho lớn):

Tab "Thuộc tính" Tab "nguồn"

Ngoài ra, bạn cũng có thể tạo một ResourceBundle.Controltriển khai tùy chỉnh trong đó bạn đọc rõ ràng các tệp thuộc tính dưới dạng UTF-8 InputStreamReader, để bạn có thể lưu chúng dưới dạng UTF-8 mà không cần phải gặp rắc rối native2ascii. Đây là một ví dụ khởi động:

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

Điều này có thể được sử dụng như sau:

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

Xem thêm:


Cảm ơn. BTW có vẻ là một ý tưởng tốt để ghi đè getFormats để trả về FORMAT_PROPERTIES.
Flávio Etrusco

Bạn có thể giải thích về đề xuất này để ghi đè getFormats () không?
Đánh dấu Roper

1
@ imgx64: Cảm ơn bạn đã thông báo. Câu trả lời đã được sửa.
BalusC

10
Đừng ngần ngại sử dụng StandardCharsets.UTF_8nếu bạn đang sử dụng Java 7+
Niks

1
@Nyerguds: nếu bạn thấy lý do để thay đổi nó theo chương trình (mặc dù tôi không thể tưởng tượng được cuộc sống), hãy thoải mái làm điều đó. Tất cả các đoạn mã tôi đăng chỉ là ví dụ khởi động.
BalusC

131

Cho rằng bạn có một phiên bản của ResourceBundle và bạn có thể nhận Chuỗi bằng cách:

String val = bundle.getString(key); 

Tôi đã giải quyết vấn đề hiển thị tiếng Nhật của mình bằng cách:

return new String(val.getBytes("ISO-8859-1"), "UTF-8");

37
Đối với tất cả những người ủng hộ / bình luận ngây thơ ở đây: đây không phải là một giải pháp, mà là một cách giải quyết. Vấn đề thực sự tiềm ẩn vẫn còn và cần giải quyết.
BalusC

2
Điều này đã khắc phục tình trạng của tôi. Giải pháp sẽ là để Java bắt đầu xử lý UTF-8 nguyên bản trong các gói tài nguyên và trong các tệp thuộc tính. Cho đến khi điều đó xảy ra tôi sẽ sử dụng một cách giải quyết.
JohnRDOrazio

@BalusC; nhược điểm của phương pháp này là gì? (ngoài việc tạo thêm Chuỗi?)
Paaske

8
@Paaske: đó là một cách giải quyết, không phải là một giải pháp. Bạn cần phải áp dụng lại cách giải quyết trên tất cả các vị trí trên tất cả các biến chuỗi trong toàn bộ cơ sở mã. Điều này là vô nghĩa. Chỉ cần sửa nó ở một nơi duy nhất, đúng nơi để các biến chuỗi ngay lập tức chứa giá trị đúng. Hoàn toàn không cần phải sửa đổi máy khách.
BalusC

3
Có, nếu bạn phải sửa đổi toàn bộ ứng dụng, tất nhiên điều này là xấu. Nhưng nếu bạn đã sử dụng ResourceBundle dưới dạng đơn lẻ, bạn chỉ phải sửa nó một lần. Tôi có ấn tượng rằng cách tiếp cận đơn lẻ là cách phổ biến nhất để sử dụng ResourceBundle.
Paaske

51

nhìn vào đây: http://docs.oracle.com/javase/6/docs/api/java/util/ProperIES.html#load(java.io.Reader)

các thuộc tính chấp nhận một đối tượng Reader làm đối số mà bạn có thể tạo từ InputStream.

tại thời điểm tạo, bạn có thể chỉ định mã hóa của Reader:

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

sau đó áp dụng Reader này cho phương thức tải:

prop.load(isr);

BTW: lấy luồng từ tệp .properations :

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

BTW: nhận gói tài nguyên từ InputStreamReader:

ResourceBundle rb = new PropertyResourceBundle(isr);

Hy vọng điều này có thể giúp bạn !


3
Câu hỏi thực tế ở đây là về ResourceBundle, mặc dù.
Nyerguds

1
Đúng, câu trả lời này phải được chấp nhận nếu bạn đang sử dụng Propertiesvà bạn muốn truy xuất UTF-8String thì điều này hoạt động như một cơ duyên. Tuy nhiên, đối với một ResourceBundletài nguyên ngôn ngữ như vậy thì câu trả lời được chấp nhận là thanh lịch. Tuy nhiên, đã bình chọn câu trả lời.
Ilgıt Yıldırım

ResourceBundle rb = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"))
khấu trừ

23

ResourceBundle.Control với UTF-8 và các phương thức Chuỗi mới không hoạt động, nếu tệp thuộc tính sử dụng bộ ký tự cp1251 chẳng hạn.

Vì vậy, tôi đã đề xuất sử dụng một phương pháp phổ biến: viết bằng các ký hiệu unicode . Đối với điều này:

IDEA - có tùy chọn " Chuyển đổi gốc trong suốt sang ASCII " đặc biệt (Cài đặt> Mã hóa tệp).

Eclipse - có một plugin " Trình soạn thảo thuộc tính " . Nó có thể hoạt động như một ứng dụng riêng biệt.


4
Trong IntelliJ IDEA 14, phần này nằm trong Cài đặt -> Trình chỉnh sửa -> Mã hóa tệp. Tôi cũng phải xóa bất kỳ tệp thuộc tính hiện có nào và tạo lại chúng để tùy chọn này có hiệu lực.
Cypher

IDE không liên quan đặc biệt đến câu trả lời mà chỉ là các công cụ thực sự không giải quyết được vấn đề tiềm ẩn là không lưu trữ nội dung trong bộ ký tự UTF-8 .... sẽ giải quyết vấn đề ngay lập tức mà không cần chuyển đổi hoặc hack như viết thuộc tính trong các ký hiệu unicode bên trong một tệp được xác định bằng một bộ ký tự khác.
Darrell Teague

21

Vấn đề này cuối cùng đã được sửa trong Java 9: https://docs.oracle.com/javase/9/intl/i quốc tế-eancancements-jdk- 9

Mã hóa mặc định cho các tệp thuộc tính hiện là UTF-8.

Hầu hết các tệp thuộc tính hiện có không bị ảnh hưởng: UTF-8 và ISO-8859-1 có cùng mã hóa cho các ký tự ASCII và mã hóa không phải ASCII ISO-8859-1 có thể đọc được của con người không phải là UTF-8 hợp lệ. Nếu phát hiện chuỗi byte UTF-8 không hợp lệ, thời gian chạy Java sẽ tự động đọc lại tệp trong ISO-8859-1.


19

Chúng tôi tạo một tệp resource.utf8 có chứa các tài nguyên trong UTF-8 và có một quy tắc để chạy như sau:

native2ascii -encoding utf8 resources.utf8 resources.properties

Chúng ta lấy native2asciitừ đâu? Tôi mới làm find / -name native2ascii*và không có kết quả, vì vậy tôi cho rằng nó không chỉ là một phần của JDK ...
ArtOfWarfare

Hừm. Nó không phải là một phần của JDK của IBM, nhưng dường như nó được bao gồm trong Oracle JDK jdk1.*.0_*/bin.
ArtOfWarfare

Nó dường như là một phần của JDK của IBM, ít nhất là trong 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 <tomas.varaneckas@gmail.com> 
 */  
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);  
            }  
        }  
    }  
}  

1
Tôi thích giải pháp này và tôi đăng nó như Gist gist.github.com/enginer/3168dd4a374994718f0e
Sllouyssgort

Điều này hoạt động rất tốt. Chỉ cần thêm một tệp thuộc tính dịch tiếng Trung trong UTF8 và nó tải lên mà không có vấn đề gì.
tresf

9

Chú ý: các tệp thuộc tính java phải được mã hóa theo ISO 8859-1!

Mã hóa ký tự ISO 8859-1. Các ký tự không thể được biểu diễn trực tiếp trong bảng mã này có thể được viết bằng cách thoát Unicode; chỉ một ký tự 'u' được phép trong một chuỗi thoát.

@see Thuộc tính Java Doc

Nếu bạn vẫn thực sự muốn làm điều này: hãy xem: Các thuộc tính Java mã hóa UTF-8 trong Eclipse - có một số mẫu mã


1
Java! = Eclipse ... cái sau là IDE. Dữ liệu khác! = Java. Java hỗ trợ xử lý luồng bằng cách sử dụng một loạt các bộ ký tự, để quốc tế hóa (câu hỏi là về ResourceBundles) ... quyết định sử dụng UTF-8 như một câu trả lời thẳng thắn nhất. Viết các tệp thuộc tính trong một bộ ký tự không được hỗ trợ bởi ngôn ngữ đích làm phức tạp vấn đề một cách không cần thiết.
Darrell Teague

@Darell Teague: "Gợi ý" rằng một tệp thích hợp được tải cho ResouceBundle phải là ISO 8859-1 là một tuyên bố java: docs.oracle.com/javase/8/docs/api/java/util/ . Phần thứ hai trong câu trả lời của tôi chỉ là "gợi ý" cách xử lý vấn đề mũ.
Ralph


3

Đây là một giải pháp Java 7 sử dụng thư viện hỗ trợ tuyệt vời của Guava và cấu trúc thử tài nguyên. Nó đọc và ghi các tệp thuộc tính bằng UTF-8 cho trải nghiệm tổng thể đơn giản nhất.

Để đọc tệp thuộc tính dưới dạng 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
  }
}

Để viết tệp thuộc tính dưới dạng 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
}

Câu trả lời này rất hữu ích. Vấn đề cốt lõi ở đây với các câu trả lời khác nhau dường như là một sự hiểu lầm về dữ liệu và bộ ký tự. Java có thể đọc bất kỳ dữ liệu nào (một cách chính xác) bằng cách chỉ định bộ ký tự mà nó được lưu trữ như được hiển thị ở trên. UTF-8 thường được sử dụng để hỗ trợ hầu hết nếu không phải mọi ngôn ngữ trên hành tinh và do đó được áp dụng rất nhiều cho các thuộc tính dựa trên ResourceBundle.
Darrell Teague

@DarrellTeague: Chà, "UTF-8 thường được sử dụng để hỗ trợ ..." - nên có " Unicode thường được sử dụng để hỗ trợ ..." :) vì UTF-8 chỉ là mã hóa ký tự của Unicode ( vi .wikipedia.org / wiki / UTF-8 ).
Honza Zidek

Trên thực tế UTF-8 có nghĩa được gọi cụ thể là "bộ ký tự" (thay vì chỉ tham chiếu 'bất kỳ bộ ký tự UniCode nào) như UTF-8 trong bối cảnh này (dữ liệu) chiếm ưu thế sử dụng trên Internet bằng một số biện pháp cao như 67%. Tham chiếu: stackoverflow.com/questions/8509339/ khăn
Darrell Teague

3

Như đã đề xuất, tôi đã thực hiện triển khai gói tài nguyên .. nhưng điều đó không giúp ích gì .. vì gói này luôn được gọi theo miền địa phương en_US ... tôi đã cố gắng đặt ngôn ngữ mặc định của mình sang một ngôn ngữ khác và vẫn triển khai gói tài nguyên của tôi Kiểm soát đã được gọi với en_US ... tôi đã cố gắng đặt thông điệp tường trình và thực hiện từng bước gỡ lỗi và xem liệu một cuộc gọi cục bộ khác có được thực hiện sau khi tôi thay đổi ngôn ngữ trong thời gian chạy thông qua các cuộc gọi xhtml và JSF ... điều đó không xảy ra ... sau đó tôi đã cố gắng thực hiện một hệ thống được đặt mặc định thành utf8 để đọc tệp bởi máy chủ của tôi (máy chủ tomcat) .. nhưng điều đó gây ra phát âm vì tất cả các thư viện lớp của tôi không được biên dịch theo utf8 và tomcat bắt đầu đọc sau đó ở định dạng utf8 và máy chủ không chạy đúng cách ... sau đó tôi đã kết thúc việc thực hiện một phương thức trong bộ điều khiển java của mình để được gọi từ các tệp xhtml ..trong phương pháp đó tôi đã làm như sau:

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

Tôi đặc biệt lo lắng vì điều này có thể làm chậm hiệu suất của ứng dụng của tôi ... tuy nhiên, sau khi thực hiện điều này, có vẻ như ứng dụng của tôi nhanh hơn bây giờ .. tôi nghĩ đó là bởi vì, bây giờ tôi đang truy cập trực tiếp vào các thuộc tính thay vì cho phép JSF phân tích cách truy cập các thuộc tính ... tôi đặc biệt chuyển đối số Boolean trong lệnh gọi này vì tôi biết một số thuộc tính sẽ không được dịch và không cần phải ở định dạng utf8 ...

Bây giờ tôi đã lưu tệp thuộc tính của mình ở định dạng UTF8 và nó đang hoạt động tốt vì mỗi người dùng trong ứng dụng của tôi có tùy chọn ngôn ngữ giới thiệu.


2
Properties prop = new Properties();
String fileName = "./src/test/resources/predefined.properties";
FileInputStream inputStream = new FileInputStream(fileName);
InputStreamReader reader = new InputStreamReader(inputStream,"UTF-8");

1

Đối với những gì đáng giá vấn đề của tôi là bản thân các tệp đã mã hóa sai. Sử dụng iconv làm việc cho tôi

iconv -f ISO-8859-15 -t UTF-8  messages_nl.properties > messages_nl.properties.new

+1 để đề cập iconv. Tôi chưa bao giờ nghe về nó trước đây nhưng tôi đã gõ nó vào bảng điều khiển và lo lắng, đó là một thứ tồn tại (trong CentOS 6, dù sao đi nữa.)
ArtOfWarfare

Bây giờ tôi thực sự đã thử sử dụng nó, nhưng nó không hoạt động: nó đã tạo ra ký tự đầu tiên không thể chuyển đổi thành ISO-8559-1.
ArtOfWarfare

1

Tôi đã cố gắng sử dụng cách tiếp cận do Rod cung cấp, nhưng xem xét mối quan tâm của BalusC về việc không lặp lại cùng một công việc trong tất cả các ứng dụng và đi kèm với lớp này:

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

Cách sử dụng này sẽ rất giống với cách sử dụng ResourceBundle thông thường:

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

Hoặc bạn có thể sử dụng hàm tạo thay thế sử dụng UTF-8 theo mặc định:

private MyResourceBundle labels = new MyResourceBundle("es");

0

Mở hộp thoại Cài đặt / Tùy chọn ( Ctrl+ Alt+ S), sau đó nhấp vào Trình chỉnh sửa và Mã hóa tệp.

Ảnh chụp màn hình của cửa sổ hiển thị

Sau đó, ở phía dưới, bạn sẽ mã hóa mặc định cho các tệp thuộc tính. Chọn loại mã hóa của bạn.

Ngoài ra, bạn có thể sử dụng các ký hiệu unicode thay vì văn bản trong gói tài nguyên của mình (ví dụ "ів"bằng \u0456\u0432)


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.