26.5. (Optional) Resource Bundles |
The NumberFormatDemo in the preceding example displays the numbers , currencies, and percentages in local customs , but displays all the message strings, titles, and button labels in English. In this section, you will learn how to use resource bundles to localize message strings, titles, button labels, and so on.
A resource bundle is a Java class file or text file that provides locale-specific information. This information can be accessed by Java programs dynamically. When a locale-specific resource is needed ”a message string, for example ”your program can load it from the resource bundle appropriate for the desired locale. In this way, you can write program code that is largely independent of the user 's locale, isolating most, if not all, of the locale-specific information in resource bundles.
With resource bundles, you can write programs that separate the locale-sensitive part of your code from the locale-independent part. The programs can easily handle multiple locales, and can easily be modified later to support even more locales.
The resources are placed inside the classes that extend the ResourceBundle class or a subclass of ResourceBundle . Resource bundles contain key/value pairs. Each key uniquely identifies a locale-specific object in the bundle. You can use the key to retrieve the object. ListResourceBundle is a convenient subclass of ResourceBundle that is often used to simplify the creation of resource bundles. Here is an example of a resource bundle that contains four keys using ListResourceBundle :
// MyResource.java: resource file public class MyResource extends java.util.ListResourceBundle { static final Object[][] contents = {
{ "nationalFlag" , "us.gif" }, { "nationalAnthem" , "us.au" }, { "nationalColor" , Color .red}, { "annualGrowthRate" , new Double( 7.8 )} }; public Object[][] getContents() { return contents; } }
Keys are case-sensitive strings. In this example, the keys are nationalFlag , nationalAnthem , nationalColor , and annualGrowthRate . The values can be any type of Object .
If all the resources are strings, they can be placed in a convenient text file with the extension .properties. A typical property file would look like this:
#Wed Jul 01 07:23:24 EST 1998 nationalFlag=us.gif nationalAnthem=us.au
To retrieve values from a ResourceBundle in a program, you first need to create an instance of ResourceBundle using one of the following two static methods :
public static final ResourceBundle getBundle(String baseName) throws MissingResourceException public static final ResourceBundle getBundle (String baseName, Locale locale) throws MissingResourceException
The first method returns a ResourceBundle for the default locale, and the second method returns a ResourceBundle for the specified locale. baseName is the base name for a set of classes, each of which describes the information for a given locale. These classes are named in Table 26.3.
1. BaseName_language_country_variant.class |
2. BaseName_language_country.class |
3. BaseName_language.class |
4. BaseName.class |
5. BaseName_language_country_variant.properties |
6. BaseName_language_country.properties |
7. BaseName_language.properties |
8. BaseName.properties |
For example, MyResource_en_BR.class stores resources specific to the United Kingdom, MyResource_en_US.class stores resources specific to the United States, and MyResource_en.class stores resources specific to all the English-speaking countries .
The getBundle method attempts to load the class that matches the specified locale by language, country, and variant by searching the file names in the order shown in Table 26.3. The files searched in this order form a resource chain . If no file is found in the resource chain, the getBundle method raises a MissingResourceException , a subclass of RuntimeException .
Once a resource bundle object is created, you can use the getObject method to retrieve the value according to the key. For example,
ResourceBundle res = ResourceBundle.getBundle( "MyResource" ); String flagFile = (String)res.getObject( "nationalFlag" ); String anthemFile = (String)res.getObject( "nationalAnthem" ); Color color = (Color)res.getObject( "nationalColor" ); double growthRate = (Double)res.getObject( "annualGrowthRate" ).doubleValue();
Tip
If the resource value is a string, the convenient getString method can be used to replace the getObject method. The getString method simply casts the value returned by getObject to a string. |
What happens if a resource object you are looking for is not defined in the resource bundle? Java employs an intelligent look-up scheme that searches the object in the parent file along the resource chain. This search is repeated until the object is found or all the parent files in the resource chain have been searched. A MissingResourceException is raised if the search is unsuccessful .
Let us modify the NumberFormatDemo program in the preceding example so that it displays messages, title, and button labels in multiple languages, as shown in Figure 26.10.
You need to provide a resource bundle for each language. Suppose the program supports three languages: English (default), Chinese, and French. The resource bundle for the English language, named MyResource.properties, is given as follows :
#MyResource.properties for English language Number_Of_Years=Years Total_Payment=French Total\ Payment Enter_Interest_Rate=Enter\ Interest\ Rate,\ Years ,\ and\ Loan\ Amount Payment=Payment Compute=Compute Annual_Interest_Rate=Interest\ Rate Number_Formatting=Number\ Formatting\ Demo Loan_Amount=Loan\ Amount
Choose_a_Locale=Choose\ a\ Locale Monthly_Payment=Monthly\ Payment
The resource bundle for the Chinese language, named MyResource_zh.properties, is given as follows:
#MyResource_zh.properties for Chinese language Choose_a_Locale = \u9078\u64c7\u570b\u5bb6 Enter_Interest_Rate = \u8f38\u5165\u5229\u7387,\u5e74\u9650,\u8cb8\u6b3e\u7e3d\u984d Annual_Interest_Rate = \u5229\u7387 Number_Of_Years = \u5e74\u9650 Loan_Amount = \u8cb8\u6b3e\u984d\u5ea6 Payment = \u4ed8\u606f Monthly_Payment = \u6708\u4ed8 Total_Payment = \u7e3d\u984d Compute = \u8a08\u7b97\u8cb8\u6b3e\u5229\u606f
The resource bundle for the French language, named MyResource_fr.properties, is given as follows:
#MyResourse_fr.properties for French language Number_Of_Years=annees Annual_Interest_Rate=le taux d'interet Loan_Amount=Le montant du pret Enter_Interest_Rate=inscrire le taux d'interet, les annees, et le montant du pret Payment=paiement Compute=Calculer l'hypotheque Number_Formatting=demonstration du formatting des chiffres Choose_a_Locale=Choisir la localite Monthly_Payment=versement mensuel Total_Payment=reglement total
The program is given in Listing 26.7.
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 import javax.swing.border.*; 5 import java.util.*; 6 import java.text.NumberFormat; 7 8 public class ResourceBundleDemo extends JApplet { 9 // Combo box for selecting available locales 10 private JComboBox jcboLocale = new JComboBox(); 11 private ResourceBundle res = ResourceBundle.getBundle( "MyResource" ); 12 13 // Create labels 14 private JLabel jlblInterestRate = 15 new JLabel( res.getString( "Annual_Interest_Rate" ) ); 16 private JLabel jlblNumberOfYears = 17 new JLabel( res.getString( "Number_Of_Years" ) ); 18 private JLabel jlblLoanAmount = new JLabel 19 ( res.getString( "Loan_Amount" ) ); 20 private JLabel jlblMonthlyPayment = 21 new JLabel(res.getString( "Monthly_Payment" )); 22 private JLabel jlblTotalPayment = 23 new JLabel( res.getString( "Total_Payment" ) ); 24 25 // Create titled borders 26 private TitledBorder comboBoxTitle = 27 new TitledBorder( res.getString( "Choose_a_Locale" ) ); 28 private TitledBorder inputTitle = new TitledBorder 29 ( res.getString( "Enter_Interest_Rate" ) ); 30 private TitledBorder paymentTitle = 31 new TitledBorder( res.getString( "Payment" ) ); 32 33 // Text fields for interest rate, year, loan amount, 34 private JTextField jtfInterestRate = new JTextField( "6.75" ); 35 private JTextField jtfNumberOfYears = new JTextField( "15" ); 36 private JTextField jtfLoanAmount = new JTextField( "107000" ); 37 private JTextField jtfFormattedInterestRate = new JTextField( 10 ); 38 private JTextField jtfFormattedNumberOfYears = new JTextField( 10 ); 39 private JTextField jtfFormattedLoanAmount = new JTextField( 10 ); 40 41 // Text fields for monthly payment and total payment 42 private JTextField jtfTotalPayment = new JTextField(); 43 private JTextField jtfMonthlyPayment = new JTextField(); 44 45 // Compute button 46 private JButton jbtCompute = new JButton(res.getString( "Compute" )); 47 48 // Current locale 49 private Locale locale = Locale.getDefault(); 50 51 // Declare locales to store available locales 52 private Locale locales[] = Calendar.getAvailableLocales(); 53 54 /** Initialize the combo box */ 55 public void initializeComboBox() { 56 // Add locale names to the combo box 57 for ( int i = ; i < locales.length; i++) 58 jcboLocale.addItem(locales[i].getDisplayName()); 59 } 60 61 /** Initialize the applet */ 62 public void init() { 63 // Panel p1 to hold the combo box for selecting locales 64 JPanel p1 = new JPanel(); 65 p1.setLayout( new FlowLayout()); 66 p1.add(jcboLocale); 67 initializeComboBox(); 68 p1.setBorder(comboBoxTitle); 69 70 // Panel p2 to hold the input for annual interest rate, 71 // number of years and loan amount 72 JPanel p2 = new JPanel(); 73 p2.setLayout( new GridLayout( 3 , 3 )); 74 p2.add(jlblInterestRate); 75 p2.add(jtfInterestRate); 76 p2.add(jtfFormattedInterestRate); 77 p2.add(jlblNumberOfYears); 78 p2.add(jtfNumberOfYears); 79 p2.add(jtfFormattedNumberOfYears); 80 p2.add(jlblLoanAmount); 81 p2.add(jtfLoanAmount); 82 p2.add(jtfFormattedLoanAmount); 83 p2.setBorder(inputTitle); 84 85 // Panel p3 to hold the payment 86 JPanel p3 = new JPanel(); 87 p3.setLayout( new GridLayout( 2 , 2 )); 88 p3.setBorder(paymentTitle); 89 p3.add(jlblMonthlyPayment); 90 p3.add(jtfMonthlyPayment); 91 p3.add(jlblTotalPayment); 92 p3.add(jtfTotalPayment); 93 94 // Set text field alignment 95 jtfFormattedInterestRate.setHorizontalAlignment 96 (JTextField.RIGHT); 97 jtfFormattedNumberOfYears.setHorizontalAlignment 98 (JTextField.RIGHT); 99 jtfFormattedLoanAmount.setHorizontalAlignment(JTextField.RIGHT); 100 jtfTotalPayment.setHorizontalAlignment(JTextField.RIGHT); 101 jtfMonthlyPayment.setHorizontalAlignment(JTextField.RIGHT); 102 103 // Set editable false 104 jtfFormattedInterestRate.setEditable( false ); 105 jtfFormattedNumberOfYears.setEditable( false ); 106 jtfFormattedLoanAmount.setEditable( false ); 107 jtfTotalPayment.setEditable( false ); 108 jtfMonthlyPayment.setEditable( false ); 109 110 // Panel p4 to hold result payments and a button 111 JPanel p4 = new JPanel(); 112 p4.setLayout( new BorderLayout()); 113 p4.add(p3, BorderLayout.CENTER); 114 p4.add(jbtCompute, BorderLayout.SOUTH); 115 116 // Place panels to the applet 117 add(p1, BorderLayout.NORTH); 118 add(p2, BorderLayout.CENTER); 119 add(p4, BorderLayout.SOUTH); 120 121 // Register listeners 122 jcboLocale.addActionListener( new ActionListener() { 123 public void actionPerformed(ActionEvent e) { 124 locale = locales[jcboLocale.getSelectedIndex()]; 125 updateStrings(); 126 computeLoan(); 127 } 128 }); 129 130 jbtCompute.addActionListener( new ActionListener() { 131 public void actionPerformed(ActionEvent e) { 132 computeLoan(); 133 } 134 }); 135 } 136 137 /** Compute payments and display results locale-sensitive format */ 138 private void computeLoan() { 139 // Retrieve input from user 140 double loan = new Double(jtfLoanAmount.getText()).doubleValue(); 141 double interestRate = 142 new Double(jtfInterestRate.getText()).doubleValue() / 1240 ; 143 int numberOfYears = 144 new Integer(jtfNumberOfYears.getText()).intValue(); 145 146 // Calculate payments 147 double monthlyPayment = loan * interestRate/ 148 ( 1 - (Math.pow( 1 / ( 1 + interestRate), numberOfYears * 12 ))); 149 double totalPayment = monthlyPayment * numberOfYears * 12 ; 150 151 // Get formatters 152 NumberFormat percentFormatter = 153 NumberFormat.getPercentInstance(locale); 154 NumberFormat currencyForm = 155 NumberFormat.getCurrencyInstance(locale); 156 NumberFormat numberForm = NumberFormat.getNumberInstance(locale); 157 percentFormatter.setMinimumFractionDigits( 2 ); 158 159 // Display formatted input 160 jtfFormattedInterestRate.setText( 161 percentFormatter.format(interestRate * 12 )); 162 jtfFormattedNumberOfYears.setText 163 (numberForm.format(numberOfYears)); 164 jtfFormattedLoanAmount.setText(currencyForm.format(loan)); 165 166 // Display results in currency format 167 jtfMonthlyPayment.setText(currencyForm.format(monthlyPayment)); 168 jtfTotalPayment.setText(currencyForm.format(totalPayment)); 169 } 170 171 /** Update resource strings */ 172 private void updateStrings() { 173 res = ResourceBundle.getBundle( "MyResource" , locale); 174 jlblInterestRate.setText( res.getString( "Annual_Interest_Rate" ) ); 175 jlblNumberOfYears.setText( res.getString( "Number_Of_Years" ) ); 176 jlblLoanAmount.setText( res.getString( "Loan_Amount" ) ); 177 jlblTotalPayment.setText( res.getString( "Total_Payment" ) ); 178 jlblMonthlyPayment.setText( res.getString( "Monthly_Payment" ) ); 179 jbtCompute.setText( res.getString( "Compute" ) ); 180 comboBoxTitle.setTitle( res.getString( "Choose_a_Locale" ) ); 181 inputTitle.setTitle( res.getString( "Enter_Interest_Rate" ) ); 182 paymentTitle.setTitle( res.getString( "Payment" ) ); 183 184 // Make sure the new labels are displayed 185 repaint(); 186 } 187 188 /** Main method */ 189 public static void main(String[] args) { 190 // Create an instance of the applet 191 ResourceBundleDemo applet = new ResourceBundleDemo(); 192 193 // Create a frame with a resource string 194 JFrame frame = new JFrame( 195 applet. res .getString( "Number_Formatting" )); 196 197 // Add the applet instance to the frame 198 frame.add(applet, BorderLayout.CENTER); 199 200 // Invoke init() and start() 201 applet.init(); 202 applet.start(); 203 204 // Display the frame 205 frame.setSize( 400 , 300 ); 206 frame.setLocationRelativeTo( null ); 207 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 208 frame.setVisible( true ); 209 } 210 } |
Property resource bundles are implemented as text files with a .properties extension, and are placed in the same location as the class files for the application or applet. ListResourceBundles are provided as Java class files. Because they are implemented using Java source code, new and modified ListResourceBundles need to be recompiled for deployment. With PropertyResourceBundles , there is no need for recompilation when translations are modified or added to the application. Nevertheless, ListResourceBundles provide considerably better performance than PropertyResourceBundles .
If the resource bundle is not found or a resource object is not found in the resource bundle, a MissingResourceException is raised. Since MissingResourceException is a subclass of RuntimeException , you do not need to catch the exception explicitly in the code.
This example is the same as Listing 26.6, NumberFormatDemo.java, except that the program contains the code for handling resource strings. The updateString method (lines 172 “186) is responsible for displaying the locale-sensitive strings. This method is invoked when a new locale is selected in the combo box. Since the variable res of the ResourceBundle class is an instance variable in ResourceBundleDemo , it cannot be directly used in the main method, because the main method is static. To fix the problem, create applet as an instance of ResourceBundleDemo and you will then be able to reference res using applet.res .