-
Notifications
You must be signed in to change notification settings - Fork 1
Tutorial03
The new version of JButler is now hosted at Labes/UFES and there is a new tutorial as well. This repository contains an older version, if you're interested.
We are getting closer to JButler, I promise! There's one more thing to do before actually implementing something: prepare the Web application for i18n (short for internationalization, because there are 18 letters between i and n). JButler assumes that i18n is being used, so it's better to do it from the start.
Here are the steps (which will be explained later):
-
Right-click
Java Resources/src/main/java
and select New > Package, then create packagebr.ufes.informatica.oldenburg
; -
Right-click the
br.ufes.informatica.oldenburg
package underJava Resources/src/main/java
and select New > Other.... Choose General > File from the list and click Next. Create a file calledfaces.properties
with the following contents (based on the JSF 2.3 specification, section 2.5.2.4):## ## Standard localized error and informational messages from the JSF 2.3 specification ## Language: American English ## javax.faces.component.UIInput.CONVERSION = {0}: Conversion error occurred javax.faces.component.UIInput.REQUIRED = {0}: Validation Error: Value is required javax.faces.component.UIInput.UPDATE = {0}: An error occurred when processing your submitted information javax.faces.component.UISelectOne.INVALID = {0}: Validation Error: Value is not valid javax.faces.component.UISelectMany.INVALID = {0}: Validation Error: Value is not valid javax.faces.converter.BigDecimalConverter.DECIMAL={2}: ''{0}'' must be a signed decimal number. javax.faces.converter.BigDecimalConverter.DECIMAL_detail={2}: ''{0}'' must be a signed decimal number consisting of zero or more digits, that may be followed by a decimal point and fraction. Example: {1} javax.faces.converter.BigIntegerConverter.BIGINTEGER={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.BigIntegerConverter.BIGINTEGER_detail={2}: ''{0}'' must be a number consisting of one or more digits. Example: {1} javax.faces.converter.BooleanConverter.BOOLEAN={1}: ''{0}'' must be 'true' or 'false'. javax.faces.converter.BooleanConverter.BOOLEAN_detail={1}: ''{0}'' must be 'true' or 'false'. Any value other than 'true' will evaluate to 'false'. javax.faces.converter.ByteConverter.BYTE={2}: ''{0}'' must be a number between -128 and 127. javax.faces.converter.ByteConverter.BYTE_detail={2}: ''{0}'' must be a number between -128 and 127. Example: {1} javax.faces.converter.CharacterConverter.CHARACTER={1}: ''{0}'' must be a valid character. javax.faces.converter.CharacterConverter.CHARACTER_detail={1}: ''{0}'' must be a valid ASCII character. javax.faces.converter.DateTimeConverter.DATE={2}: ''{0}'' could not be understood as a date. javax.faces.converter.DateTimeConverter.DATE_detail={2}: ''{0}'' could not be understood as a date. Example: {1} javax.faces.converter.DateTimeConverter.TIME={2}: ''{0}'' could not be understood as a time. javax.faces.converter.DateTimeConverter.TIME_detail={2}: ''{0}'' could not be understood as a time. Example: {1} javax.faces.converter.DateTimeConverter.DATETIME={2}: ''{0}'' could not be understood as a date and time. javax.faces.converter.DateTimeConverter.DATETIME_detail={2}: ''{0}'' could not be understood as a date and time. Example: {1} javax.faces.converter.DateTimeConverter.PATTERN_TYPE={1}: A 'pattern' or 'type' attribute must be specified to convert the value ''{0}''. javax.faces.converter.DoubleConverter.DOUBLE={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.DoubleConverter.DOUBLE_detail={2}: ''{0}'' must be a number between 4.9E-324 and 1.7976931348623157E308 Example: {1} javax.faces.converter.EnumConverter.ENUM={2}: ''{0}'' must be convertible to an enum. javax.faces.converter.EnumConverter.ENUM_detail={2}: ''{0}'' must be convertible to an enum from the enum that contains the constant ''{1}''. javax.faces.converter.EnumConverter.ENUM_NO_CLASS={1}: ''{0}'' must be convertible to an enum from the enum, but no enum class provided. javax.faces.converter.EnumConverter.ENUM_NO_CLASS_detail={1}: ''{0}'' must be convertible to an enum from the enum, but no enum class provided. javax.faces.converter.FloatConverter.FLOAT={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.FloatConverter.FLOAT_detail={2}: ''{0}'' must be a number between 1.4E-45 and 3.4028235E38 Example: {1} javax.faces.converter.IntegerConverter.INTEGER={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.IntegerConverter.INTEGER_detail={2}: ''{0}'' must be a number between -2147483648 and 2147483647 Example: {1} javax.faces.converter.LongConverter.LONG={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.LongConverter.LONG_detail={2}: ''{0}'' must be a number between -9223372036854775808 to 9223372036854775807 Example: {1} javax.faces.converter.NumberConverter.CURRENCY={2}: ''{0}'' could not be understood as a currency value. javax.faces.converter.NumberConverter.CURRENCY_detail={2}: ''{0}'' could not be understood as a currency value. Example: {1} javax.faces.converter.NumberConverter.PERCENT={2}: ''{0}'' could not be understood as a percentage. javax.faces.converter.NumberConverter.PERCENT_detail={2}: ''{0}'' could not be understood as a percentage. Example: {1} javax.faces.converter.NumberConverter.NUMBER={2}: ''{0}'' is not a number. javax.faces.converter.NumberConverter.NUMBER_detail={2}: ''{0}'' is not a number. Example: {1} javax.faces.converter.NumberConverter.PATTERN={2}: ''{0}'' is not a number pattern. javax.faces.converter.NumberConverter.PATTERN_detail={2}: ''{0}'' is not a number pattern. Example: {1} javax.faces.converter.ShortConverter.SHORT={2}: ''{0}'' must be a number consisting of one or more digits. javax.faces.converter.ShortConverter.SHORT_detail={2}: ''{0}'' must be a number between -32768 and 32767 Example: {1} javax.faces.converter.STRING={1}: Could not convert ''{0}'' to a string. javax.faces.validator.BeanValidator.MESSAGE = {0} javax.faces.validator.DoubleRangeValidator.MAXIMUM = {1}: Validation Error: Value is greater than allowable maximum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.DoubleRangeValidator.MINIMUM = {1}: Validation Error: Value is less than allowable minimum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.DoubleRangeValidator.NOT_IN_RANGE = {2}: Validation Error: Specified attribute is not between the expected values of {0} and {1}. javax.faces.validator.DoubleRangeValidator.TYPE = {0}: Validation Error: Value is not of the correct type javax.faces.validator.LengthValidator.MAXIMUM = {1}: Validation Error: Length is greater than allowable maximum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.LengthValidator.MINIMUM = {1}: Validation Error: Length is less than allowable minimum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.LongRangeValidator.MAXIMUM = {1}: Validation Error: Value is greater than allowable maximum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.LongRangeValidator.MINIMUM = {1}: Validation Error Value is less than allowable minimum of \u2018\u2019{0}\u2019\u2019 javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2}: Validation Error: Specified attribute is not between the expected values of {0} and {1}. javax.faces.validator.LongRangeValidator.TYPE = {0}: Validation Error: Value is not of the correct type
-
In that same package, create the
messages.properties
file with the following contents (most of it is for JButler later, only the last few items will be used in this part):## ## Global resource bundle for a JButler project. ## Language: American English ## # Messages for PrimeFaces components. primefaces.password.prompt = Please enter a password primefaces.password.weak = Weak primefaces.password.good = Good primefaces.password.strong = Strong # For JButler's base project decorator: jbutler.text.ajax.loading = Loading data, please wait... jbutler.text.ajax.error = There has been an error communicating with the server. jbutler.text.ajax.complete = Last communication with the server successful. # Generic messages for JButler's CRUD web pages: jbutler.crud.button.add = Add jbutler.crud.button.filter = Filter jbutler.crud.button.cancelFilter = Cancel jbutler.crud.button.clearFilter = Clear jbutler.crud.button.create = New jbutler.crud.button.retrieve = View jbutler.crud.button.update = Modify jbutler.crud.button.delete = Delete jbutler.crud.button.cancelDeletion = Cancel jbutler.crud.button.confirmDeletion = Confirm deletion jbutler.crud.button.cancel = Cancel jbutler.crud.button.save = Save jbutler.crud.button.back = Back jbutler.crud.help.hotkeys.filterFocus = focus the filter field jbutler.crud.help.hotkeys.clearFilter = clear current filter jbutler.crud.help.hotkeys.create = open the form for a new entry jbutler.crud.help.hotkeys.retrieve = open the details of the selected item jbutler.crud.help.hotkeys.update = open the form for modifying the selected item jbutler.crud.help.hotkeys.delete = add the selected item to the trash bin for deletion jbutler.crud.help.hotkeys.cancelDeletion = cancel deletion and restore the items in the trash bin jbutler.crud.help.hotkeys.confirmDeletion = confirm the deletion of the items in the trash bin jbutler.crud.help.hotkeys.focusFirstField = focus the first form field jbutler.crud.help.hotkeys.backToList = go back to the listing jbutler.crud.help.hotkeys.save = save changes and go back to the listing jbutler.crud.help.hotkeys.cancel = cancel the operation and go back jbutler.crud.help.hotkeys.back = go back jbutler.crud.hotkey.filterFocus = f jbutler.crud.hotkey.clearFilter = x jbutler.crud.hotkey.create = c jbutler.crud.hotkey.retrieve = r jbutler.crud.hotkey.update = u jbutler.crud.hotkey.delete = d jbutler.crud.hotkey.cancelDeletion = esc jbutler.crud.hotkey.confirmDeletion = y jbutler.crud.hotkey.focusFirstField = f jbutler.crud.hotkey.backToList = esc jbutler.crud.hotkey.cancel = esc jbutler.crud.hotkey.back = esc jbutler.crud.text.trashHeader = Items to delete: # JButler default formats: jbutler.format.date.java=dd/MM/yyyy jbutler.format.date.primefaces=99/99/9999 jbutler.format.date.label=dd/mm/aaaa jbutler.format.datetime.java=dd/MM/yyyy HH:mm:ss jbutler.format.taxCode.primefaces=999.999.999-99 jbutler.format.taxCode.label=\#\#\#.\#\#\#.\#\#\#-\#\# jbutler.format.zipCode.primefaces=99999-999 jbutler.format.zipCode.label=\#\#\#\#\#-\#\#\# # JButler regular expressions and validator messages. jbutler.regex.email=([^.@]+)(\\.[^.@]+)*@([^.@]+\\.)+([^.@]+) jbutler.regex.confirmationCode=[\\w\\-]\{36\} jbutler.regex.email.message = This is not a valid email address. jbutler.regex.confirmationCode.message = This confirmation code given is not valid. # Global messages for the Web application. menu.home = Home text.developedBy = Developed by <a href="http://www.inf.ufes.br/~vitorsouza/">Prof. Vítor E. Silva Souza</a> for <a href="http://informatica.ufes.br/">PPGI/UFES</a> text.home.about01 = This Web application allows professors teaching teaching Research Methodology courses to simulate a workshop in which the students will submit papers and perform peer review. text.home.about02 = The application is named after <a href="https://en.wikipedia.org/wiki/Henry_Oldenburg">Henry Oldenburg</a> which, according to Wikipedia, is the creator of scientific peer review. title.home = Home title.home.description = Welcome to Oldenburg, the Workshop Simulator.
-
Now open
Deployed Resources/webapp/WEB-INF/faces-config.xml
, go to the Source tab and add the i18n configuration for JSF under the<faces-config></faces-config>
tag:<application> <!-- Defines the resource bundle that contains the standard JSF messages (overriding the ones provided). --> <message-bundle>br.ufes.informatica.oldenburg.faces</message-bundle> <!-- Loads resource bundles for i18n messages and assigns names to them. --> <resource-bundle> <base-name>br.ufes.informatica.oldenburg.messages</base-name> <var>msgs</var> </resource-bundle> <!-- Defines the default locale. --> <locale-config> <default-locale>en-US</default-locale> </locale-config> </application>
-
We can now internationalize our Web pages. Open the template file
Deployed Resources/webapp/WEB-INF/templates/template.xhtml
and replace the Home menu item:<span>Home</span>
with:
<span><h:outputText value="#{msgs['menu.home']}" /></span>
-
Also replace some of the text at the footer:
<ui:define name="footer"> <h:outputText value="#{msgs['text.developedBy']}" escape="false" /> <div class="pull-right hidden-xs" style="color: gray"><i>1.0.0</i></div> </ui:define>
-
Finally, open the home page
Deployed Resources/webapp/index.xhtml
and also internationalize it:<ui:define name="title"> <h:outputText value="#{msgs['title.home']}" /> </ui:define> <ui:define name="description"> <h:outputText value="#{msgs['title.home.description']}" /> </ui:define> <ui:define name="body"> <p><h:outputText value="#{msgs['text.home.about01']}" /></p> <p><h:outputText value="#{msgs['text.home.about02']}" escape="false" /></p> </ui:define>
Deploy the application to see if everything works. The result is the same as before, but we are now prepared to use JButler and to translate our website to other languages more easily.
In general, i18n in Java works by performing a process called externalizing strings. This means that the strings with the text that is shown in the user interface is taken from the UI component and placed in an external file with .properties
extension called resource bundle.
The name of the resource bundles follow a standard format: basename_ll_CC.properties
, in which ll
is the ISO-3166 language code and CC
is the ISO-3166 country code, both optional. When a resource bundle is requested, one provides its basename and the desired locale, and Java will search for the most specific bundle that satisfies the locale. For instance, if the requested locale is en-US (American English), then Java will look for basename_en_US.properties
, then (if not found), basename_en.properties
(generic English), then finally basename.properties
(default bundle). So what we did in the steps above was to provide the default bundle, which happens to be written in en-US.
In Jakarta EE, with JSF, resource bundles are registered in the faces-config.xml
file using their fully qualified name (FQN) and associated with a name. For instance, br.ufes.informatica.oldenburg.messages
(i.e., messages.properties
under package br.ufes.informatica.oldenburg
) was associated with the name msgs
. The configuration file also indicates the default locale for this Web application: <default-locale>en-US</default-locale>
.
Then, at any Web page, we can use Expression Language to refer to text that should be extracted from that resource bundle, e.g.: <h:outputText value="#{msgs['title.home']}" />
. In this example, JSF will request the resource bundle associated with the msgs
name for the default locale and search for the key title.home
inside the .properties
file, rendering in the page the contents associated with that key.
If we want to translate our Web application, we just need to provide another copy of each .properties
file with the appropriate suffix for language and country, then change the default locale of the application. It's also possible to change the locale only for a specific user, so each visitor of the website could ask for the UI in their own language. We could also detect the preferred language from the user's OS locale configuration. All of this is left as exercises for the interested reader...
Finally, JSF also outputs many different messages as result of conversion or validation (mostly errors during these processes). If we don't do anything, it will fetch the default messages from its own implementation and all of them will be in English, independently of the locale of the system. If we want to internationalize them as well, we need to provide the <message-bundle>br.ufes.informatica.oldenburg.faces</message-bundle>
configuration pointing to a resource bundle and then we can provide translations for it.