enuma label from message bundle - java

I have a enum with some entries for a selectOneMenu, that means the enum stucture looks like this: display, pdfLabel.
I want to load the entries label from my message bundle, that means depending on the locale.
It works fine, but only the first time after I depoly the project. That means, if the locale is "en" first time I load the entries, even after logout - session invalidate; if I change the locale to "de" the entries are still from the "en" - message. It works only if I redeploy.
Anyone has an idea about this behavior?
My enum:
public enum Transportmittel {
TRUCK(I18n.get("tv.moc.truck"), "TRUCK"),
AIRFREIGHT(I18n.get("tv.moc.airfreight"), "AIRFREIGHT"),
TRAIN(I18n.get("tv.moc.train"), "TRAIN"),
SEAFREIGHT(I18n.get("tv.moc.seafreight"), "SEAFREIGHT"),
BARGE(I18n.get("tv.moc.barge"), "BARGE");
String ausgabe;
String pdfLabel;
private Transportmittel(String ausgabe, String pdfLabel) {
this.ausgabe = ausgabe;
this.pdfLabel = pdfLabel;
}
public String toString() {
return ausgabe;
}
public String getLabelForPdf() {
return pdfLabel;
}
}
The controller where I load the entries:
#PostConstruct
public void init() {
transportMittelSelectList.add(new SelectItem(Transportmittel.TRUCK.pdfLabel, Transportmittel.TRUCK.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.TRAIN.pdfLabel, Transportmittel.TRAIN.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.AIRFREIGHT.pdfLabel, Transportmittel.AIRFREIGHT.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.SEAFREIGHT.pdfLabel, Transportmittel.SEAFREIGHT.ausgabe));
transportMittelSelectList.add(new SelectItem(Transportmittel.BARGE.pdfLabel, Transportmittel.BARGE.ausgabe));
}
And this is where I load the message bundle:
public class I18n {
public static String get(String msg) {
FacesContext context = FacesContext.getCurrentInstance();
ResourceBundle bundle = context.getApplication().getResourceBundle(
context, "messages");
return bundle.getString(msg);
}
}

The enum-values are static - so their constructor is called only once when loading the class by the classloader (=the first use). So at consecutive uses you still use the same instance containing the same string ausgabe set at construction-time during the first use.
So you have to set the values for ausgabe and pdfLabel when it is used. But maybe it is even better to have some "external" class which knows how to get the different labels for your enum-value instead of having these values somehow hard-coded inside the enum.

This is indeed not going to work. Enum properties are initialized only once, applicationwide, while i18n is essentially supposed to be resolved on a per-request basis.
You need to redesign your enum as such that only the label keys are hold instead of the resolved localized values.
TRUCK("tv.moc.truck", "TRUCK"),
AIRFREIGHT("tv.moc.airfreight", "AIRFREIGHT"),
TRAIN("tv.moc.train", "TRAIN"),
SEAFREIGHT("tv.moc.seafreight", "SEAFREIGHT"),
BARGE("tv.moc.barge", "BARGE");
And then provide the enum values as follows in an application scoped bean:
#ManagedBean
#ApplicationScoped
public class Data {
public Transportmittel[] getTransportmittels() {
return Transportmittel.values();
}
}
And then reference it in <f:selectItems> as follows (look, no need for SelectItem boilerplate):
<f:selectItems value="#{data.transportmittels}" var="transportmittel"
itemValue="#{transportmittel}" itemLabel="#{bundle[transportmittel.ausgabe]}" />
Or, if you happen to use JSF utility library OmniFaces already, as currently indicated in your user profile, then you could also bypass the whole application scoped Data bean and import it straight in the EL scope as follows:
<o:importConstants type="com.example.Transportmittels" /> <!-- can be declared in a master template -->
...
<f:selectItems value="#{Transportmittels}" var="transportmittel"
itemValue="#{transportmittel}" itemLabel="#{bundle[transportmittel.ausgabe]}" />
See also:
Localizing enum values in resource bundle

I had the same problem, but with ZK, I did need to fetch some properties to my enum, but it was blank String everytime.
To solve this you need to pass as the arguments the key of your property file in your enum constructor, like this:
After that in the get method of your enum propertie you must get the values in resource bundle and return them, like this:

Related

Static ResourceBundle

