I want to implement an i18n-support for a wicket 7 application.
Requirements:
Translations must be easily editable by the admin-user
Translations must take place without redeployment
My actual apporach is to hold the translations inside a DB. All translations will be cached. If a translation is changed by a Frontend-task the cache and the db will be updated.
So far so easy.
Actually I'm stuck in replacing the translations inside a page.
A working solution would be loading every translation during implementation. These translations would be set inside many of wicket-elements.
I don't like this approach, because it'll mess up the code (html + java) heavily.
I'll try to implement a replacement-mechanism in my actual approach. After the page is rendered, the mechanism is run through the whole page and is doing these jobs:
search for all placeholders
load the translation for the placeholder-keys(cache)
replace the placeholders with the translations
This should work for body and header (site's title)
Here is an example of a wicket-template
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${landingpage.site.title}</title>
</head>
<body>
<header wicket:id="headerPanel">header</header>
${welcome.message}
<footer wicket:id="footerPanel">footer</footer>
</body>
</html>
In this case ${landingpage.site.title} and ${welcome.message} should be recognized and replaced. As you can see it is directly definied inside the template, not in the java-code. And this is what I want to achieve.
I hope I made the requirements clear enough. If not, don't mind to comment. I'll update the question to make it more clear.
My approach is to implement a BasePage (extends Page) and overwrite the onAfterRender-Method
#Override
protected void onAfterRender() {
super.onAfterRender();
Response originalResponse = RequestCycle.get().getResponse();
String updatedResponse = replaceWithTranslations(originalResponse);
originalResponse.reset();
originalResponse.write(updatedResponse);
}
The method replaceWithTranslations is not yet implemented and returns a simple String actually. This method should convert the outputstream of the originalRepsonse to a String, searches for placeholders and replace them with the values of the db.
This approach seems to have 2 difficulties:
I'm not getting the response as String
I'm getting a WicketRuntimeException (Page.checkRendering in Page.java:666)
Any advice would be great!
OK, the problems seems to be a very simple one.
The trick or the luck is, here we have a BufferedWebResponse. A simple cast will do the trick:
#Override
protected void onAfterRender() {
super.onAfterRender();
BufferedWebResponse originalResponse = (BufferedWebResponse) RequestCycle.get().getResponse();
String translatedResponse = replaceWithTranslations(originalResponse);
originalResponse.reset();
originalResponse.write(translatedResponse);
}
private String replaceWithTranslations(BufferedWebResponse originalResponse) {
String untranslatedText = originalResponse.getText().toString();
String translatedText = doTheTranslation(untranslatedText);
return translatedText;
}
Inspired by #RobAu I gave the wicket's approach of i18n a chance. Here is with what I came up with:
The template:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><wicket:message key="landingpage.site.title">Site-Title</wicket:message></title>
</head>
<body>
<header wicket:id="headerPanel">header</header>
<wicket:message key="welcome.message">Welcome</wicket:message>
<footer wicket:id="footerPanel">footer</footer>
</body>
</html>
wicket:message for attributes:
<input type="text" placeholder="username" wicket:message="placeholder:login.username"/>
The IStringResourceLoader:
#org.springframework.stereotype.Component
public class I18NResourceLoader implements IStringResourceLoader {
#Autowired
private I18NCache i18nCache;
#Override
public String loadStringResource(final Class<?> clazz, final String key, final Locale locale, final String style, final String variation) {
return loadTranslation(key, locale);
}
#Override
public String loadStringResource(final Component component, final String key, final Locale locale, final String style, final String variation) {
return loadTranslation(key, locale);
}
private String loadTranslation(final String key, final Locale locale) {
final Optional<Translation> optional = i18nCache.get(key, locale);
if (!optional.isPresent()) {
return key;
}
return optional.get().getText();
}
}
Translation and I18NCache are self-implemented classes.
And finally the registration:
public abstract class BasePage extends WebPage {
#SpringBean
private I18NResourceLoader i18NResourceLoader;
public BasePage(){
addI18NResourceLoader();
...
}
private void addI18NResourceLoader() {
final List<IStringResourceLoader> resourceLoaders = Application.get().getResourceSettings().getStringResourceLoaders();
final boolean existsResourceLoader = resourceLoaders.stream()
.filter(p -> p instanceof I18NResourceLoader)
.collect(Collectors.counting()) > 0L;
if (!existsResourceLoader) {
resourceLoaders.add(i18NResourceLoader);
}
}
...
}
Pro's:
Wicket's approach
No mess with RegEx-Replacement-Handling
SPR
Con's
Template feels a little more messy
Actually, I have no informations about the performance of this approach.
I decided to keep the logic of adding the ResourceLoader in BasePage by 2 reasons.
BasePage is responsible for everything concering the page-representation (weak reason :-) )
I'm using DI. If I would add the logic in the WebApplication, I would have to manually inject the I18NResourceLoader or its dependencies.
I think you can extend IComponentResolver to replace the placeholders like WicketMessageResolver.
Related
I created an Arduino device that converts data every time mail comes in. The data is sent to the Web server below by Arduino using Wi-Fi.
<%# page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%>
<%# page import="java.sql.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>Mail Log</title>
</head>
<body>
<table id="mail" align="center" border="0" cellspacing="1" cellpadding="3">
<tr align="center">
<td> <strong>Time</strong> </td><td> <strong>Mail</strong> </td>
</tr>
<%
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/STACK","root","mingky1218");
ps=conn.prepareStatement("SELECT * FROM MESSAGE ORDER BY TIMESTAMP DESC");
rs=ps.executeQuery();
while(rs.next()){
String time=rs.getString("timeStamp");
String stack=rs.getString("STACK");
out.println("<tr><td> "+time+" </td><td> Letter: "+stack+" </td></tr>");
}
}catch(SQLException e){
e.printStackTrace();
}
%>
</table>
</body>
</html>
The Java Fx GUI, which appears whenever data is entered into a table on the Web server, must change the value in real time, but it does not change.
public class Mail {
String mail;
public Mail() {
try {
Document doc = Jsoup.connect("http://localhost14999/stackserver/index.jsp").get();
Element table = doc.select("table").get(0);
Elements rows = table.select("tr");
Element row = rows.get(1);
Elements cols = row.select("td");
mail=cols.get(1).text();
}catch(IOException e) {
System.out.println(e.getMessage());
System.out.println(e.toString());
}
}
public String getMail() {
return mail;
}
}
The above is Jsoup, the parsing class, and the following is the controler of Java fx.
public class Controller implements Initializable {
#FXML private Label mail;
private Mail mt=new Mail();
#Override
public void initialize(URL location, ResourceBundle resources) {
Thread thread = new Thread() {
#Override
public void run() {
while(true) {
String wfmail = mt.getMail();
Platform.runLater(()->{
mail.setText(wfmail);
});
try {Thread.sleep(1000);}catch(InterruptedException e) {}
}
}
};
thread.setDaemon(true);
thread.start();
};
}
I also tried parsing using selenium, but the results were the same.
Parsing certain data has succeeded, but the value does not change in real time.
Is there a good any way?
I'm not sure how Java fx controller works but I assume that only one instance of Controller will be created and if that is correct then you create Mail object only once. So you trying to call getMail() object every time on the same object.
Since you requesting index.jsp from your Mail class during object construction (i.e. in Mail contructor) the only moment when you actually read the data from index.jsp is when Mail object creating. And from the above assumption it happens only once, when fx controller is being loaded. So the easiest way to achieve what you want is to move the code from Mail constructor to the getMail() method.
public String getMail() {
try {
Document doc = Jsoup.connect("http://localhost14999/stackserver/index.jsp").get();
Element table = doc.select("table").get(0);
Elements rows = table.select("tr");
Element row = rows.get(1);
Elements cols = row.select("td");
return cols.get(1).text();
}catch(IOException e) {
System.out.println(e.getMessage());
System.out.println(e.toString());
return "";
}
}
But be aware that the whole code is looking ugly and non-obvious so it can be used just for a quick try.
After migrating wicket to 1.5 I encounter this issue.
In Wicket 1.5 I (programmer before me) was adding JS and CSS reference in constructor and then component, simmilar to this:
public abstract class PageTemplate extends WebPage implements IHeaderContributor {
public BarePageTemplate() {
this(null);
}
public BarePageTemplate(PageParameters params) {
super(params);
add(JavascriptPackageResource.getHeaderContribution(ResourceMarker.class, "js/jquery-1.4.2.min.js"));
add(JavascriptPackageResource.getHeaderContribution(ResourceMarker.class, "js/jquery-ui-1.8.4.custom.min.js"));
[...]
jGrowlMarker = new Label("jGrowlMarker"); // placeholder for jgrowl messages
jGrowlMarker.setOutputMarkupId(true);
jGrowlMarker.add(new JGrowlBehavior());
add(jGrowlMarker);
}
}
In wicket 1.5 this is no longer possible. According to migration guide I refactored this code into this:
public abstract class PageTemplate extends WebPage implements IHeaderContributor {
public BarePageTemplate() {
this(null);
}
public BarePageTemplate(PageParameters params) {
super(params);
jGrowlMarker = new Label("jGrowlMarker"); // placeholder for jgrowl messages
jGrowlMarker.setOutputMarkupId(true);
jGrowlMarker.add(new JGrowlBehavior());
add(jGrowlMarker);
}
#Override
public void renderHead(IHeaderResponse response) {
response.renderJavaScriptReference(new CommonResourceRef("js/jquery-1.4.2.min.js"));
response.renderJavaScriptReference(new CommonResourceRef("js/jquery-ui-1.8.4.custom.min.js"));
[...]
}
}
This piece of code works, what I mean by that is that it includes those js. What causing me an issue is fact that right now my component (in this case jGrowlMarker) is rendering in page before page includes those js:
// jGrowl component
<script type="text/javascript" src="./wicket/resource/pl.softq.commons.ui.resource.ResourceMarker/js/jquery-1.4.2.min-ver-65B352E1BA79F0E2A3B1E014BC2571AF.js"></script>
<script type="text/javascript" src="./wicket/resource/pl.softq.commons.ui.resource.ResourceMarker/js/jquery-ui-1.8.4.custom.min-ver-88A9784D8E08D25998F239E2D65F03D7.js"></script>
Because of that it doesn't work properly (I believe thats an issue). It tries to create jGrowl component but jGrowl.js is added after this component.
[...] symbolizes rest of my js and css refenreces (including jGrowl ones). I've tried to add super.renderHead(response); to renderHead but it also doesnt work (super.renderHead refers to empty one in Component.class).
So my question is: how to add component after renderHead initializes or how to add it inside renderHead.
I'm not an expert in wicket so if you need more piece of code, let me know.
You can move the renderHead() method to JGrowlBehavior. This way it will contribute the dependencies first and then jgrowl.js itself.
If the dependencies are contributed by something else in the page too Wicket will detect this and contribute them just once.
In Wicket 6.x there are further improvements in this area. You can read http://wicketinaction.com/2012/07/wicket-6-resource-management/
I currently have the following:
cartServlet.java
public class CartServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
CartBean cartBean = new CartBean();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int counter = 0;
while (request.getParameterMap().containsKey("id" + counter)){
String songID = request.getParameter("id" + counter);
cartBean.setCartInfo(songID);
++counter;
}
request.setAttribute("cart", cartBean);
RequestDispatcher rd = request.getRequestDispatcher("/cart.jsp");
rd.forward(request, response);
}
cart.jsp
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Shopping Cart</title>
</head>
<body>
"${cart.cartInfo}"
</body>
</html>
cartBean.java
public class CartBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> cart;
public CartBean(){
cart = new ArrayList<String>();
}
public void setCartInfo(String cartItem) {
this.cart.add(cartItem);
}
public List<String> getCartInfo() {
return cart;
}
}
When I print "${cart.cartInfo}", my output is coming out like this:
"[381d3af3-c113-46c1-b9d0-2c46cf445e22}, 3913ac54-0c03-4025-8279-5cfad2fcab5f}, 50ed6861-f6e2-479b-865c-cbbbc5c27efd}, eb9b29d6-d93e-4cd8-8d7a-7fe26ff6c05d}]"
Is this the correct way the output should be printed out? I don't know why the additional } is appearing at the end of each item..
Also, should I be defining CartBean cartBean = new CartBean(); in cartServlet.java? If a user were to come back to this shopping cart page and select more items, would the new items be placed in a different bean to the one I was originally using?
Thanks for your help.
Is this the correct way the output should be printed out? I don't know
why the additional } is appearing at the end of each item..
I'm guessing that yes, what's being listed on your JSP is what's supposed to be listed. Now it may differ from what you want, which is another matter. What appears to be currently listed is the ID for a song. In this XML file, I see one of the IDs listed as for "The Sweetest Taboo" by Sade in this homework file: http://www.cse.unsw.edu.au/~cs9321/14s1/assignments/musicDb.xml.
You need to use the useBean tag in your JSP. The syntax is as follows:
<jsp:useBean id = "idName" class = "packageName.SpecificClass" scope = "desiredScope" />
Fill in id, class, and scope with the desired values. The most common value for scope seems to be session.
Then set the property:
<jsp:setProperty name = "idName" property = "*" />
For more information about setProperty (along with useBean), see: jsp:setproperty what does property="*" mean?.
Also, should I be defining CartBean cartBean = new CartBean(); in
cartServlet.java?
It's usually best to use the servlet for the bean, especially if there's some processing that's going on, like filling in lists. Avoid putting a lot of Java code in your JSPs. The JSP should be able to grab a list of products and place them on the page, but it should not be able to instantiate the list, populate it, massage it into the form you need, and then place the products.
If a user were to come back to this shopping cart
page and select more items, would the new items be placed in a
different bean to the one I was originally using?
Read this page: http://www.jguru.com/faq/view.jsp?EID=53309.
You need to put the statement of creating CartBean inside doPost() method as a local variable, otherwise each request will create a new Thread and these threads share the instance variable which means the data in CartBean will be corrupted by different users.
One DropDownChoice list on my webapp takes very long time to create, because of getting options by some operations with LDAP connection and SQL connection. And because of that the whole page is loading much more than a couple of seconds - I'd say too much.
So what I want to achieve, is to use (best for me) the built-in Ajax functionality of Wicket to lazy load this dropdown, but I have some problems.
I know how to make regular DropDownChoice list, this simple example working great for me - link
I also know how to make lazy-loaded paragraph, from wicket-examples - link (Source Code -> LazyLoadingPage.html/LazyLoadingPage.java)
But putting it together throwing me exceptions and resulting Wicket's Internal error.
Here is how I try to do it:
in HTML:
<select wicket:id="lazy"></select>
in Java:
private String selected = "abc";
(...)
add(new AjaxLazyLoadPanel("lazy") {
#Override
public Component getLazyLoadComponent(String id) {
//simulating long time for simple list
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new DropDownChoice<String>(
id, new PropertyModel<String>(this,"selected"),
Arrays.asList("abc","def"));
}
});
}
And I'm getting Internal Error from Wicket, with that in logs:
ERROR Unexpected error occurred
Component [content] (path = [0:lazy:content]) must be applied to a tag of type [select], not: '<div wicket:id="content">' (line 0, column 0)
MarkupStream: [markup = jar:file:/C:/Program%20Files/Apache%20Software%20Foundation/Tomcat%207.0/webapps/devservices/WEB-INF/lib/wicket-extensions-1.5.7.jar!/org/apache/wicket/extensions/ajax/markup/html/AjaxLazyLoadPanel.html
, index = 0, current = ''
and stacktrace.
I would really appreciate some help, what I'm doing wrong, or maybe some better code examples.
Thanks to bert, I'm putting here full solution, in case someone will use it in the future.
We need to create our own panel, because AjaxLazyLoadPanel can only change one panel to another.
Example of MyPanel.html:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
<select wicket:id="project"></select>
</wicket:panel>
</body>
</html>
and MyPanel.java :
public class MyPanel extends Panel {
private String selected = <what you want>;
private List<String> projectList <what you want>;
public MyPanel(String id) {
super(id);
add(new DropDownChoice<String>(
"project", new PropertyModel<String>(this, "selected"), projectsList));
}
}
On your main page html simply add this:
<span wicket:id="lazy2"></span>
and in main page java file:
add(new AjaxLazyLoadPanel("lazy") {
#Override
public Component getLazyLoadComponent(String id) {
return new MyPanel(id);
}
});
Hope it will help someone else too :-)
as I asked time ago in this question, I solved my problem using this method:
In loging.xhtm, for instance:
<f:view locale="#{languageDetails.locale}" >
<head>
.....
<f:loadBundle basename="messages.Messages" var="msg1"/>
.....
</h:form>
</body>
</f:view>
2.In java source code I also made some changes:
public class LanguageDetails {
private static String locale = Locale.getDefault().getDisplayLanguage();
public void setLocale(String locale1) {
this.locale = locale1;
}
public synchronized String getLocale() {
return locale;
}
public synchronized String changeLanguage() {
return "changed";
}
}
But now I'm trying to have the same option, not just in Login page, but in other pages.
Adding the same code in other pages, doesn't work, because function setLocale is not called. Any help?
Thanks in advance
I realized, it's really important to put
<f:view locale="#{languageDetails.locale}" >
....
</f:view>
Or in every single file, or just in top file. Later, put <h:selectOneMenu> where necessary, but having always in mind that you can not have all <h:form> , <a4j:form>... etc. you want, it makes things more complicated. I put this form tags just on top files, and now everything is ok.
Hope this could help somebody.