Java 현지화 – 메시지 형식화

1. 소개

이 튜토리얼에서는 Locale을 기반 으로 메시지현지화하고 형식을 지정 하는 방법을 고려합니다 .

Java의 MessageFormat 과 타사 라이브러리 인 ICU를 모두 사용합니다 .

2. 현지화 사용 사례

애플리케이션이 전 세계의 광범위한 사용자를 확보 할 때 자연스럽게 사용자의 선호도에 따라 다른 메시지표시 하고 싶을 수 있습니다 .

첫 번째이자 가장 중요한 측면은 사용자가 말하는 언어입니다. 기타에는 통화, 숫자 및 날짜 형식이 포함될 수 있습니다. 마지막으로 문화적 선호도는 중요합니다. 한 국가의 사용자에게 허용되는 것은 다른 국가에서는 허용되지 않을 수 있습니다.

이메일 클라이언트가 있고 새 메시지가 도착할 때 알림을 표시하려고한다고 가정합니다.

이러한 메시지의 간단한 예는 다음과 같습니다.

Alice has sent you a message.

영어를 사용하는 사용자에게는 괜찮지 만 영어를 사용하지 않는 사용자는 그렇게 행복하지 않을 수 있습니다. 예를 들어 프랑스어를 사용하는 사용자는 다음 메시지를 선호합니다.

Alice vous a envoyé un message. 

폴란드 사람들은 이것을보고 기뻐할 것입니다.

Alice wysłała ci wiadomość. 

Alice가 하나의 메시지 만 보내는 것이 아니라 몇 개의 메시지를 보내는 경우에도 올바른 형식의 알림을 받고 싶다면 어떻게해야합니까?

다음과 같이 다양한 부분을 단일 문자열로 연결하여 문제를 해결하고 싶을 수 있습니다.

String message = "Alice has sent " + quantity + " messages"; 

Alice뿐만 아니라 Bob도 메시지를 보낼 수있는 경우 알림이 필요할 때 상황을 쉽게 제어 할 수 없습니다.

Bob has sent two messages. Bob a envoyé deux messages. Bob wysłał dwie wiadomości.

폴란드어 ( wysłała vs wysłał ) 언어 의 경우 동사가 어떻게 변하는 지 주목하십시오 . 이는 메시지를 지역화 하는 데 진부한 문자열 연결이 거의 허용되지 않는다는 사실을 보여줍니다 .

보시다시피 두 가지 유형의 문제가 있습니다. 하나는 번역과 관련된 문제 이고 다른 하나는 형식과 관련된 문제 입니다. 다음 섹션에서 이에 대해 설명하겠습니다.

3. 메시지 현지화

우리는 응용 프로그램을 사용자의 편의에 맞게 조정하는 프로세스로 응용 프로그램지역화 또는 l10n을 정의 할 수 있습니다 . 때로는 내부화 또는 i18n 이라는 용어 도 사용됩니다.

응용 프로그램을 현지화하기 위해 먼저 하드 코딩 된 모든 메시지를 리소스 폴더 로 이동하여 제거하겠습니다 .

각 파일은 해당 언어로 된 메시지와 함께 키-값 쌍을 포함해야합니다. 예를 들어, messages_en.properties 파일 에는 다음 쌍이 포함되어야합니다.

label=Alice has sent you a message.

messages_pl.properties 는 다음 쌍을 포함해야합니다.

label=Alice wysłała ci wiadomość.

마찬가지로 다른 파일은 키 레이블에 적절한 값을 할당합니다 . 이제 영어 버전의 알림을 가져 오기 위해 ResourceBundle 을 사용할 수 있습니다 .

ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.UK); String message = bundle.getString("label");

변수의 값 메시지가 될 것 "앨리스는 당신에게 메시지를 보냈습니다."

Java의 Locale 클래스에는 자주 사용되는 언어 및 국가에 대한 바로 가기가 포함되어 있습니다.

폴란드어의 경우 다음과 같이 작성할 수 있습니다.

ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.forLanguageTag("pl-PL")); String message = bundle.getString("label");

로케일을 제공하지 않으면 시스템은 기본 로케일을 사용합니다. 이 문제에 대한 자세한 내용은 "Java 8의 국제화 및 지역화"기사에서 확인할 수 있습니다. 그런 다음 사용 가능한 번역 중에서 현재 활성 로케일과 가장 유사한 번역을 선택합니다.

리소스 파일에 메시지를 배치하는 것은 응용 프로그램을보다 사용자 친화적으로 만드는 좋은 단계입니다. 다음과 같은 이유로 전체 애플리케이션을 더 쉽게 번역 할 수 있습니다.

  1. 번역자는 메시지를 찾기 위해 응용 프로그램을 살펴볼 필요가 없습니다.
  2. a translator can see the whole phrase which helps to grasp the context and hence facilitates a better translation
  3. we don't have to recompile the whole application when a translation for a new language is ready

4. Message Format

Even though we have moved the messages from the code into a separate location, they still contain some hardcoded information. It would be nice to be able to customize the names and numbers in the messages in such a way that they remain grammatically correct.

We may define the formatting as a process of rendering the string template by substituting the placeholders by their values.

In the following sections, we'll consider two solutions that allow us to format the messages.

4.1. Java's MessageFormat

In order to format strings, Java defines numerous format methods in java.lang.String. But, we can get even more support via java.text.format.MessageFormat.

