.
( ) .
( "currentLanguage" ), .
( "fr-messages_" ) . ...
, , ResourceBundleMessageSource, .
Spring (messages_en.properties, messages_fr.properties,...), Javascript ( ExtJs). , () JS.
... ReloadableResourceBundleMessageSource. "getAllProperties()", "getAllPropertiesAsMap()" "getAllPropertiesAsMessages()".
. , stackoverflow, ReloadableResourceBundleMessageSource, . "getAvailableLocales()" "isAvailableLocale()" ( ).
package fr.ina.archibald.web.support;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ReflectionUtils;
import fr.ina.archibald.commons.util.StringUtils;
import fr.ina.archibald.entity.MessageEntity;
public class ReloadableResourceBundleMessageSource extends org.springframework.context.support.ReloadableResourceBundleMessageSource {
private static final Logger LOGGER = LoggerFactory.getLogger(ReloadableResourceBundleMessageSource.class);
private static final String PROPERTIES_SUFFIX = ".properties";
private static final String XML_SUFFIX = ".xml";
private Set<Locale> cacheAvailableLocales;
private Set<Resource> cacheResources;
public Properties getAllProperties(final Locale locale) {
if(locale == null) {
LOGGER.debug("Cannot get all properties. 'locale' argument is null.");
return null;
}
return getMergedProperties(locale).getProperties();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public Map<String, String> getAllPropertiesAsMap(final Locale locale) {
if(locale == null) {
LOGGER.debug("Cannot get all properties as Map. 'locale' argument is null.");
return null;
}
Properties props = getAllProperties(locale);
if(props == null) {
LOGGER.debug("Cannot get all properties as Map. The properties are missing.");
return null;
}
return new HashMap<String, String>((Map) props);
}
public List<MessageEntity> getAllPropertiesAsMessages(final Locale locale) {
if(locale == null) {
LOGGER.debug("Cannot get all properties as MessageEntity. 'locale' argument is null.");
return null;
}
Properties props = getAllProperties(locale);
if(props == null) {
LOGGER.debug("Cannot get all properties as MessageEntity. The properties are missing.");
return null;
}
Set<Entry<Object, Object>> propsSet = props.entrySet();
List<MessageEntity> messages = new ArrayList<MessageEntity>();
for(Entry<Object, Object> prop : propsSet) {
messages.add(new MessageEntity((String) prop.getKey(), (String) prop.getValue()));
}
return messages;
}
public Set<Locale> getAvailableLocales() {
if(cacheAvailableLocales != null) {
return cacheAvailableLocales;
}
cacheAvailableLocales = getLocales(getAllFileNames(), getMessageFilePrefixes());
return cacheAvailableLocales;
}
public boolean isAvailableLocale(final Locale locale) {
Set<Locale> locales = getAvailableLocales();
if(locales == null) {
return false;
}
return locales.contains(locale);
}
private Set<Locale> getLocales(final List<String> fileNames, List<String> filePrefixes) {
if(fileNames == null || fileNames.isEmpty() || filePrefixes == null || filePrefixes.isEmpty()) {
LOGGER.debug("Cannot get available Locales. fileNames=[" + StringUtils.toString(fileNames) + "], filePrefixes=[" + StringUtils.toString(filePrefixes) + "]");
return null;
}
Set<Locale> locales = new HashSet<Locale>();
for(String fileName : fileNames) {
String fileNameWithoutExtension = FilenameUtils.getBaseName(fileName);
for(String filePrefixe : filePrefixes) {
String localeStr = fileNameWithoutExtension.substring(filePrefixe.length() + 1);
try {
locales.add(LocaleUtils.toLocale(localeStr));
} catch(IllegalArgumentException ex) {
continue;
}
}
}
return locales;
}
private List<String> getAllFileNames() {
Set<Resource> resources = getAllResources();
if(resources == null) {
LOGGER.debug("Missing resources bundles.");
return null;
}
List<String> filenames = new ArrayList<String>(resources.size());
for(Resource resource : resources) {
filenames.add(resource.getFilename());
}
return filenames;
}
private List<String> getMessageFilePrefixes() {
String[] basenames = getBasenames();
if(basenames == null) {
LOGGER.debug("Missing basenames of the resources bundles.");
return null;
}
List<String> prefixes = new ArrayList<String>(basenames.length);
for(int i = 0; i < basenames.length; ++i) {
prefixes.add(FilenameUtils.getName(basenames[i]));
}
return prefixes;
}
private Set<Resource> getAllResources() {
if(cacheResources != null) {
return cacheResources;
}
String[] basenames = getBasenames();
if(basenames == null) {
LOGGER.debug("Missing basenames of the resources bundles.");
return null;
}
ResourceLoader resourceLoader = getResourceLoader();
if(resourceLoader == null) {
LOGGER.debug("Missing ResourceLoader.");
return null;
}
Set<Resource> resources = new HashSet<Resource>();
for(String basename : basenames) {
for(Locale locale : Locale.getAvailableLocales()) {
List<String> filenames = calculateFilenamesForLocale(basename, locale);
for(String filename : filenames) {
Resource resource = resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
if( ! resource.exists()) {
resource = resourceLoader.getResource(filename + XML_SUFFIX);
}
if(resource.exists()) {
resources.add(resource);
}
}
}
}
cacheResources = resources;
return resources;
}
private String[] getBasenames() {
Field field = ReflectionUtils.findField(org.springframework.context.support.ReloadableResourceBundleMessageSource.class, "basenames");
if(field == null) {
LOGGER.debug("Missing field 'basenames' from 'org.springframework.context.support.ReloadableResourceBundleMessageSource' class.");
return null;
}
ReflectionUtils.makeAccessible(field);
try {
return (String[]) field.get(this);
} catch(Exception ex) {
LOGGER.debug("Unable to get the 'basenames' field value from the 'org.springframework.context.support.ReloadableResourceBundleMessageSource' class.");
return null;
}
}
private ResourceLoader getResourceLoader() {
Field field = ReflectionUtils.findField(org.springframework.context.support.ReloadableResourceBundleMessageSource.class, "resourceLoader");
if(field == null) {
LOGGER.debug("Missing field 'resourceLoader' from 'org.springframework.context.support.ReloadableResourceBundleMessageSource' class.");
return null;
}
ReflectionUtils.makeAccessible(field);
try {
return (ResourceLoader) field.get(this);
} catch(Exception ex) {
LOGGER.debug("Unable to get the 'resourceLoader' field value from the 'org.springframework.context.support.ReloadableResourceBundleMessageSource' class.");
return null;
}
}
}
( Spring ), .
ReloadableResourceBundleMessageSource, .
:
<bean id="messageSource" class="fr.ina.archibald.web.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:config/i18n/messages" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
, Locales:
@Inject
private ReloadableResourceBundleMessageSource resourceBundleMessageSource;
, , Locale Locale of the User , Spring LocaleChangeInterceptor ( URL = > 'http://your.domain?lang=en '):
package fr.ina.archibald.web.resolver;
import java.util.Locale;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import fr.ina.archibald.commons.annotation.Log;
import fr.ina.archibald.dao.entity.UserEntity;
import fr.ina.archibald.security.entity.CustomUserDetails;
import fr.ina.archibald.security.util.SecurityUtils;
import fr.ina.archibald.service.UserService;
import fr.ina.archibald.web.support.ReloadableResourceBundleMessageSource;
public class SessionLocaleResolver extends org.springframework.web.servlet.i18n.SessionLocaleResolver {
@Log
private Logger logger;
@Inject
private UserService userService;
@Inject
private ReloadableResourceBundleMessageSource resourceBundleMessageSource;
@Override
public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale newLocale) {
super.setLocale(req, res, newLocale);
updateUserLocale(newLocale);
}
private void updateUserLocale(final Locale newLocale) {
if(newLocale == null) {
logger.debug("Cannot update the user browsing locale. The new locale is null.");
return;
}
CustomUserDetails userDetails = SecurityUtils.getCurrentUser();
if(userDetails == null || userDetails.getUser() == null) {
logger.debug("Cannot update the user browsing locale. The user is missing.");
return;
}
UserEntity user = userDetails.getUser();
if(newLocale.equals(user.getBrowsingLocale()) || ! resourceBundleMessageSource.isAvailableLocale(newLocale)) {
return;
}
user.setBrowsingLocale(newLocale);
try {
userService.update(user);
} catch(Exception ex) {
logger.error("The browsing locale of the user with identifier " + user.getUserId() + " cannot be updated.", ex);
}
}
}
SessionLocaleResolver:
<bean id="localeResolver" class="fr.ina.archibald.web.resolver.SessionLocaleResolver">
<property name="defaultLocale" value="fr" />
</bean>
, ...
!: -)