Углубленный анализ локали

Java

Резюме

Локаль — это технический момент, который легко игнорировать в повседневной разработке. Особенно при разработке некоторых проектов, предназначенных только для внутреннего рынка и только на китайском языке, Locale может быть прямо проигнорирован. И когда проект предложил мультиязычную поддержку, потому что нет хорошего понимания, он, возможно, зарыл для себя много дыр.

Что такое локаль

фактическиjava.util.LocaleВ Java Doc есть очень подробное объяснение, поэтому я не буду объяснять его слишком подробно.

A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.

Главное помнить, что экземпляр Locale включает следующую информацию. При многолетнем опыте разработки сценарий и вариант практически бесполезны. Вступление не очень.

  • language
  • script
  • country (region)
  • variant

lanugage

ISO 639 alpha-2 or alpha-3 language code

В реальном использовании, в основном, мы не можем коснуться языка, представленного 3-значными буквами.

country

ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.

Также в действительности используются страны, которые в основном используют 2 буквы.

сценарий и вариант

IANA Language Subtag RegistryПолный список определен. Я чувствую, что сценарий - это другое название региона, а вариант - это диалект. Просто узнайте.

При реальном использовании первой реакцией большинства учащихся может быть написание следующего Locale.

  • zh_CN
  • en_US
  • ja_JP

Если вы думаете только об этом, откройте браузер -> инструменты разработчика -> консоль и введите следующие js

window.navigator.language

Браузер китайский, и вы снова в Китае. Выход zh-CN Затем, если вы сбрасываете язык браузера, например, установите его на английский. Выполнить снова, вывод en-CN Что??en-CN??Что это за хрень? Прежде всего, с помощью этой локали мы можем узнать страну, которая связана с тем регионом, в котором вы на самом деле находитесь. Как с этим быть? Подробности о том, как применить его позже.

Сценарии применения

Сценарий использования Locale в основном заключается в том, чтобы отображать по-разному в зависимости от страны и языка. Фактический опыт основан на следующих двух моментах.

  1. Мультиязычность (подробнее об этом ниже)
  2. Отображается сумма.
  3. Отображается формат даты.

Возьмем в качестве примера отображение суммы.Если у вас нет подобного опыта разработки, вы можете подумать об этом.Существуют разные форматы для суммы. Вообще не все 1,000,000.00. Тогда вы ошибаетесь. Приведите два примера.

  1. Японский язык. Японская сумма без десятичной точки.
  2. Французский. Во французском языке разделителем тысяч является пробел, а десятичной точкой — запятая. например 1 000 000,00

Правильное понимание и правильное использование Locale позволяет писать стандартный, лаконичный и высококачественный код. Вместо того, чтобы видеть уловки и разбирать уловки, каждый язык пишет свою реализацию.

Правильная поза для создания экземпляра Locale

Очень важно использовать правильную позу для создания, что очень важно в прикладной части Spring.

Следующий код является наиболее созданным способом, который я видел.

Locale locale = new Locale("zh_CN");

Среди них zh_CN может быть передан напрямую из внешнего интерфейса, который для удобства напрямую используется в качестве параметра метода построения Locale. На самом деле это неправильное использование. При таком использовании созданным Locale.language является zh_cn.

Вот две правильные позы. Затем сравните результаты

  1. Locale API
// 使用Locale构造方法
// 如果前端传入"zh_CN",此处需要自行解析并拆分
Locale locale = new Locale("zh", "CN");
// 使用Locale预置常量。请自行查看Locale源代码。
Locale locale = Locale.SIMPLIFIED_CHINESE
  1. Commons-Lang LocaleUtils.toLocale()
Locale locale = LocaleUtils.toLocale("zh_CN");

Вышеупомянутые три метода создания могут создать один и тот же объект Locale. Я рекомендую использовать Commons Lang3LocaleUtils.toLocale()

Давайте сравним разницу между Locale, созданным ошибкой, и положительным решением.

public class LocaleShowCase {
    public static void main(String[] args) {
        logLocale(new Locale("zh_CN"));
        logLocale(Locale.SIMPLIFIED_CHINESE);
    }

