Skip to content

Instantly share code, notes, and snippets.

@rmuller
Created April 17, 2014 12:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmuller/10980706 to your computer and use it in GitHub Desktop.
Save rmuller/10980706 to your computer and use it in GitHub Desktop.
Change Form Locale of Vaadin UI on the fly
/* VaadinUtils.java
*
* Created: 2014-03-20 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.infomas.vaadin;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.SortedSet;
import java.util.TreeSet;
import com.vaadin.ui.Component;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.UI;
import eu.infomas.logging.Logger;
import eu.infomas.logging.LoggerFactory;
/**
* {@code VaadinUtils} offers some utility functions for the
* <a href="https://vaadin.com">Vaadin UI Framework</a>.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since INFOMAS NG 3.0
*/
public final class VaadinUtils {
private static final Logger LOG = LoggerFactory.getLogger();
// Utility class
private VaadinUtils() {
}
/**
* Change all {@code Locale} dependant properties of the {@code com.vaadin.ui.Component}s
* within of the given component container (typically an {@link UI} or other top level
* layout component).
* If the specified {@code Locale} is the same as the current {@code Locale} of the
* component container, this method does nothing.
* A property is considered {@code Locale} dependant if there exists a key for this
* property in the given {@code ResourceBundle}. The key must have the format
* "componentId.propertyName", i.e. "user.caption".
* <p>
* To use this method, do something like:
* <pre>
* public class MyUI extends UI {
*
* {@literal @}Override
* public void setLocale(Locale locale) {
* VaadinUtils.updateLocale(this, locale,
* ResourceBundle.getBundle("i18n/Login", locale));
* super.setLocale(locale);
* }
*
* {@literal @}Override
* public void init(final VaadinRequest request) {
* // ... usual code
* // somewhere in the UI the user can change the "Form Locale". This code must
* // call myUI#setLocale(newLocale);
* }
*
* // ...
*
* }
* </pre>
*
* @param ui The component container for which the {@code Locale} dependent component
* properties must be changed, never {@code null}
* @param locale The new {@code Locale}, never {@code null}
* @param rb The {@code ResourceBundle} for the specified {@code Locale},
* never {@code null}
*/
public static void updateLocale(final HasComponents ui, final Locale locale,
final ResourceBundle rb) {
// locale may not be null, howvever the current UI Locale may be null!
if (locale.equals(ui.getLocale())) {
return;
}
final long time = System.currentTimeMillis();
final SortedSet<String> ss = new TreeSet<>();
for (Enumeration<String> e = rb.getKeys(); e.hasMoreElements(); ) {
ss.add(e.nextElement());
}
walkComponentTree(ui, new Consumer<Component>() {
@Override
public void accept(Component c) {
final String id = c.getId();
if (id == null) {
return;
}
final Class<?> type = c.getClass();
final int prefixLen = id.length() + 1;
// char '/' is the char right after '.' in Unicode
for (String key : ss.subSet(id.concat("."), id.concat("/"))) {
if (rb.containsKey(key)) {
// update value!
final String mname = keyToMethodName(key, prefixLen);
try {
final Method m = type.getMethod(mname, String.class);
final String value = rb.getString(key);
LOG.info("%s(%s) [key=%s]", mname, value, key);
m.invoke(c, value);
} catch (Exception ex) {
LOG.error(ex);
}
}
}
}
});
LOG.info("Locale updated: %s -> %s in %d ms.",
ui.getLocale(), locale, System.currentTimeMillis() - time);
}
// private
// if switching to Java 8, remove this and replace by java.util.funtion.Consumer
// (and make to code more functional)
interface Consumer<T> {
void accept(T t);
}
// recursively walk the Component true
private static void walkComponentTree(Component c, Consumer<Component> visitor) {
visitor.accept(c);
if (c instanceof HasComponents) {
for (Component child : ((HasComponents)c)) {
walkComponentTree(child, visitor);
}
}
}
// keyToMethodName("user.caption", 5) -> "setCaption"
private static String keyToMethodName(final String key, final int prefixLen) {
final int n = key.length() - prefixLen;
final char[] buffer = new char[n + 3];
"set".getChars(0, 3, buffer, 0);
key.getChars(prefixLen, prefixLen + n, buffer, 3);
buffer[3] = Character.toUpperCase(buffer[3]);
return new String(buffer);
}
}
@javydreamercsw
Copy link

This looks interesting but is not working for me. While debugging all components return null when using on line 108 so nothing is actually translated.

@javydreamercsw
Copy link

I think I found the issue after reading the comments. I'll adjust to my case. Thanks for the code!

@javydreamercsw
Copy link

See my revision to this code here.

Basically made the following changes:

  • Works with Components implementing the Property interface (i.e. Label).
  • Works with any component that has an id matching a key in the provided resource bundle. No special formatting needed.
  • Works with components with complex captions/values. For example a label with multiple keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment