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.
Related
I've written an application in JSF which generates some CRUD pages based on some configuration files. To configure those pages it is possible to provide parameters. They can be of any type and also EL expressions. Currently I create and evaluate these expressions for each getter-invocation, which is not the ideal solution, I think:
Now I have two simple questions which I can't answer for myself:
It should be possible to cache those ValueExpression instances per request. On first access (in my case I use JSF's postAddToView event) I create the expression and evaluate in in each getter-invokation.
public void populateComponent(ComponentSystemEvent event) {
FacesContext context = FacesContext.getCurrentContext();
_valueExpression = context.getApplication().getExpressionFactory()
.createValueExpression(context.getELContext(), _expression, String.class);
}
public String getExpressionValue() {
FacesContext context = FacesContext.getCurrentContext();
return (String) _valueExpression.getValue(context.getELContext())
}
This should be the same as using the expression directly in the XHTML which is processed by JSF itself. Any problems with this solution that I don't see at the moment?
Is it also possible to create the instance of the ValueExpression once and use it for all request? The cache would be on the application level instead of per request. This removes the postAddToView event and moves that logic to my configuration parser (which has access to the FacesContext). Or may I hit any side effects when a single value expression is used in multiple requests/threads?
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 have different locale file for messages to user in JSF based Project.
i want to retrieve messages from all locale available in my project using Key
I am using in managed bean
String message=ResourceBundle.getBundle("com.tech.resources.messages",
new Locale(loggedInUser.getLanguage())).getString("label.hostel.room.allocated");
Instead of one String i want all messages as a array or list assosiated with this key in all resource bundles like messages.properties, messages_hin.properties etc.
As you've already figured how to get a locale-specific bundle and then get its message by key and that part thus doesn't need to be answered, your sole question basically boils down to:
How can I get all supported locales of my JSF application?
You can get all supported locales by Application#getSupportedLocales().
Application application = FacesContext.getCurrentInstance().getApplication();
Iterator<Locale> supportedLocales = application.getSupportedLocales();
while (supportedLocales.hasNext()) {
Locale supportedLocale = supportedLocales.next();
// ...
}
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 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.