JavaRush /Java Blog /Random EN /Constants and Internationalization
Joysi
Level 41

Constants and Internationalization

Published in the Random EN group
When working, the program relies in calculations or input-output using not only variables, but also explicitly written constants. Let's say you process financial data and in the course of the code output the results several times (even to the console) like this: Constants and Internationalization - 1
System.out.println("Totalвое количество =" + countLoans);
...
String strCount = "Totalвое количество :" + loans.calc();
...
System.out.println(strCount + strSumDesc);
And this is very bad for several reasons.
  1. Violation of the DRY principle (Don't repeat yourself - do not repeat).
    Duplication of text and, as a result, an increase in the size of the compiled code. Also, there may be typos.

  2. Violation of the KISS principle (keep it simple stupid - make things easier).
    If you need to change such constants, you will have to go through the entire code.

  3. Each constant change will require recompilation of the code.

  4. The introduction of multilingualism will require (with such a construction of the code) a lot of additional constructions for each such constant.

The first two reasons will eliminate the introduction of constants with modifiers static finaland their initialization + applying them in the course of the code. Those. create:
public class Constants {
    public static final int COUNT_DEPARTMENTS = 20;
    public static final String MSG_TOTAL_AMOUNT = "Totalвое количество";
    ...
}
and apply something like System.out.println(Constants.MSG_TOTAL_AMOUNTCOUNT_DEPARTMENT + "= " + countLoans);If we need to change the phrase, we do only in one place . To eliminate the third reason, you can use the class Properties, see the corresponding CodeGym lectures and some big-tasks of the second half of the 20th levels for more details. The most frequently used variables can be taken out as separate public static final, less frequently used ones. Receive via method call PROPS.getProperty().
public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        MSG_TOTAL_AMOUNT = PROPS.getProperty("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments"));
        ...
    }
}
It remains to deal with internationalization. Let's make sure that staticthe necessary file is loaded in the block properties. To do this, we will work with ResourceBundle. Note that ResourceBundleyou can read key values ​​from standard methods, but not set them. Therefore, we still store in a separate Properties (config.properites)file the parameters that are not related to internationalization (besides, some of them can be changed during the course of the program). For example, interface language. The convenient IDEA interface will help us. Create it: Constants and Internationalization - 2Add the necessary languages: Constants and Internationalization - 3In the project structure for IDEA, it is displayed as: Constants and Internationalization - 3Fill it with content: Constants and Internationalization - 5
public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;
    public static final ResourceBundle UI_LANGUAGE;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        if (PROPS.getProperty("ui.language").equalsIgnoreCase("RUS"))
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.RUS);
        else
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.ENG);

        MSG_TOTAL_AMOUNT = UI_LANGUAGE.getString("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments"));
        ...
    }
}
We have achieved that the interface language changes without interfering with the code . In addition, we got a clear division of labor : we can give the Russian propertiesfile to a humanities translator, who will translate it in a text editor that is convenient for him. We just have to rename it to the desired filename and place it back in ResourceBundle. A small addition (using CharsetControl): Properties files are not sensitive to encoding (they process the contents of the file only one byte at a time, which is equivalent to using ISO8859-1) and Cyrillic in the Properties-file will be displayed incorrectly. To avoid this, you can:
  • Convert Cyrillic characters to ESCAPE sequence \uxxxx via native2ascii

  • Help Propertiesby passing it the necessary one ResourceBundle.Control, in which we need to overload the method (the code was found on the Internet + slightly modified).

Maybe it will help someone:
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.*;
import java.util.*;

public class CharsetControl extends ResourceBundle.Control {

    public static final CharsetControl UTF_8 = new CharsetControl("utf8");
    public static final CharsetControl RUS = new CharsetControl("cp1251");
    public static final Locale    ENG = new Locale("en","GB");

    private Charset charset;

    public CharsetControl(String charset) {
        this(Charset.forName(charset));
    }

    public CharsetControl(Charset charset) {
        this.charset = charset;
    }

    public Charset getCharset() {
        return charset;
    }

    @Override
    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException {

        String bundleName = toBundleName(baseName, locale);
        ResourceBundle bundle = null;
        if (format.equals("java.class")) {
            try {
                Class<? extends ResourceBundle> bundleClass  =
                        (Class<? extends ResourceBundle>)loader.loadClass(bundleName);

                if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
                    bundle = bundleClass.newInstance();
                } else {
                    throw new ClassCastException(bundleClass.getName()
                            + " cannot be cast to ResourceBundle");
                }
            } catch (ClassNotFoundException e) {
            }
        } else if (format.equals("java.properties")) {
            final String resourceName = toResourceName(bundleName, "properties");
            final ClassLoader classLoader = loader;
            final boolean reloadFlag = reload;
            InputStream stream = null;
            try {
                stream = AccessController.doPrivileged(
                        new PrivilegedExceptionAction<InputStream>() {
                            public InputStream run() throws IOException {
                                InputStream is = null;
                                if (reloadFlag) {
                                    URL url = classLoader.getResource(resourceName);
                                    if (url != null) {
                                        URLConnection connection = url.openConnection();
                                        if (connection != null) {
                                            connection.setUseCaches(false);
                                            is = connection.getInputStream();
                                        }
                                    }
                                } else {
                                    is = classLoader.getResourceAsStream(resourceName);
                                }
                                return is;
                            }
                        });
            } catch (PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
            if (stream != null) {
                try {
                    bundle = new PropertyResourceBundle(new InputStreamReader(stream, getCharset()));
                } finally {
                    stream.close();
                }
            }
        } else {
            throw new IllegalArgumentException("unknown format: " + format);
        }
        return bundle;
    }
}
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION