Динамически генерировать список доступных языков в Spring MVC - PullRequest
4 голосов
/ 21 марта 2011

Я установил i18n в Spring MVC 3, и он работает правильно. Существует несколько файлов, каждый со своим языком: messages_en.properties, messages_de.properties и т. Д.

В одном из моих JSP мне нужно показать пользователям комбо со всеми доступными языками, и я бы хотел, чтобы этот список был динамическим, то есть создавался на лету из существующих языковых файлов на сервере.

Есть ли какой-нибудь встроенный метод для генерации этого списка? Или я должен прибегнуть к проверке папки, в которой находятся языковые файлы, и проанализировать их?

Спасибо!

Начо

Ответы [ 5 ]

1 голос
/ 30 марта 2011

Как насчет того, чтобы поместить это во что-то, имеющее доступ к 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, и все готово.

0 голосов
/ 19 июля 2019

Надеюсь, это поможет, если кто-то все еще ищет краткий ответ:

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
0 голосов
/ 24 мая 2014

Я хочу поделиться с вами своим решением.

Подтвержденный ответ (с двумя решениями) на текущий вопрос действительно интересен. Единственная проблема в первом решении - использовать жестко закодированный ключ сообщения (« currentLanguage »), который может исчезнуть из соответствующего файла свойств. Второй должен жестко закодировать базовое имя (" fr-messages_ ") файла свойств. Но имя файла можно изменить ...

Итак, я последовал примеру проверенного ответа, чтобы расширить свой пользовательский ResourceBundleMessageSource , чтобы сделать это.

Изначально мне нужно было получить содержимое файлов свойств сообщений Spring ( messages_en.properties , messages_fr.properties , ...), поскольку у меня есть полный интерфейс Javascript (используя ExtJs). Итак, мне нужно было загрузить все (интернационализированные) метки приложения на объект JS. Но его не существует ... По этой причине я разработал пользовательский класс ReloadableResourceBundleMessageSource . Соответствующими методами являются " getAllProperties () ", " getAllPropertiesAsMap () " и " getAllPropertiesAsMessages () ".

Позже мне нужно было получить доступные локали в приложении. И, читая эту страницу, я хотел расширить класс ReloadableResourceBundleMessageSource , чтобы сделать это. Вы можете увидеть методы " getAvailableLocales () " и " isAvailableLocale () " (для проверки только одного Locale).

<code>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
     * 
* * * @return набор {@code Locales} или ноль, если происходит ошибка. * / public Set getAvailableLocales () { if (cacheAvailableLocales! = null) { return cacheAvailableLocales; } cacheAvailableLocales = getLocales (getAllFileNames (), getMessageFilePrefixes ()); return cacheAvailableLocales; } / ** * Указывает, доступен ли указанный {@code Locale} в приложении. *

* Примеры результатов, возвращаемых, если приложение содержит файлы «messages_en.properties» и «messages_fr.properties»: * *

     * en --> true
     * fr --> true
     * de --> false
     * es --> false
     * 
* * @param locale {@code Locale}. * * @return {@code true}, если локаль доступна, {@code false} в противном случае. * / public boolean isAvailableLocale (окончательный языковой стандарт) { Set locales = getAvailableLocales (); if (locales == null) { вернуть ложь; } возврат locales.contains (локаль); } // ********************** ЧАСТНЫЕ МЕТОДЫ ********************** / ** * Возвращает {@code Locales}, указанный в именах файлов. * * @param fileNames имена файлов. * @param filePrefixes префиксы базовых имен пакетов ресурсов. * * @ вернуть набор {@code Locales}. * / приватный набор getLocales (окончательный список fileNames, список filePrefixes) { if (fileNames == null || fileNames.isEmpty () || filePrefixes == null || filePrefixes.isEmpty ()) { LOGGER.debug ("Невозможно получить доступные локали. FileNames = [" + StringUtils.toString (fileNames) + "], filePrefixes = [" + StringUtils.toString (filePrefixes) + "]"); вернуть ноль; } Set locales = new HashSet (); for (строковое имя файла: имя файла) { String fileNameWithoutExtension = FilenameUtils.getBaseName (fileName); for (строка filePrefixe: filePrefixes) { Строка localeStr = fileNameWithoutExtension.substring (filePrefixe.length () + 1); пытаться { locales.add (LocaleUtils.toLocale (localeStr)); } catch (IllegalArgumentException ex) { Продолжить; } } } вернуть локали; } / ** * Возвращает все имена файлов пакетов ресурсов. * * @ вернуть список имен файлов или {@code null}, если ресурсы отсутствуют.* / приватный список getAllFileNames () { Set resources = getAllResources (); if (resources == null) { LOGGER.debug («Недостающие пакеты ресурсов.»); вернуть ноль; } List filenames = new ArrayList (resources.size ()); для (Ресурсный ресурс: ресурсы) { filenames.add (resource.getFilename ()); } вернуть имена файлов; } / ** * Получает массив префиксов для файлов сообщений. * *
     * "WEB-INF/messages"               --> "messages"
     * "classpath:config/i18n/messages" --> "messages"
     * "messages"                       --> "messages"
     * 