    private static void logLocale(Locale locale) {
        System.out.println("=================================");
        System.out.println(String.format("Locale.toString: %s", locale.toString()));
        System.out.println(String.format("Language: %s", locale.getLanguage()));
        System.out.println(String.format("Country: %s", locale.getCountry()));
        System.out.println(String.format("LanguageTag: %s", locale.toLanguageTag()));
        System.out.println("=================================");
    }
}

выходной результат

=================================
Locale.toString: zh_cn
Language: zh_cn
Country: 
LanguageTag: und
=================================
=================================
Locale.toString: zh_CN
Language: zh
Country: CN
LanguageTag: zh-CN
=================================

Давайте проанализируем результаты

  1. Сначала посмотрите на данные. Неправильный способ создания — использовать zh_CN в качестве языка. Country и LanguageTag пусты
  2. Вывод языка в нижнем регистре
  3. Выпуск страны капитализируется
  4. LanugageTag связывает язык и страну с помощью "-"
  5. Locale.toString - это язык и страна, связанные "_"

zh_CN vs zh-CN

Когда использовать "_" и когда использовать "-", это действительно сложнее. Например, при анализе Locale в request.getLocale() одновременно могут обрабатываться два формата. Входной параметр Commons Lang3 LocaleUtils.toLocale() поддерживает только формат подчеркивания.

Однако мы можем определить такую ​​спецификацию, что во внутреннем сервисе используется только формат «_», а во внешнем — только формат «-».

внешний интерфейс

Интерфейсных фреймворков слишком много, так что давайте просто поговорим о umi+dva+react, в который я недавно играл.

UMI

Обработка локали

Используется в проектах, разработанных umiumi-plugin-react/localeдля обработки Locale.

import { setLocale, getLocale } from 'umi-plugin-react/locale';

setLocale(language, true);
getLocale();

многоязычный

Файлы ресурсов именуются в формате «-».

.
|-- en-US
|   |-- common.ts
|   `-- form.ts
|-- ja-JP
|   |-- common.ts
|   `-- form.ts
|-- zh-CN
|   |-- common.ts
|   `-- form.ts
|-- en-US.ts
|-- ja-JP.ts
`-- zh-CN.ts

Отображение на нескольких языках

import { formatMessage } from 'umi-plugin-react/locale';
formatMessage({id: 'xxx'})

отображение даты

import { formatDate } from 'umi-plugin-react/locale';
formatDate(new Date());

Цифровой дисплей

formatNumber(10000000.00);

серверная служба

Здесь представлен только Stateless Rest API, разработанный на основе Spring Boot. SpringMVC устарел и не будет представлен.

Обработка локали

Как подтвердить локаль, используемую текущим вызовом API?

Spring Boot использует LocaleResolver, чтобы определить, какую локаль использовать для текущего вызова API. После того как LocaleResolver получает Locale, Locale сохраняется в LocaleContextHolder.

Spring Boot предоставляет несколько стандартных реализаций, основное отличие которых заключается в том, что соответствующие методы получения предоставляются для разных мест, где хранится Locale.

  • AcceptHeaderLocaleResovler получается из Accept-Language заголовка запроса.
  • CookieLocaleResolver получает из файла cookie
  • FixedLocaleResolver Фиксированная локаль, использует только локаль, настроенную системой
  • SessionLocaleResolver получает из сеанса

Хотя Spring предоставляет множество реализаций для получения LocaleResolver, в конкретных бизнес-сценариях будут более сложные сценарии. Например, его необходимо установить в соответствии с языком текущего пользователя, вошедшего в систему. На данный момент нам нужно самим реализовать набор LocaleResovler.

многоязычный

ресурс

В Spring мы можем добавить файл свойств для многоязычной поддержки.

