Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/list/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
List 在SpringMVC中动态生成可用语言列表_List_Dynamic_Spring Mvc_Internationalization - Fatal编程技术网

List 在SpringMVC中动态生成可用语言列表

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

我已经在SpringMVC3中设置了i18n,并且它工作正常。 有几个文件,每个文件都有自己的语言:messages\u en.properties、messages\u de.properties等

在我的一个JSP中,我需要向用户显示一个包含所有可用语言的组合,我希望此列表是动态的,即从服务器中的现有语言文件动态生成

是否有生成此列表的内置方法?或者我必须检查语言文件所在的文件夹并解析它们吗

谢谢


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