To illustrate, let's create a pattern and feed it to a MessageFormat instance:

String pattern = "On {0, date}, {1} sent you " + "{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}."; MessageFormat formatter = new MessageFormat(pattern, Locale.UK); 

The pattern string has slots for three placeholders.

If we supply each value:

String message = formatter.format(new Object[] {date, "Alice", 2});

Then MessageFormat will fill in the template and render our message:

On 27-Apr-2019, Alice sent you two messages.

4.2. MessageFormat Syntax

From the example above, we see that the message pattern:

pattern = "On {...}, {..} sent you {...}.";

contains placeholders which are the curly brackets {…} with a required argument index and two optional arguments, type and style:

{index} {index, type} {index, type, style}

The placeholder's index corresponds to the position of an element from the array of objects that we want to insert.

When present, the type and style may take the following values:

type style
number integer, currency, percent, custom format
date short, medium, long, full, custom format
time short, medium, long, full, custom format
choice custom format

The names of the types and styles largely speak for themselves, but we can consult the official documentation for more details.

Let's take a closer look, though, at custom format.

In the example above, we used the following format expression:

{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}

In general, the choice style has the form of options separated by the vertical bar (or pipe):

Inside the options, the match value ki and the string vi are separated by # except for the last option. Notice that we may nest other patterns into the string vi as we did it for the last option:

{2, choice, ...|2<{2, number, integer} messages}

The choice type is a numeric-based one, so there is a natural ordering for the match values ki that split a numeric line into intervals:

If we give a value k that belongs to the interval [ki, ki+1) (the left end is included, the right one is excluded), then value vi is selected.

Let's consider in more details the ranges of the chosen style. To this end, we take this pattern:

pattern = "You''ve got " + "{0, choice, 0#no messages|1#a message|2#two messages|2<{0, number, integer} messages}.";

and pass various values for its unique placeholder:

n message
-1, 0, 0.5 You've got no messages.
1, 1.5 You've got a message.
2 You've got two messages.
2.5 You've got 2 messages.
5 You've got 5 messages.

4.3. Making Things Better

So, we're now formatting our messages. But, the message itself remains hardcoded.

From the previous section, we know that we should extract the strings patterns to the resources. To separate our concerns, let's create another bunch of resource files called formats:

In those, we'll create a key called label with language-specific content.

For example, in the English version, we'll put the following string:

label=On {0, date, full} {1} has sent you + {2, choice, 0#nothing|1#a message|2#two messages|2<{2,number,integer} messages}.

We should slightly modify the French version because of the zero message case:

label={0, date, short}, {1}0< vous a envoyé + {2, choice, 0#aucun message|1#un message|2#deux messages|2<{2,number,integer} messages}.

And we'd need to do similar modifications as well in the Polish and Italian versions.

In fact, the Polish version exhibits yet another problem. According to the grammar of the Polish language (and many others), the verb has to agree in gender with the subject. We could resolve this problem by using the choice type, but let's consider another solution.

4.4. ICU's MessageFormat

Let's use the International Components for Unicode (ICU) library. We have already mentioned it in our Convert a String to Title Case tutorial. It's a mature and widely-used solution that allows us to customize the application for various languages.

Here, we're not going to explore it in full details. We'll just limit ourselves to what our toy application needs. For the most comprehensive and updated information, we should check the ICU's official site.

At the time of writing, the latest version of ICU for Java (ICU4J) is 64.2. As usual, in order to start using it, we should add it as a dependency to our project:

 com.ibm.icu icu4j 64.2 

Suppose that we want to have a properly formed notification in various languages and for different numbers of messages:

N English Polish
0 Alice has sent you no messages.

Bob has sent you no messages.

Alice nie wysłała ci żadnej wiadomości.

Bob nie wysłał ci żadnej wiadomości.

1 Alice has sent you a message.

Bob has sent you a message.

Alice wysłała ci wiadomość.

Bob wysłał ci wiadomość.

> 1 Alice has sent you N messages.

Bob has sent you N messages.

Alice wysłała ci N wiadomości.

Bob wysłał ci N wiadomości.

우선 로케일 별 리소스 파일에 패턴을 만들어야합니다.

format.properties 파일을 재사용 하고 다음 내용 이 포함 된 키 label-icu 를 추가해 보겠습니다 .

label-icu={0} has sent you + {2, plural, =0 {no messages} =1 {a message} + other {{2, number, integer} messages}}.

여기에는 3 요소 배열을 전달하여 제공하는 3 개의 자리 표시자가 포함되어 있습니다.

Object[] data = new Object[] { "Alice", "female", 0 }

영어 버전에서는 성별 값 자리 표시자가 쓸모가 없지만 폴란드 버전에서는 다음과 같이 표시됩니다.

label-icu={0} {2, plural, =0 {nie} other {}} + {1, select, male {wysłał} female {wysłała} other {wysłało}} + ci {2, plural, =0 {żadnych wiadomości} =1 {wiadomość} + other {{2, number, integer} wiadomości}}.

wysłał / wysłała / wysłało 를 구별하기 위해 사용합니다 .

5. 결론

이 튜토리얼에서는 애플리케이션 사용자에게 보여줄 메시지를 현지화하고 형식을 지정하는 방법을 고려했습니다.

항상 그렇듯이이 자습서의 코드 조각은 GitHub 저장소에 있습니다.