.
|-- java
....
`-- resources
    `-- i18n
        |-- messages.properties
        |-- messages_ja.properties
        |-- messages_ja_JP.properties
        |-- messages_xx.properties
        |-- messages_zh.properties
        |-- messages_zh_CN.properties
        |-- another.properties
        |-- another_zh_CN.properties
        |-- another_zh_TW.properties
        |-- another_en.properties
        `-- another_ja.properties

можно увидеть на примере

  • Существует два набора файлов ресурсов. Одна группа — это сообщение, а одна группа — это сообщение. Модульное управление может быть сделано таким образом в проекте.
  • Каждый набор файлов ресурсов может иметь собственный список поддерживаемых локалей.
  • Каждый файл определяет перевод для соответствующей локали.
  • Для языка по умолчанию нет файла ресурсов локали. Такие как messages.properties, other.properties. Если ни одна локаль не соответствует, используется содержимое файла ресурсов по умолчанию.
  • messages_xx.properties? Это законно. Но его рекомендуется использовать, и он в основном не встречается. Вот заметка, фреймворк поддерживается. Используйте new Locale("xx"), чтобы создать Locale с языком xx.

Приоритет соответствия локали

language+country+variant > language+country > lanaguage

Возьмите message*.properties в качестве примера:

  • zh_CN -> messages_zh_CN.properties
  • zh или zh_JP -> messages_zh.properties
  • en_US -> messages.properties.

настроитьMessageSource

@Configuration
public class MessageConfiguration {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another");
        return messageSource;
    }
}

Обратите внимание здесьmessageSource.setBasenames("classpath:i18n/messages", "classpath:i18n/another")BaseName — это имя группы файла ресурсов.

Как использовать

@Service
public class XXXService {
    private final MessageSource messageSource;
    public XXXService(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
    public String getI18N(String key, Object[] params) {
        return messageSource.getMessage(key, params, LocaleContextHolder.getLocale())
    }
}

отображение даты

DateFormat fullDF = DateFormat.getDateInstance(DateFormat.FULL, locale);
System.out.println(fullDF.format(new Date()));

Цифровой дисплей

System.out.println(NumberFormat.getInstance(locale).format(10000000));

Применение в реальных сценариях

Обычные продукты в основном требуют входа пользователя, что также упоминается в LocaleResovler. Мы можем использовать Locale в соответствии с языковыми настройками текущего пользователя. Таким образом, лучше контролировать Локаль, полученную службой. И мы можем определить языки, поддерживаемые системой при разработке, такие как zh_CN, en_US, ja_JP. Таким образом, вызов API после входа пользователя не должен беспокоиться о получении неподдерживаемой локали. И поскольку пользователю нужно установить язык, нам нужно реализовать LocaleResovler самостоятельно.

@Data
public class Principal {
    private String username;
    private String language;
    ...
}
public class CustomLocaleResolver implements LocaleResolver {
    private Locale defaultLocale;

    public CustomLocaleResolver(Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public Locale resolveLocale(HttpServletRequest request) {
        Principal principal = (Principal) SecurityContextHolder.getContext().getAuthentication();
        if (principal != null && !StringUtils.isEmpty(principal.getLanguage())) {
            return LocaleUtils.toLocale(principal.getLanguage());
        } else {
            return request.getHeader("Accept-Language") != null ? request.getLocale() : this.defaultLocale;
        }
    }

    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        throw new UnsupportedOperationException("Cannot change Principal data - use a different locale resolution strategy");
    }
}

@Configuration
public class LocaleConfiguration {
    @Bean
    public CustomLocaleResolver localeResolver(@Value("${default-language:zh_CN}") String defaultLanguage) {
        return new CustomLocaleResolver(LocaleUtils.toLocale(defaultLanguage));
    }
}

Прежде чем пользователь войдет в систему и внешний интерфейс не выполнит никакой обработки, серверная часть получит Locale, аналогичный en_CN, но не может соответствовать файлу ресурсов. Если вы установите перевод по умолчанию для каждого языка в соответствии со следующим дизайном файла ресурсов, вы можете решить проблему получения неправильной локали.

.
|-- java
....
`-- resources
    `-- i18n
        |-- messages.properties
        |-- messages_ja.properties
        |-- messages_ja_JP.properties
        |-- messages_en.properties
        |-- messages_en_US.properties
        |-- messages_en_GB.properties
        |-- messages_zh.properties
        |-- messages_zh_CN.properties
        `-- messages_zh_TW.properties

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

Cheers~