파란하늘의 지식창고
반응형

Java의 Locale은 로케일 데이터 교환을 위한 LDML (UTS#35, UnicodeLocale Data Markup Language) BCP 47 호환 확장을 지원하는 RFC 4647 "Matching of LanguageTags" 및 RFC 5646 "Tags for Identifying Languages"로 구성된 IETF BCP 47을 구현한 것이다.

여러 속성들이 있지만 보통 Language가 필수이고 추가로 country, script, variant, extensions를 사용한다.
(거의 대부분 {language}_{country}의 형태로 사용한다. ex: ko_KR)

언어나 국가에 대한 code는 불변이 아니고 세월이 변하면 달라질 수 있다.

이전 버전 JDK의 java.util.Locale은 오래된 language에 대해 호환을 유지하기 위해 다음과 같은 형태로 내부에서 처리하고 있었다.

public Locale(String language, String country, String variant) {
    if (language== null || country == null || variant == null) {
        throw new NullPointerException();
    }
    baseLocale = BaseLocale.getInstance(convertOldISOCodes(language), "", country, variant);
    localeExtensions = getCompatibilityExtensions(language, "", country, variant);
}

private static String convertOldISOCodes(String language) {
    // we accept both the old and the new ISO codes for the languages whose ISO
    // codes have changed, but we always store the OLD code, for backward compatibility
    language = LocaleUtils.toLowerString(language).intern();
    if (language == "he") {
        return "iw";
    } else if (language == "yi") {
        return "ji";
    } else if (language == "id") {
        return "in";
    } else {
        return language;
    }
}

예를 들어 "id_ID" 로케일을 사용하면 내부에선 "in_ID"로 처리되었다.

위의 부분이 JDK 17에선 다음과 같이 변경되었다.

public Locale(String language, String country, String variant) {
    if (language == null || country == null || variant == null) {
        throw new NullPointerException();
    }
    baseLocale = BaseLocale.getInstance(convertOldISOCodes(language), "", country, variant);
    localeExtensions = getCompatibilityExtensions(language, "", country, variant);
}

private static String convertOldISOCodes(String language) {
    // we accept both the old and the new ISO codes for the languages whose ISO
    // codes have changed, but we always store the NEW code, unless the property
    // java.locale.useOldISOCodes is set to "true"
    return BaseLocale.convertOldISOCodes(LocaleUtils.toLowerString(language).intern());
}

private static final boolean OLD_ISO_CODES = GetPropertyAction.privilegedGetProperties()
        .getProperty("java.locale.useOldISOCodes", "false")
        .equalsIgnoreCase("true");

public static String convertOldISOCodes(String language) {
    return switch (language) {
        case "he", "iw" -> OLD_ISO_CODES ? "iw" : "he";
        case "id", "in" -> OLD_ISO_CODES ? "in" : "id";
        case "yi", "ji" -> OLD_ISO_CODES ? "ji" : "yi";
        default -> language;
    };
}

java 17 이전과 이후의 변경된 부분을 테스트 코드로 확인해보자.

@Test
void localeTest() {
    List<Locale> localeList = Arrays.asList(
            new Locale("ko", "KR"),
            new Locale("zh", "TW"),
            new Locale("ja", "JP"),
            new Locale("ru", "RU"),
            new Locale("id", "ID"),
            new Locale("th", "TH"),
            new Locale("ar", "SA")
            );
    localeList.forEach(locale -> log.debug("language : {}, country :{}, toLanguageTag : {}, toString : {}", locale.getLanguage(), locale.getCountry(), locale.toLanguageTag(), locale.toString()));
}

Java 11에서는 다음과 같은 결과를 얻을 수 있다.

language : ko, country :KR, toLanguageTag : ko-KR, toString : ko_KR
language : zh, country :TW, toLanguageTag : zh-TW, toString : zh_TW
language : ja, country :JP, toLanguageTag : ja-JP, toString : ja_JP
language : ru, country :RU, toLanguageTag : ru-RU, toString : ru_RU
language : in, country :ID, toLanguageTag : id-ID, toString : in_ID
language : th, country :TH, toLanguageTag : th-TH, toString : th_TH
language : ar, country :SA, toLanguageTag : ar-SA, toString : ar_SA

java 17에서는 다음과 같은 결과를 얻을 수 있다.

language : ko, country :KR, toLanguageTag : ko-KR, toString : ko_KR
language : zh, country :TW, toLanguageTag : zh-TW, toString : zh_TW
language : ja, country :JP, toLanguageTag : ja-JP, toString : ja_JP
language : ru, country :RU, toLanguageTag : ru-RU, toString : ru_RU
language : id, country :ID, toLanguageTag : id-ID, toString : id_ID
language : th, country :TH, toLanguageTag : th-TH, toString : th_TH
language : ar, country :SA, toLanguageTag : ar-SA, toString : ar_SA

보면 위에서 old code로 설정되어 있는 id language의 경우 11에서는 "in"으로 호출되고 toString도 "in_ID"로 반환된다. (languageTag는 아님)

기존의 경우는 이렇게 내부적으로 old ISO code를 사용하고 반환하였는데 JDK 17부터는 설정에 따라 old ISO code 또는 new ISO code를 반환하도록 변경되었고 기본값이 new ISO code가 되었다.

만약 JDK 17에서 이전 값을 유지하고 싶은 경우 System property를 선언해야 한다.

다음과 같이 java 옵션으로 선언하거나

java -Djava.locale.useOldISOCodes=true ...

또는 java code에서 선언하면 된다.

System.setProperty("java.locale.useOldISOCodes", "true");

대부분의 경우 해당 변경점이 별 문제가 없지만 다국어를 제공하는 웹서비스를 위 old ISO code가 다른 언어에 대해 제공하고 있었다면 JDK 17 이후 설정에 대해 확인이 필요하다.

참고 문서 : https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html

반응형
profile

파란하늘의 지식창고

@Bluesky_

내용이 유익했다면 광고 배너를 클릭 해주세요