I am currently making resources for an app that is using ResourceBundle. The thing is, with the current code to dispatch the resources I would need to create an instance of the resource bundle every time I need it and I can guess this is not a good idea, since I would end up loading the resources again and again.
The second solution would be to divide the bundle into many, But I would end up with bundles have only 2-3 strings and like 15 bundles.
My question is:
Is there a way to simple load all the resources in a single static class and access them from there.
I made this little piece of code that seems to work for me but I doubt its quality.
public class StaticBundle
{
private final static ResourceBundle resBundle =
ResourceBundle.getBundle("com.resources");
public final static String STRING_A = resBundle.getString("KEY_A");
public final static String STRING_B = resBundle.getString("KEY_B");
public final static String STRING_C = resBundle.getString("KEY_C");
}
With this I can call StaticBundle.STRING_A and get the value anywhere in the project but since the bundle is initialized at the same time as the class itself... It is highly possible that the program won't have the time to load the proper local from the preferences.
Is there a good way to do this or any other possible solution?
Thank you
If you intend to have only messages for the default locale then what you have is fine.
Alternatively you could let the caller specify which key it needs instead of having constants, like this:
public static String getMessage(String key) {
return resBundle.getString(key);
}
If you like to support multiple locales then the usual approach is to have a Map<Locale, ResourceBundle>Map<Locale, Map<String, String> where you load the resources only once for each locale. In that case your class would have a method where the caller can specify the locale:
public static String getMessage(String key, Locale locale) {
Map<String, String> bundle = bundles.get(locale); // this is the map with all bundles
if (bundle == null) {
// load the bundle for the locale specified
// here you would also need some logic to mark bundles that were not found so
// to avoid continously searching bundles that are not present
// you could even return the message for the default locale if desirable
}
return bundle.get(key);
}
Edit: As correctly pointed out by #JB Nizet (thanks) ResourceBundle already stores a Map. The custom solution I provided in the source example, was about a custom mechanism similar to ResourceBundle that used a Map of Maps to load translations of keys in a property=value format, not only from files but also a database. I have incorrectly thought that we had a Map of ResourceBundle in that solution. The source example is fixed now.
You can create a singleton class:
public class MyResouceBundle extends ResourceBundle {
private static MyResourceBundle instance = new MyResouceBundle();
// private constructor, no one can instantiate this class, only itself
private MyResourceBundle() {
}
public ResourceBundle getInstance() {
return instance;
}
}
Then, everyone will access the same instance of the class with (to get string for KEY_A, for example):
MyResourceBunde.getInstance().get("KEY_A");

Make Enum.toString() localized

I'm developing an Android application and I want to know if I can set Enum.toString() multilanguage.
I'm going to use this Enum on a Spinner and I want to use multi language texts.
public class Types
{
public enum Stature
{
tall (0, "tall"),
average(1, "average"),
small(2, "small");
private final int stature;
private final String statureString;
Stature(int anStature, String anStatureString) { stature = anStature; statureString = anStatureString; }
public int getValue() { return stature; }
#Override
public String toString() { return statureString; }
}
}
I don't know how to use Context.getString() inside an Enum, and I have hardcoded "tall", "average" and "small" to test it. I have defined that enum inside on a helper class.
This how I use the enum on a Spinner:
mSpinStature.setAdapter(new ArrayAdapter<Stature>(mActivity, android.R.layout.simple_dropdown_item_1line, Stature.values()));
Do you know how can I do it?
I created a simple library which is a part of my big project (Xdroid):
compile 'com.shamanland:xdroid-enum-format:0.2.4'
Now you can avoid the same monkey-job (declaring field, constructor, etc) for all enumetations by using annotations:
public enum State {
#EnumString(R.string.state_idle)
IDLE,
#EnumString(R.string.state_pending)
PENDING,
#EnumString(R.string.state_in_progress)
IN_PROGRESS,
#EnumString(R.string.state_cancelled)
CANCELLED,
#EnumString(R.string.state_done)
DONE;
}
And then use the common Java approach - use extensions of class java.text.Format:
public void onStateChanged(State state) {
EnumFormat enumFormat = EnumFormat.getInstance();
toast(enumFormat.format(state));
}
strings.xml
<string name="state_idle">Idle</string>
<string name="state_pending">Pending</string>
<string name="state_in_progress">In progress</string>
<string name="state_cancelled">Cancelled</string>
<string name="state_done">Done</string>
Look here how to show Toast simply.
You can also compile a demo app from github.
Assume this resource path
String resourceBundlePath = "my.package.bundles.messages"
In package my.package.bundles you may have messages.properties, messages_en_US.properties etc.
Then, using
ResourceBundle resourceBundle = ResourceBundle.getBundle(resourceBundlePath);
String messageKey = "myFirstMessage";
String message = resourceBundle.getMessage(messageKey);
message will contain the value of the messageKey property defined on messages.properties. If the current Locale is actually en_US you will get the value from messages_en_US.properties. If the current locale is something you do not have a properties file for the value will be from the default messages.properties
You can also call
ResourceBundle.getBundle(resourceBundlePath, myLocale);
but it is generally better to use the platform locale (have a look at jvm arguments -Duser.language, -Duser.country)
You can have a ResourceBundle for each enum you want to translate with keys the enum element names and use it in the toString() implementation of your enum:
#Override
public String toString() {
return resourceBudle.getString(super.toString());
}
I would leave enum as is and use the standard ResourceBundle approach http://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html using Enum.toString as the key
#Override
public String toString()
{
//java
return ResourceBundle.getBundle().getString(id);
//android?
App.getContext().getString(id);
}

