在Spring MVC中动态生成可用语言列表

时间:2011-03-21 16:25:59

标签: list dynamic spring-mvc internationalization

我在Spring MVC 3中设置了i18n,它运行正常。 有几个文件,每个文件都有自己的语言:messages_en.properties,messages_de.properties等。

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

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

谢谢!

纳乔

6 个答案:

答案 0 :(得分:0)

这将是一个很好的功能,但我认为你不会找到内置方法,因为属性文件的“直通”机制意味着拥有一个messages_de.properties并不一定意味着每条消息都以德语提供。所以Spring无法建立一个很好的Map<Locale, ResourceBundle>,你可以从中获得密钥。

Enumeration<URL> allMsgs = bundleClassLoader.findResources("messages");

  • 然后遍历Enumeration,获取每个en
  • 的区域设置(deURL等)部分

答案 1 :(得分:0)

如何将其置于可以访问ReloadableResourceBundleMessageSource

的内容中  
ReloadableResourceBundleMessageSource rrbms = getMessageSource();   
final String defaultMessage = "NOT FOUND";
List<Locale> availableLocales = new ArrayList<Locale>();
for (Locale locale : Locale.getAvailableLocales()) {
    String msg = rrbms.getMessage("test.code", null, defaultMessage, locale);
    if (!defaultMessage.equals(msg)) {
       availableLocales.add(locale);
    }
}

只需确保每种支持的语言都提供test.code值即可。

答案 2 :(得分:0)

好的,找到了两个解决方案。对于两者,假设它们是在Spring MVC @Controller - 带注释的类中执行的。每个都将生成一个HashMap(languages),其中键是2个字母的ISO语言代码,值是语言名称(在当前Locale中,在这些示例中是一个名为{{1}的静态变量})

1.-由@millhouse提交的那个(见上文/下方),经过一些调整后才能运作:

HSConstants.currentLocale

此解决方案要求在您的每个语言.properties文件中,使用该语言设置条目(在上面的示例中,它将是'currentLanguage')。对于ecample,在messages_it.properties中,必须有这样的条目:currentLanguage = Italiano

2.- Raw方法,即直接访问文件夹/文件:假设文件语言是/ WEB-INF / languages,并且具有fr-messages的基本名称:


    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);  

然后,在JSP中,使用 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); 地图渲染选择框:

languages

答案 3 :(得分:0)

我希望与您分享我的解决方案。

当前问题的经过验证的响应(使用两个解决方案)实际上是有趣的。 第一个解决方案的唯一问题是使用可以从相应属性文件中消失的硬编码消息密钥(&#34; currentLanguage &#34;)。 第二个需要硬编码属性文件的基名(&#34; fr-messages _ &#34;)。但文件名可以更改......

所以,我按照验证响应的示例来扩展我的自定义 ResourceBundleMessageSource 来做到这一点。

最初,我需要获取Spring消息属性文件的内容( messages_en.properties messages_fr.properties ,...),因为我有一个完整的Javascript前端(使用ExtJs)。所以,我需要在JS对象上加载应用程序的所有(国际化)标签。 但它并不存在......出于这个原因,我开发了一个自定义 ReloadableResourceBundleMessageSource 类。相应的方法是&#34; getAllProperties()&#34;,&#34; getAllPropertiesAsMap()&#34;和&#34; getAllPropertiesAsMessages()&#34;。

稍后,我需要在应用程序上获取可用的Locales。阅读这个stackoverflow页面,我有了扩展我的 ReloadableResourceBundleMessageSource 类的想法。你可以看到&#34; getAvailableLocales()&#34;和&#34; isAvailableLocale()&#34; (仅测试一个Locale)方法。

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;
        }
    }
}

如果你想使用两个功能(获取可用的Locales并从属性文件中获取所有Spring消息),那么你需要获得这个完整的类。

要使用此 ReloadableResourceBundleMessageSource ,它非常简单。 您需要声明资源包:

<!-- 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;

这是一个用法示例,用于在Spring LocaleChangeInterceptor检测到更改时自动更新数据库上用户的浏览区域设置,以检查Locale是否可用(例如,通过URL =&gt;&#39; http://your.domain?lang=en &#39):

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);
        }
    }

}

相应的 SessionLocaleResolver 声明:

<!-- 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>

我希望这对你有用......

享受! : - )

答案 4 :(得分:0)

如果有人仍在寻找简明答案,希望这会有所帮助:

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());
    }
}

想法是Autowire所有可用的消息源messages_*.properties并根据文件名导出可用的语言环境。 默认语言环境可以单独标记为受支持,如下所示:

availableLocals.add(Locale.getDefault()); // for default messages.properties

答案 5 :(得分:0)

想法如下:在test文件中创建.properties变量,并且此变量的内容必须为ISO国家名称(2个符号,例如Russia = RU)。对于默认语言环境,您可以设置test=DEFAUL
文件夹结构和资源包内容:
enter image description here enter image description here

{
    public static void main(String[] args) {
        Locale[] availableLocales = Locale.getAvailableLocales();
        List<Locale> existingLocales = new ArrayList<>();
        existingLocales.add(Locale.getDefault());

        for (Locale value : availableLocales) {
            if (isLocaleExist(value)) {
                existingLocales.add(value);
            }
        }

        for (Locale value : existingLocales) {
            System.out.println(value);
        }
    }

    public static boolean isLocaleExist(Locale locale) {
        ResourceBundle bundle = ResourceBundle.getBundle("languages.but", locale);
        final String value = bundle.getString("test");
        return value.equals(locale.getCountry());
    }
}

代码结果: enter image description here