List 在SpringMVC中动态生成可用语言列表
我已经在SpringMVC3中设置了i18n,并且它工作正常。 有几个文件,每个文件都有自己的语言:messages\u en.properties、messages\u de.properties等 在我的一个JSP中,我需要向用户显示一个包含所有可用语言的组合,我希望此列表是动态的,即从服务器中的现有语言文件动态生成 是否有生成此列表的内置方法?或者我必须检查语言文件所在的文件夹并解析它们吗 谢谢List 在SpringMVC中动态生成可用语言列表,list,dynamic,spring-mvc,internationalization,List,Dynamic,Spring Mvc,Internationalization,我已经在SpringMVC3中设置了i18n,并且它工作正常。 有几个文件,每个文件都有自己的语言:messages\u en.properties、messages\u de.properties等 在我的一个JSP中,我需要向用户显示一个包含所有可用语言的组合,我希望此列表是动态的,即从服务器中的现有语言文件动态生成 是否有生成此列表的内置方法?或者我必须检查语言文件所在的文件夹并解析它们吗 谢谢 Nacho这将是一个很好的功能,但我认为您不会找到内置的方法,因为属性文件的“fall-thr
Nacho这将是一个很好的功能,但我认为您不会找到内置的方法,因为属性文件的“fall-through”机制意味着拥有messages\u de.properties并不一定意味着所有消息都有德语版本。因此Spring无法构建一个好的
映射
,您可以从中获取密钥
不过,您应该能够使用Spring使您的工作更轻松,而不必亲自访问文件系统:
- 从Spring的
- 将该
用于所有“消息”资源:ClassLoader
Enumeration allMsgs=bundleClassLoader.findResources(“消息”)代码>
- 然后迭代
枚举
,从每个URL
如何将其放入能够访问可重新加载的ResourceBundleMessageSource
的东西中
ReloadableResourceBundleMessageSource rrbms=getMessageSource();
最终字符串defaultMessage=“未找到”;
List availableLocales=new ArrayList();
for(Locale:Locale.getAvailableLocales()){
字符串msg=rrbms.getMessage(“test.code”,null,defaultMessage,locale);
如果(!defaultMessage.equals(msg)){
添加(区域设置);
}
}
只要确保每种支持的语言都提供了一个
测试。code
值,您就完成了。好的,找到了两种解决方案。对于这两者,假设它们都是在SpringMVC@Controller
-注释类中执行的。每种语言都将生成一个HashMap(languages
),其中键是两个字母的ISO语言代码,值是语言名称(在当前语言环境中,在这些示例中是名为HSConstants.currentLocale
)的静态变量)
1.-由@millhouse(见上/下)提交的,经过一点调整后生效:
HashMap languages = new HashMap();
final String defaultMessage = "NOT FOUND";
HashMap availableLocales = new HashMap();
for (Locale locale : Locale.getAvailableLocales()) {
String msg = rrbms.getMessage("currentLanguage", null, defaultMessage, locale);
if (!defaultMessage.equals(msg) && !availableLocales.containsKey(locale.getLanguage())){
availableLocales.put(locale.getLanguage(), locale);
}
}
for (String c : availableLocales.keySet()){
languages.put(c, availableLocales.get(c).getDisplayLanguage(HSConstants.currentLocale));
}
model.addAttribute("languages", languages);
此解决方案要求,在每个language.properties文件中,使用该语言设置一个条目(在上面的示例中,它将是“currentLanguage”)。例如,在messages_it.properties中,必须有这样一个条目:currentLanguage=Italiano
2.-Raw方法,即直接访问文件夹/文件:假设文件语言为/WEB-INF/languages,并且基本名称为fr消息:
HashMap languages = new HashMap();
String languagesFolderPath = request.getSession().getServletContext().getRealPath("/WEB-INF/languages");
File folder = new File(languagesFolderPath);
File[] listOfFiles = folder.listFiles();
for (int i = 0; i < listOfFiles.length; i++){
String fileName = listOfFiles[i].getName();
if (fileName.startsWith("fr-messages_") && fileName.endsWith(".properties")){
// Extract the language code, which is between the underscore and the .properties extension
String language = fileName.substring(12, fileName.indexOf(".properties"));
Locale l = new Locale(language);
languages.put(language, l.getDisplayLanguage(HSConstants.currentLocale));
}
}
model.addAttribute("languages", languages);
我希望与你分享我的解决方案 对当前问题的有效回答(有两种解决方案)确实是相互质疑的。 第一种解决方案的唯一问题是使用硬编码的消息密钥(“currentLanguage”),该密钥可能会从相应的属性文件中消失。 第二个需要硬编码属性文件的basename(“fr-messages”)。但是文件名可以更改 因此,我按照验证响应的示例扩展了我的定制ResourceBundleMessageSource来实现这一点 首先,我需要获取Spring消息属性文件(messages_en.properties、messages_fr.properties等)的内容,因为我有一个完整的Javascript前端(使用ExtJs)。因此,我需要在JS对象上加载应用程序的所有(国际化)标签。 但它并不存在。。。因此,我开发了一个定制的可重新加载的ResourceBundleMessageSource类。相应的方法是“getAllProperties()”、“getAllPropertiesAsMap()”和“getAllPropertiesAsMessages()” 稍后,我需要获取应用程序上的可用区域设置。阅读这个stackoverflow页面,我想到了扩展我的ReloadableResourceBundleMessageSource类来实现这一点。您可以看到“getAvailableLocales()”和“isAvailableLocale()”(只测试一个区域设置)方法 下面是一个使用示例,当Spring LocaleChangeInterceptor检测到更改(通过URL,例如=>'')时,在自动更新数据库上用户的浏览区域设置之前,检查该区域设置是否可用:
包fr.ina.archibald.web.resolver;
导入java.util.Locale;
导入javax.inject.inject;
导入javax.servlet.http.HttpServletRequest;
导入javax.servlet.http.HttpServletResponse;
导入org.slf4j.Logger;
导入fr.ina.archibald.commons.annotation.Log;
导入fr.ina.archibald.dao.entity.UserEntity;
导入fr.ina.archibald.security.entity.CustomUserDetails;
导入fr.ina.archibald.security.util.SecurityUtils;
导入fr.ina.archibald.service.UserService;
导入fr.ina.archibald.web.support.ReloadableResourceBundleMessageSource;
/**
*自定义会话LocaleResolver。
*
*@author srambeau
*
*@见org.springframework.web.servlet.i18n.sessionLocalerResolver
*/
公共类SessionLocaleResolver扩展org.springframework.web.servlet.i18n.SessionLocaleResolver{
@日志
私人记录器;
@注入
私人用户服务;
@注入
私有可重新加载的resourceBundleMessageSource resourceBundleMessageSource;
@凌驾
public void setLocale(HttpServletRequest-req、HttpServletResponse-res、Locale-newLocale){
super.setLocale(req、res、newLocale);
updateUserLocale(newLocale);
}
// /**
//*返回此冲突解决程序应返回的默认区域设置(如果有)。
// */
//@覆盖
//公共区域设置getDefaultLocale(){
//返回super.getDefaultLocale();
// }
//****************************私人
<select name="language">
<c:forEach items="${languages}" var="language">
<c:choose>
<c:when test="${platform.language == language.key}">
<option value="${language.key}" selected="SELECTED">${language.value}</option>
</c:when>
<c:otherwise>
<option value="${language.key}">${language.value}</option>
</c:otherwise>
</c:choose>
</c:forEach>
</select>
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;
/**
* Custom {@link org.springframework.context.support.ReloadableResourceBundleMessageSource}.
*
* @author srambeau
*/
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;
/**
* Returns all messages for the specified {@code Locale}.
*
* @param locale the {@code Locale}.
*
* @return a {@code Properties} containing all the expected messages or {@code null} if the {@code locale} argument is null or if the properties are empty.
*/
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();
}
/**
* Returns all messages for the specified {@code Locale}.
*
* @param locale the {@code Locale}.
*
* @return a {@code Map} containing all the expected messages or {@code null} if the {@code locale} argument is null or if the properties are empty.
*/
@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);
}
/**
* Returns all messages for the specified {@code Locale}.
*
* @param locale the {@code Locale}.
*
* @return a {@code List<MessageEntity>} containing all the expected messages or {@code null} if the {@code locale} argument is null or if the properties are empty.
*/
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;
}
/**
* Returns the available {@code Locales} on the specified application context. Calculated from the Spring message files of the application context.
* <p>
* Example of Locales returned corresponding with the messages files defines on the application:
*
* <pre>
* messages_en.properties --> en
* messages_fr.properties --> fr
* messages_en.properties, messages_fr.properties --> en, fr
* </pre>
* </p>
*
* @return the set of {@code Locales} or null if an error occurs.
*/
public Set<Locale> getAvailableLocales() {
if(cacheAvailableLocales != null) {
return cacheAvailableLocales;
}
cacheAvailableLocales = getLocales(getAllFileNames(), getMessageFilePrefixes());
return cacheAvailableLocales;
}
/**
* Indicates if the specified {@code Locale} is available on the application.
* <p>
* Examples of results returned if the application contains the files "messages_en.properties" and "messages_fr.properties":
*
* <pre>
* en --> true
* fr --> true
* de --> false
* es --> false
* </pre>
*
* @param locale the {@code Locale}.
*
* @return {@code true} if the locale is available, {@code false} otherwise.
*/
public boolean isAvailableLocale(final Locale locale) {
Set<Locale> locales = getAvailableLocales();
if(locales == null) {
return false;
}
return locales.contains(locale);
}
// ********************** PRIVATE METHODES **********************
/**
* Returns the {@code Locales} specified on the file names.
*
* @param fileNames the file names.
* @param filePrefixes the basenames' prefixes of the resources bundles.
*
* @return the set of the {@code Locales}.
*/
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;
}
/**
* Returns all the file names of the resources bundles.
*
* @return the list of file names or {@code null} if the resources are missing.
*/
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;
}
/**
* Gets the array of the prefixes for messages files.
*
* <pre>
* "WEB-INF/messages" --> "messages"
* "classpath:config/i18n/messages" --> "messages"
* "messages" --> "messages"
* </pre>
*
* @return the array of the prefixes or null if an error occurs.
*/
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;
}
/**
* Returns all the resources bundles.
*
* @return the set of resources or null if {@code basenames} or the {@link ResourceLoader} is missing.
*/
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;
}
/**
* Gets the array of basenames, each following the basic ResourceBundle convention of not specifying file extension or language codes.
*
* @return the array of basenames or null if an error occurs.
*
* @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setBasenames
*/
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;
}
}
/**
* Gets the resource loader.
*
* @return the resource loader.
*
* @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setResourceLoader
*/
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;
}
}
}
<!-- Custom message source. -->
<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>
@Inject
private ReloadableResourceBundleMessageSource resourceBundleMessageSource;
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;
/**
* Custom SessionLocaleResolver.
*
* @author srambeau
*
* @see org.springframework.web.servlet.i18n.SessionLocaleResolver
*/
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);
}
// /**
// * Returns the default Locale that this resolver is supposed to fall back to, if any.
// */
// @Override
// public Locale getDefaultLocale() {
// return super.getDefaultLocale();
// }
// ********************** PRIVATE METHODES **********************
/**
* Updates the locale of the currently logged in user with the new Locale.
* <p>
* The locale is not updated if the specified locale is {@code null} or the same as the previous, if the user is missing or if an error occurs.
* </p>
*
* @param newLocale the new locale.
*/
private void updateUserLocale(final Locale newLocale) {
if(newLocale == null) {
logger.debug("Cannot update the user's browsing locale. The new locale is null.");
return;
}
CustomUserDetails userDetails = SecurityUtils.getCurrentUser();
if(userDetails == null || userDetails.getUser() == null) {
logger.debug("Cannot update the user's browsing locale. The user is missing.");
return;
}
UserEntity user = userDetails.getUser();
// Updates the user locale if and only if the locale has changed and is available on the application.
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);
}
}
}
<!-- This custom SessionLocaleResolver allows to update the user Locale when it change. -->
<bean id="localeResolver" class="fr.ina.archibald.web.resolver.SessionLocaleResolver">
<property name="defaultLocale" value="fr" />
</bean>
import org.springframework.core.io.Resource;
@Configuration
class LanguageConfig {
private final Set<Locale> availableLocals;
public LanguageConfig(@Value("classpath:messages_*.properties") final Resource[] localesResources) {
availableLocals = getAvailableLocalesFromResources(localesResources);
}
private Set<Locale> getAvailableLocalesFromResources(Resource[] localesResources) {
return Arrays.stream(localesResources).map(resource -> {
final String localeCode = resource.getFilename().split("messages_")[1].split(".properties")[0];
return Locale.forLanguageTag(localeCode);
}).collect(Collectors.toSet());
}
}
availableLocals.add(Locale.getDefault()); // for default messages.properties