i just wanted to know as which language the default messages.properties is read.
i thought that it is the in the faces-config.xml configured default locale is:
<locale-config>
<default-locale>de</default-locale>
<supported-locale>de</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
it contains no <message-bundle> tag,i created a messages.properties, messages_en.properties and messages_de.properties. To access the values i use this code
ResourceBundle resourceBundle = SeamResourceBundle.getBundle();
String bundleMessage = resourceBundle.getString("key.something");
In the menu i used this to show (and switch) the language what works fine
<h:selectOneMenu value="#{localeSelector.localeString}">
<f:selectItems value="#{localeSelector.supportedLocales}"/>
</h:selectOneMenu>
Now it doesn't matter what language i select, je always uses the messages.properties and not _de or _en. Do i need a concrete class for <message-bundle> to find also the _de and _en resource bundles?
EDIT:
ResourceBundle resourceBundle = SeamResourceBundle.getBundle();
java.util.Locale locale = resourceBundle.getLocale();
Contains always the correct locale de or en but always uses messages.properties and if this file is deleted, returns just the key as if he found no other file. The messages*.properties are in the /WEB-INF/classes folder.
i tried now to take Map<String, String> messages = org.jboss.seam.international.Messages.instance(); It contains also the values from messages.properties and not _de or _en
Using #{messages[key.label]} in the *.xhtml file also returns just the messages.properties values but not from _de or _en.
But a messages_de properties or _en directly in the xyz.war file with a <a4j:loadBundle var="i18n" basename="messages"/> does work. (thats how i did the i18n in the "not Java" frontend)
two more tries always return just the default properties and not _de or _en
resourceBundle = context.getApplication().getResourceBundle(context, "messages");
java.util.Locale locale = new java.util.Locale("de");
resourceBundle = ResourceBundle.getBundle("messages",locale);
if i create a new messages2_de.properties and *_en* and use the code above, everything works fine.
java.util.Locale locale = new java.util.Locale("de");
resourceBundle = ResourceBundle.getBundle("messages2",locale);
EDIT (Apparently JBoss Seam is a bit different)
As this document says, you probably should not instantiate bundles yourself.
Instead, you would define bundles you want to use and let Seam read message for you:
#In("#{messages['Hello']}") private String helloMessage;
Generally getBundle() method of any of ResourceBundle derived implementations will give you invariant bundle if you omit Locale parameter. This is by design.
If you need to access localized version, you need to get Locale from UIViewRoot:
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
ResourceBundle resourceBundle = SeamResourceBundle.getBundle();
String bundleMessage = resourceBundle.getString("key.something");
I am not aware how your localeSelector bean is coded, but it too should set Locale in UIViewRoot.
Normally, the bundle without _xx is simply the bundle that is used if the key is not found in any of the more specific bundles for the current language.
Although I don't know what SeamResourceBundle exactly does, you do have to tell it somewhere what the 'current' language is. You say switching the language works, but what exactly do you do upon switching? At what point do you execute SeamResourceBundle.getBundle()?
Is key.something actually defined in all 3 bundles?
My bad. You couldn't find the error. The project had one messages.properties in /WEB-INF/classes and a second set(but without default properties) directly in the web content directory with the same names.
So i guess he took the only existing default messages.properties from the classes folder and the messages_de/en.properties from the web-content folder.
Related
I need some placeholders with automatic filling in my resource bundle.
An user of my application has two possible configuration, for example "1" and "2". Depending on this configuration, an entry should returned with the correct value.
I know, that conditions are possible:
currentConfig=The configuration is currently the {0,choice,0#first|1#second}
In my faces-config.xml the resource bundle is configured for access in JSF.
Now, I want to get this value in JSF without specifying parameters:
<h:outputText value="#{res.currentConfig}"/>
If the user has configured "1", the first value should be returned, otherwise the second value.
with configuration "1": The configuration is currently the first.
with configuration "2": The configuration is currently the second.
Can this be implemented independently of the JSF pages ?
You can implement get text from resource bundle in your own way. For instance you could create such a method in some managedbean :
public String getCongiurationText() {
FacesContext context = FacesContext.getCurrentInstance();
Locale locale = context.getViewRoot().getLocale();
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
String msg = bundle.getString("currentConfig");
Integer value = 1;
return MessageFormat.format(msg, getConfiguration());
}
Then in your outputText :
<h:outputText value="#{someUtilBean.getCongiurationText()}" />
With that approach you get message independently of JSF pages. So in every page you specify the same method, but depending on configuration it display different text. Configuration can be obtained of course in different way, but it depends how you would like to handle it.
The requirements of the application that I'm building demands that user roles are to be dynamic, they will be stored in the database, and they will also be mapped to functionalities (forms) of the application, also stored in the database.
Restricting a role from accessing a specific page won't be difficult, but the requirements also states that form inputs must be customized based on roles, which means, an input can be mandatory or not, visible or not, read-only or not based on the role.
My approach to control these restrictions is based on creating a property file for each role, which will store all the inputs of all the forms in the application, as keys, and a long string as value in which we define the state of the input, like the following:
user-inputs.properties
# form.input=mandatory:visibility
searchBooks.bookName=true:true
searchBooks.bookCategory=false:true
searchBooks.authorName=false:false
admin-inputs.properties
searchBooks.bookName=true:true
searchBooks.bookCategory=false:true
searchBooks.authorName=false:true
And then do some magic Java code, whenever a form is accessed, read its inputs properties from the file of the specific user role, and parse the values so I could provide the right value for the rendered="" and required="" attribute of an <h:inputText/>.
This could be a solution, but the inputs of the application are much more than a book name and category, means I will be putting lots of required and rendered attributes which will make JSF pages look ugly with huge amount of variables in the managed bean.
Is there a better approach/framework/solution to my issue?
I think that you are in the right way, and i will continue using your approach which consists of creating multiple property files, one for each user, except that we will not use a any "huge amount of variables
in the managed bean".
So, the first step consists on managing multiple resource properties using a single resource bundle prefix ( the <var></var> in <resource-bundle>), in the second step we will see how to switch between those files, and in the last step we will read from property file using JSTL.
Managing multiple property files:
We start by defining our ResourceBundle in the faces-config file:
<application>
<resource-bundle>
<base-name>UserMessages</base-name>
<var>msgs</var>
</resource-bundle>
</application>
UserMessages is a ResourceBundle where we will implement the logic that allow us to switch between our property files (assuming that yourpackage.user-inputs is the fully qualified name of your user-inputs.properties):
import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.faces.context.FacesContext;
public class UserMessages extends ResourceBundle {
public UserMessages() {
// we are loading user-inputs.properties as the default properties file
setParent(getBundle("yourpackage.user-inputs", FacesContext.getCurrentInstance()
.getViewRoot().getLocale()));
}
#Override
protected Object handleGetObject(String key) {
// we could just return parent.getObject(key) but we want to respect JSF recommandations
try {
return parent.getObject(key);
} catch (MissingResourceException e) {
return "???" + key + "???";
}
}
#Override
public Enumeration<String> getKeys() {
return parent.getKeys();
}
// this is the method that will allow us to switch between our .properties
public void setResourceBundle(String basename) {
setParent(getBundle(basename, FacesContext.getCurrentInstance()
.getViewRoot().getLocale()));
}
}
Switching between property files:
In order to switch from a property file to another we will need to use the method setResourceBundle(String basename) that we just declared in our class above, So in the managed bean where you are declaring your business logic and where you are intending to switch files depending on the user's role, you need to inject the bundle, like:
//don't forget adding getters and setters or you end with NullPointerException
#ManagedProperty("#{msgs}")
private UserMessages userMesssages;
Then, to switch to another file (admin-inputs.properties), just use it like this:
//yourpackage.admin-inputs is the fully qualified name
userMesssages.setResourceBundle("yourpackage.admin-inputs");
NB: You can inject the bundle in that way (above) only in request scoped beans, to use it in broader scopes please see: Read i18n variables from properties file in a Bean
Now, as we can switch easily from the user-inputs to the admin-inputs, the last step is the easiest one.
Parsing the property file:
The bad news, is that when using this approach you will need to add rendered="" and required="" attribute to every input you are willing to manage (but don't forget that the good ones was that you will not need to manage variables in managed beans ;) ).
First, you need to add JSTL namespaces declaration on the top of your xhtml file:
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
you can find more about JSTL functions in the javadocs, regarding the function substringAfter:
Returns a subset of a string following a specific substring.
Example:
P.O. Box: ${fn:substringAfter(zip, "-")}
The function substringBefore:
Returns a subset of a string before a specific substring.
Example:
Zip (without P.O. Box): ${fn:substringBefore(zip, "-")}
Second, as the first part of your String represents the required attribute:
//Returns the substring of msgs['searchBooks.authorName'] before the first occurrence of the separator ':'
required="${fn:substringBefore(msgs['searchBooks.authorName'], ':')}"
and the second part:
//Returns the substring of msgs['searchBooks.authorName'] after the first occurrence of the separator ':'.
rendered="${fn:substringAfter(msgs['searchBooks.authorName'], ':')}"
See also:
JSF Internationalization f:loadbundle or through faces-config:
Performance point
Difference between by Application#getResourceBundle() and ResourceBundle#getBundle() in JSF 2.0
How to remove the surrounding ??? when message is not found in
bundle
Context Sensitive Resource Bundle entries in JavaServer Faces
applications – going beyond plain language, region & variant
locales
I've been joyfully using omnifaces' Faces.getLocale() to aquire the locale used by the currently logged in user (which in turn gets this from a <f:view> definition). I really like the fallback approach from view to client to system default locale as it fits the requirements for locale selection in my application:
If a user is logged in, use his language preference (obtained from the backend entity)
If no user preference can be found, use the highest ranking language from the Accept-Languages HTTP header
If no locale has been selected by now, use the system default.
Now I've started using JAX-RS (resteasy implementation) and find it quite difficult to write a service that will provide my backend code with the current user's locale.
I can't use Faces.getLocale(), since that requires a FacesContext which isn't present during JAX-RS request processing.
I can't use the #Context SecurityContext annotation in a #Provider (which would give me the user preferred locale) or #Context HttpHeaders (access to the client locale) since JAX-RS only injects those when it uses the provider itself, not when my backend code instantiates the class.
And I don't want to litter my method signatures with Locale parameters, since virtually everything requires a locale to be present.
To have a concrete example: I have a vcard generator that generates little NOTE fields depending on the user's preferred locale. I can both call the vcard generating method via JSF/EL:
<h:commandLink action="#{vcfGenerator.forPerson(person)}"
value="Go" target="_blank" />
And via a REST service:
#GET #Path('person/{id:[1-9][0-9]*}/vcard')
#Produces('text/vcard')
String exportVcard(#PathParam('id') Long personId, #Context HttpHeaders headers) {
VcfGenerator exporter = Component.getInstance(VcfGenerator) as VcfGenerator
Person person = entityManager.find(Person, personId)
if (! person)
return Response.noContent().build()
def locale = headers.acceptableLanguages[0] ?: Locale.ROOT
return exporter.generateVCF(person, locale).toString()
}
This works (VcfGenerator has a set of JSF-only methods that use Faces.getLocale()), but is a pain to maintain. So instead of passing the Locale object, I'd like to say:
Vcard generateVCF(Person person) {
Locale activeLocale = LocaleProvider.instance().getContext(VcfGenerator.class)
ResourceBundle bundle = ResourceBundle.getBundle("messages", activeLocale, new MyControl())
// use bundle to construct the vcard
}
Has anyone done similar work and can share insights?
I know this has been posted a while ago, but as it has not been marked as resolved, here is how I got a workaround working for this specific case:
First I got a custom ResourceBundle working, as #BalusC described here: http://balusc.blogspot.fr/2010/10/internationalization-in-jsf-with-utf-8.html
Then I updated the constructor in order to detect if a FacesContext is currently being in use, from this :
public Text() {
setParent(ResourceBundle.getBundle(BUNDLE_NAME,
FacesContext.getCurrentInstance().getViewRoot().getLocale(), UTF8_CONTROL));
}
To This:
public Text() {
FacesContext ctx = FacesContext.getCurrentInstance();
setParent(ResourceBundle.getBundle(BUNDLE_NAME,
ctx != null ? ctx.getViewRoot().getLocale() : Locale.ENGLISH, UTF8_CONTROL));
}
This now works both in JSF and JAX-RS context.
Hope this help,
I've put a properties file within src/main/resources in my JSF project.
How do I get a handle to it? I understand that EL doesn't work within a backing bean.
Note: The file is in src/main/resources - NOT src/main/webapps/resources, so the following doesn't work:
FacesContext context = FacesContext.getCurrentInstance();
File value = context.getApplication().evaluateExpressionGet(context, "#{resource['resources:email.properties']}", File.class);
It's thus in the classpath. You can just use ClassLoader#getResourceAsStream() to get an InputStream out of it. Assuming that src is the classpath root and that main/resources is the package:
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("main/resources/foo.properties");
Properties properties = new Properties();
properties.load(input);
// ...
Alternatively, if it's supposed to be specific to the webapp and thus isn't supposed to be overrideable by a file on the same path elsewhere in the classpath which has a higher classloading precedence (e.g. in appserver's lib or the JRE's lib), then use ExternalContext#getResourceAsStream() instead.
InputStream input = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream("main/resources/foo.properties");
Properties properties = new Properties();
properties.load(input);
// ...
As to the #{resource} syntax, this is indeed specifically for CSS/JS/image resources placed in /resources folder of public web content. See also How to reference CSS / JS / image resource in Facelets template?
I have a JSF Validator that I'm building that has properties in it that I would like to have loaded from a ResourceBundle. However, I'm not quite sure how to work this, as it isn't loading properly. Any ideas on how I can make this work?
I've tried using a #PostContruct to do it, but I'm getting the following error in Eclipse:
Access restriction: The type
PostConstruct is not accessible due to
restriction on required library
/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar
So, I'm not too sure what the best way to work this. A sample of what I'm talking about is below...
The validator...
#FacesValidator("usernameValidator")
public class UserNameValidator implements Validator {
#ManagedProperty(value="#{props_userNamePattern}")
private String userNamePattern;
#ManagedProperty(value="#{props_minUserNameLength}")
private int minUserNameLength;
#ManagedProperty(value="#{props_maxUserNameLength}")
private int maxUserNameLength;
public void validate(FacesContext context, UIComponent component, Object
value) throws ValidatorException {
//My validations here...
}
//Setters for the class properties
}
faces-config.xml
<resource-bundle>
<base-name>settings</base-name>
</resource-bundle>
settings.properties
props_userNamePattern = /^[a-z0-9_-]+$/
props_minUserNameLength = 3
props_maxUserNameLength = 30
The #ManagedProperty works on #ManagedBean classes only. The #PostConstruct will also not be the correct solution for your functional requirement. It is intented to be placed on a method which is to be executed when the class has been constructed and all dependency injections are been finished. The error which you're facing is caused by a specific combination of older Eclipse+JRE versions. If upgrading is not an option, you could disable the warning/error by Window > Preferences > Java > Compiler > Errors/Warnings > Deprecated and restricted API > Forbidden reference > Ignore.
As to your functional requirement, unfortunately no annotation which achieves that comes to mind. You could however get it programmatically.
String bundlename = "settings";
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
ResourceBundle bundle = ResourceBundle.getBundle(bundlename, locale);
String usernamePattern = bundle.getString("props_userNamePattern");
// ...
You can do that in the constructor of the validator. When used properly a new instance will be created for every view anyway.
Adding to the correct answer of BalusC; In JSF 2.0/2.1 Validators, Converters, PhaseListeners, etc are a kind of 'second-class' citizen as they are not injection targets.
This also means you can't inject an entity manager or an EJB, which can sometimes be used for validation purposes.
In JSF 2.2 this is supposed to change:
All JSF lifecycle artifacts should be
CDI-aware and support
injection/JSR-299/JSR-330
(PhaseListeners, NavHandlers,
Components, ActionListeners,
everything.)
See: http://jcp.org/en/jsr/detail?id=344
Perhaps seam-faces might help for this situation?
http://docs.jboss.org/seam/3/faces/latest/reference/en-US/html/artifacts.html
http://docs.jboss.org/seam/3/faces/latest/reference/en-US/html/faces.messages.html