Handling events in Tapestry layout

I have a problem in my tapestry project.
Every time I load one page, it triggers onActivate method if is defined in the page in question. But I don't know how to catch that event in my layout template.
If I define a variable in the layout.java, for example:
#Property
String a = "foo";
And I pick that variable value at the template (layout.tml):
<p>${a}</p>
Ok, that will print "foo" in the HTML of all pages that use that layout, but If I want to change that value every time that the page reloads, for example defining onActivate in the layout.java.
void onActivate(){
a="bar";
}
This method doesn't trigger in the layout.java, only in the child pages when it's defined.
(In the child pages I include the layout like Nathan Q says) How can I refresh the variable value?
Any ideas?
I guess layout is a component in this case. Only pages have an activation context, that's why the onActivate() is not fired.
I don't know your exact use case, but:
If it's a page specific variable then you can just pass a parameter to your Layout component.
Layout.java
#Parameter
#Property
private String a;
SomePage.tml
<html t:a="someString" t:type="Layout" ... />
SomePage.java
#Property
private String someString;
...
void onActivate() {
someString = "something specific for this page";
}
OR
If this variable needs to be set every render and it's not a page specific value, you can just use the SetupRender event of your Layout component.
void setupRender() {
a = ...;
}
Ok, I find a way to refresh my property value.
And It was very simple:
Instead of declare a property and update that value in the onActivate method, I declare a public method in the layout.java to get that value and make the updating changes there.
private String a = "foo";
public String getA(){
a = "bar";
return a;
}
This way, I can make any changes to the a variable every time the page loads.

How to use optionalBlock in build step's config.jelly