* * @ вернуть массив префиксов или ноль, если произошла ошибка. * / приватный список getMessageFilePrefixes () { String [] basenames = getBasenames (); if (basenames == null) { LOGGER.debug («Отсутствуют базовые имена пакетов ресурсов.»); вернуть ноль; } List prefixes = new ArrayList (basenames.length); for (int i = 0; i getAllResources () { if (cacheResources! = null) { вернуть cacheResources; } String [] basenames = getBasenames (); if (basenames == null) { LOGGER.debug («Отсутствуют базовые имена пакетов ресурсов.»); вернуть ноль; } ResourceLoader resourceLoader = getResourceLoader (); if (resourceLoader == null) { LOGGER.debug ("Отсутствует ResourceLoader."); вернуть ноль; } Set resources = new HashSet (); for (Строковое базовое имя: базовые имена) { for (Locale locale: Locale.getAvailableLocales ()) { List filenames = CalculateFileNamesForLocale (базовое имя, локаль); for (строковое имя файла: filenames) { Resource resource = resourceLoader.getResource (filename + PROPERTIES_SUFFIX); if (! resource.exists ()) { resource = resourceLoader.getResource (filename + XML_SUFFIX); } if (resource.exists ()) { resources.add (ресурс); } } } } cacheResources = resources; вернуть ресурсы; } / ** * Получает массив базовых имен, каждое из которых следует базовому соглашению ResourceBundle, в котором не указывается расширение файла или коды языков. * * @ вернуть массив базовых имен или ноль, если произошла ошибка. * * @see org.springframework.context.support.ReloadableResourceBundleMessageSource # setBasenames * / private String [] getBasenames () { Поле field = ReflectionUtils.findField (org.springframework.context.support.ReloadableResourceBundleMessageSource.class, "basenames"); if (field == null) { LOGGER.debug («Отсутствует базовое имя поля» из «org.springframework.context.support.ReloadableResourceBundleMessageSource 'class."); вернуть ноль; } ReflectionUtils.makeAccessible (поле); пытаться { return (String []) field.get (this); } catch (Exex ex) { LOGGER.debug («Невозможно получить значение поля« basenames »из класса org.springframework.context.support.ReloadableResourceBundleMessageSource».); вернуть ноль; } } / ** * Получает загрузчик ресурсов. * * @ вернуть загрузчик ресурсов. * * @see org.springframework.context.support.ReloadableResourceBundleMessageSource # setResourceLoader * / private ResourceLoader getResourceLoader () { Поле field = ReflectionUtils.findField (org.springframework.context.support.ReloadableResourceBundleMessageSource.class, "resourceLoader");if (field == null) { LOGGER.debug ("Отсутствует поле 'resourceLoader' из 'org.springframework.context.support.ReloadableResourceBundleMessageSource' class."); вернуть ноль; } ReflectionUtils.makeAccessible (поле); пытаться { return (ResourceLoader) field.get (this); } catch (Exex ex) { LOGGER.debug ("Невозможно получить значение поля 'resourceLoader' из класса 'org.springframework.context.support.ReloadableResourceBundleMessageSource'.); вернуть ноль; } } }

Если вы хотите использовать две функциональные возможности (получить доступные 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 обнаруживает изменение (например, через URL => 'http://your.domain? Lang = ан ):

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>

Надеюсь, это будет вам полезно ...

Наслаждайтесь! : -)

0 голосов
/ 30 марта 2011

Хорошо, найдено два решения. Для обоих предположим, что они выполняются внутри Spring MVC @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);  

Это решение требует, чтобы в каждом из ваших языковых файлов .properties вы указывали запись с языком (в приведенном выше примере это было бы 'currentLanguage'). Например, в messages_it.properties должна быть следующая запись: currentLanguage = Italiano

2.- Необработанный метод, то есть прямой доступ к папке / файлам: при условии, что языки файлов находятся в / WEB-INF / languages ​​и имеют базовое имя fr-messages:


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

А затем в вашем JSP визуализируйте поле выбора, используя карту languages:

<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>
0 голосов
/ 29 марта 2011

Это была бы хорошая функция, но я не думаю, что вы найдете встроенный метод, потому что механизм сквозного доступа к файлам свойств означает, что , имеющий a messages_de.properties, не делаетне обязательно означает каждое сообщение доступно на немецком языке.Поэтому Spring не может создать хороший Map<Locale, ResourceBundle>, из которого вы могли бы получить ключи.

Вы должны иметь возможность использовать Spring, чтобы облегчить вашу работу, и при этом не нужнопоразить файловую систему самостоятельно:

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

  • Затем выполните итерацию по Enumeration, получив часть локали (en, de и т. д.)с каждого URL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...