I have problem with creating constructor, which Jenkins can call for some JSON data originating from a Jelly form,. For testing, I created a minimal Jenkins plugin with mvn hpi:create and following two custom files:
src/main/resources/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder/config.jelly
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:block>
<table>
<f:optionalBlock name="enableText" title="Enable optional text" checked="${instance.enableText}">
<f:entry title="Optional text" field="text">
<f:textbox />
</f:entry>
</f:optionalBlock>
</table>
</f:block>
src/main/java/foo/hyde/jenkins/plugins/OptionalBlockSampleBuilder.java
package foo.hyde.jenkins.plugins;
public class OptionalBlockSampleBuilder extends hudson.tasks.Builder {
public final String text;
public final boolean enableText;
#org.kohsuke.stapler.DataBoundConstructor
public OptionalBlockSampleBuilder(String text, Boolean enableText) {
this.text = text;
this.enableText = (enableText != null) && enableText;
}
#Override
public boolean perform(hudson.model.AbstractBuild build, hudson.Launcher launcher, hudson.model.BuildListener listener) {
listener.getLogger().println("OptionalBlockSampleBuilder " + enableText + "/" + text);
return true;
}
#hudson.Extension
public static final class DescriptorImpl extends hudson.tasks.BuildStepDescriptor<hudson.tasks.Builder> {
public boolean isApplicable(Class<? extends hudson.model.AbstractProject> aClass) {
return true;
}
public String getDisplayName() {
return "Optional Block Sample";
}
}
}
I'm building against pom.xml parent <groupId>org.jenkins-ci.plugins</groupId><artifactId>plugin</artifactId><version>1.454</version>, and everything builds, Netbeans 6.9.1 launches Debug Jenkins and I get to create a job with this build step. Everything works if I don't check that checkbox, and I get expected OptionalBlockSampleBuilder false/null to job's console output.
But if I do check the checkbox and add text, then saving/applying the job config gives this exception from the depths of Jenkins code, when it tries to call my constructor:
java.lang.RuntimeException:
Failed to instantiate class
foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder
from {
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
There has to be a simple fix. I have tried many different changes, and also tried to see how other plugins use it, and finally created this minimal test plugin. How to fix it to make optionalBlock work?
The hint comes from the JSON data:
{
"enableText":{"text":"xx"},
"kind":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder",
"stapler-class":"foo.hyde.jenkins.plugins.OptionalBlockSampleBuilder"
}
You can see here that enableText contains a child property, text. That means that the f:optionalBlock is actually expecting an encapsulation of all the fields contained within the block -- when the block is checked, you will receive an instance of the encapsulation field class; when it is unchecked, that field will be null. To use the optionalBlock properly, you would need the #DataBoundConstructor to take in a single nullable class instance that encapsulates the entire optionalBlock. For example:
private String text;
#DataBoundConstructor
public MyClass(EnableTextBlock enableText)
{
if (enableText != null)
{
this.text = enableText.text;
}
}
public static class EnableTextBlock
{
private String text;
#DataBoundConstructor
public EnableTextBlock(String text)
{
this.text = text;
}
}
Notice that the enableText field in this case is actually an instance of EnableTextBlock class, which contains a child property, text. That will satisfy the JSON object that is being sent in the form.
Instead, if all you need is a single field that has a checkbox to enable entry of that field, you might want to consider instead using the f:optionalProperty tag, which will take care of that single-field encapsulation for you. However, in many cases, the optionalBlock is actually needed to configure multiple fields, in which case the encapsulation class--as exampled above--is usually the correct way to go.
The encapsulation class does not have to be a static inner class; it could be a separate class within your package, but the important part is that the DataBoundConstructor should take in an argument that matches the JSON structure being passed from the form.
Or you can add inline tag to optionalBlock like this:
<f:optionalBlock inline="true">
if inline is present, the foldable section will not be grouped into a separate JSON object upon submission.

How to override some Resources from a PropertyResourceBundle

I have an exsisting Java/JSF website all the text on the screen is coming from property files via <f:loadBundle basename="org.example.applicaltion" var="applicaltion" /> which pulls the text from applicaltion.properties.
For a runtime configurable subset of these I am wanting to pull the string from else where (CMS via web services). Looking at the ResourceBundle class it appares to have an infrastructer for something close to this with delegation to a parent ResourceBundle.
I am wanting somethis like this
public class Applicaltion extends ResourceBundle{
#Override
protected Object handleGetObject(String key) {
if(overridenKey(key)){
return overridedValue(key);
}
return null; // ResourceBundle.getObject will delegate to parent
// if we return null
}
}
I have tried this and parent is null, I assume this is more used for the case of default -> en -> en_GB.
I am considering the not very appealing option of have the property file a different name from the custom resourceBundle and then delegating through the full stack of ResourceBundle.getBundle(PROPERTY_FILE_NAME).getString(key) from within CustomResourceBundle.handleGetObject(key).
Any better ideas?
I ended up solving this by checking if we had an override value, if we did returning that, else delegating to the standard resource bundle
public class UILabels extends ResourceBundle {
private ResourceBundle getFileResources(){
return ResourceBundle.getBundle("com.example.web.UILabelsFile", this.getLocale());
}
public Enumeration<String> getKeys() {
return getFileResources().getKeys();
}
protected Object handleGetObject(String key) {
if(overrideValue(key)){
return getOverridenValue(key);
}
return getFileResources().getObject(key);
}
}
Note the slight difference in name class is UILabels which is what all clients will use the file is UILabelsFile so the ResourceBundle loader does not go recursive.
You could write a custom PropertyResolver, and then have that perform the logic of where to pull the property values from.
For example, you could define a bean called messageSource and have that load up application.properties, plus your CMS properties or whatever you have.
Then write a custom PropertyResolver (there's an example of how to do this with Spring's MessageSource here) and link it in to your faces-config.xml using something like this:
<application>
<property-resolver>
com.package.MessageSourcePropertyResolver
</property-resolver>
</application>

Categories