상세 컨텐츠

본문 제목

JSTL 기초, Part 3: 보여지는 것도 중요하다! (한글)

프로그래밍/JAVA

by 라제폰 2011. 9. 2. 11:10

본문

이전 기술자료에서는, JSTL과 이것의 Expression Language(EL)를 설명했으며, core 라이브러리에 의해 정의된 커스텀 태그에 대해서도 설명했다. 특히, Expression Language (한글)에서는 EL이 JSP 애플리케이션 내에서 데이터에 액세스 하고 데이터를 조작하며 그 데이터를 JSTL 커스텀 태그의 동적 속성값들로 사용할 수 있게 만들 수 있는 단순한 언어를 제공한다는 것을 설명했다. "core 분석"에서는, 범위가 지정된 변수들을 관리하고, EL 값을 디스플레이 하고, 반복적이며 조건적인 내용을 구현하고, URL과 인터랙팅 하는 커스텀 태그를 포함하고 있는 core 라이브러리를 설명했다.

우리가 논할 그 다음의 JSTL은 fmt 라이브러리이다. fmt 라이브러리의 커스텀 태그들은 리소스 번들과, 숫자와 날짜의 디스플레이와 파싱을 통해 텍스트 콘텐트의 로컬화를 지원한다. 이러한 태그들은 java.utiljava.text 패키지로 되어있는 자바의 국제화 API를 활용하기 때문에, ResourceBundle, Locale, MessageFormat, DateFormat 같은 클래스에 익숙하다면 fmt 라이브러리를 더욱 잘 활용할 수 있을 것이다. 그렇지 않다 하더라도, fmt 라이브러리의 태그는 로컬화 기능들을 JSP 애플리케이션으로 통합하기 쉽도록 국제화 API를 캡슐화 하고 있다.

로컬화

자바 언어의 국제화 API에는 데이터가 로컬화 되는 방식에 영향을 미치는 두 가지 주요한 요소들이 있다. 하나는 사용자의 로케일(locale)이고, 다른 하나는 사용자의 시간대(time zone)이다. 로케일은 날짜, 숫자, 통화 포맷팅 등 특정 지역 또는 문화에 대한 언어적 규약이다. 로케일은 언제나 연계된 언어들을 갖고 있는데, 대부분의 경우 이것은 여러 로케일에 의해서 공유되는 일종의 방언(dialect)과 같이 선언된다. 예를 들어, 미국 영어, 영국 영어, 호주 영어, 캐나다 영어에 대한 다른 로케일이 있고, 불어의 경우에도 프랑스 식, 벨기에 식, 스위스 식, 캐나나 식의 로케일이 존재한다.

이 기사의 다른 시리즈

Part 1, "Expression language" (2003년 2월)

Part 2, "core 분석" (2003년 3월)

Part 4, "SQL과 XML 콘텐트에 액세스 하기" (2007년 4월)

일부 로케일은 매우 광범위한 지리적 영역까지 확장되기 때문에 시간대(Time zone)는 데이터 로컬화의 두 번째 요소가 된다. Australian English 같은 확장형 로케일에 대한 시간대(time-of-day) 정보를 디스플레이 할 때, 사용자의 시간대에 대한 데이터를 커스터마이징 하는 것은 올바른 포맷팅 만큼 중요하다.

여기서 한 가지 의문이 생긴다. 애플리케이션은 어떻게 사용자의 로케일과 시간대를 결정하는가? 자바 애플리케이션의 경우, JVM은 로컬 OS와 인터랙팅 하여 기본 로케일과 시간대를 설정할 수 있다. 이러한 방식은 데스크탑 애플리케이션에는 알맞지만, 지구 반 바퀴나 떨어진 곳에 있는 서버로부터 요청을 핸들하는 서버 측 자바 애플리케이션에는 맞지 않다.

다행히도, HTTP 프로토콜을 사용하여 Accept-Language 요청 헤더로 브라우저에서 서버로 지역 정보를 보낼 수 있다. 많은 웹 브라우저는 사용자들이 선호하는 언어를 선택할 수 있도록 하고 있다. (그림 1) 일반적으로, 선호하는 로케일을 설정할 수 없는 브라우저는 OS에게 Accept-Language 헤더에서 보낼 값을 결정하도록 한다. 서블릿은 javax.servlet.ServletRequest 클래스의 getLocale()getLocales() 메소드를 통해 HTTP 프로토콜의 기능을 활용한다. JSTL fmt 라이브러리의 커스텀 태그들은 이러한 메소드들을 활용하여 사용자 로케일을 자동으로 결정하고 이에 따라 결과값을 조정한다.


그림 1. 브라우저용 언어 선호도를 설정하여 로케일 선택하기
브라우저용 언어 선호도를 설정

안타깝게도, 브라우저에서 서버로 사용자의 시간대를 전송하는 것에 대한 표준 HTTP 요청 헤더가 없으며, 결국에는 사용자들이 사용자 지정 시간대를 결정하고 추적할 수 있는 고유의 메커니즘을 구현해야 한다. 예를 들어, 본 시리즈 Part 2 "core 분석"에 소개되었던 Weblog 애플리케이션에는 쿠키에 사용자의 시간대(time zone) 정보를 저장하고 있는 폼이 포함되어 있다.


fmt 라이브러리

JSTL fmt 라이브러리의 커스텀 태그들은 네 가지 그룹으로 구분된다. 첫 번째는 다른 태그들이 실행되는 로컬화 콘텍스트를 설정할 수 있는 태그이다. 다시 말해서, 이 태그 그룹을 사용해서 페이지 작성자는 다른 fmt 태그들이 데이터를 포맷할 때 사용할 수 있는 로케일과 시간대를 설정할 수 있다. 두 번째와 세 번째 태그 세트는 날짜와 숫자의 포맷팅과 파싱을 지원한다. 마지막 태그 그룹은 텍스트 메시지의 로컬화를 담당하고 있다.

이제부터는 이 네 가지 태그 세트를 하나씩 설명하도록 하겠다.

로컬화 콘텍스트 태그

이미 언급했던 것처럼, 데이터를 포맷팅할 때 JSTL 태그에 의해 사용되는 로케일은 일반적으로 각 HTTP 요청의 일부로서 사용자의 브라우저에 의해 보내진 Accept-Language 헤더를 검사하여 결정된다. 어떤 헤더도 존재하지 않으면, JSTL은 기본 로케일을 설정할 수 있는 JSP 설정 변수들을 제공한다. 이러한 설정 변수들이 설정되지 않았다면, JVM의 기본 로케일이 사용되는데, 이것은 JSP 컨테이너가 실행되는 OS에서 얻을 수 있다.

fmt 라이브러리는 고유의 커스텀 태그 <fmt:setLocale>을 제공한다. 다음 코드에서 보듯, <fmt:setLocale> 액션은 세 개의 애트리뷰트를 지원한다.

<fmt:setLocale value="expression"
    scope="scope" variant="expression"/>

단 한 개의 애트리뷰트, 즉 value 애트리뷰트가 필요하다. 이 애트리뷰트의 값은 로케일의 이름을 짓는 스트링 또는 java.util.Locale 클래스의 인스턴스가 되어야 한다. 로케일 이름은 소문자 두 개로 된 ISO 국가 코드에서 취해지며, 선택적으로 뒤에 언더스코어(underscore) 또는 하이픈이 붙고 두 문자의 대문자 ISO 언어 코드가 붙는다.

예를 들어, en은 English의 언어 코드이고, US는 United States의 국가 코드이므로 en_US (또는 en-US)는 American English에 대한 로케일 이름이 된다. 마찬가지로, fr은 French의 언어 코드이고, CA는 Canada용 국가 코드이므로, fr_CA (또는 fr_CA)는 Canadian French의 로케일 이름이다. (참고자료) 물론, 국가 코드는 선택 사항이기 때문에, enfr은 유효 로케일 이름이고, 상응하는 언어의 특정 방언들을 구별할 수 없는 애플리케이션에 알맞다.

<fmt:setLocale>scope 애트리뷰트는 로케일의 범위를 지정하는데 사용된다. page 범위는 이 설정이 현재 페이지에만 적용될 수 있다는 것을 나타내고, request 범위는 이것을 요청 동안 액세스 된 모든 JSP 페이지에 적용된다. scope 애트리뷰트가 session으로 설정되면, 지정된 로케일은 사용자 세션 과정 동안 액세스 된 모든 JSP 페이지에 사용된다. application의 값은 이 로케일이 웹 애플리케이션의 JSP 페이지와 그 애플리케이션의 모든 사용자에 대한 요청에 적용된다는 것을 나타낸다.

variant 애트리뷰트는 특정 웹 브라우저 플랫폼이나 벤더들로 로케일을 커스터마이징 할 수 있다. 예를 들어, MACWIN은 각각 Apple Macintosh와 Microsoft Windows 플랫폼에 대한 이름들이다.

다음 코드는 사용자 세션에 대한 로케일을 설정하는데 있어서 <fmt:setLocale> 태그가 사용되는 방식을 보여준다.

<fmt:setLocale value="fr_CA" scope="session"/>

JSP 컨테이너가 이 해당 JSP 부분을 처리하면, 사용자의 브라우저에서 설정된 언어 선호는 무시된다.

<fmt:setLocale> 같은 <fmt:setTimeZone> 액션은 다른 fmt 커스텀 태그들에 의해 사용될 기본 시간대(time zone) 값을 설정하는데 사용된다. 신택스는 다음과 같다.

<fmt:setTimeZone value="expression"
    var="name" scope="scope"/>

<fmt:setLocale>처럼, value 애트리뷰트만 필요하다. 하지만 이 경우에는 시간대의 이름 또는 java.util.TimeZone 클래스의 인스턴스가 되어야 한다.

안타깝게도, 시간대(time zone)의 네이밍에 대한 표준이 없다. <fmt:setTimezone> 태그의 value 애트리뷰트에 사용하는 시간대 이름들은 자바 플랫폼을 따른다. java.util.TimeZone 클래스의 getAvailableIDs() 정적 메소드를 호출함으로써, 유효한 시간대 이름들을 가져올 수 있다. 예를 들면, US/Eastern, GMT+8, Pacific/Guam과 같다.

<fmt:setLocale>의 경우와 마찬가지로, scope 애트리뷰트를 사용하여 시간대(time zone) 설정의 범위를 나타낼 수 있다. 아래 코드는 <fmt:setTimeZone>을 사용하여 개인 사용자의 세션에 적용되는 시간대를 지정하는 모습이다.

<fmt:setTimeZone value="Australia/Brisbane" scope="session"/>

<fmt:setTimeZone> 액션을 사용하여 범위가 지정된 변수에 TimeZone 인스턴스의 값을 지정할 수 있다. 이 경우, var 애트리뷰트를 사용하여 범위가 지정된 변수의 이름을 지을 수 있고, scope 애트리뷰트는 변수의 범위를 지정한다. (이러한 두 개의 애트리뷰트들은 <c:set><c:if> 액션에 사용된다.) 이와 같은 방식으로 <fmt:setTimeZone> 액션을 사용할 때의 유일한 부작용은 지정된 변수를 설정한다는 것이다. var 애트리뷰트가 설정되면, 다른 JSTL 태그에 의해 사용된 시간대와 관련해서는 JSP 환경에는 어떤 변화도 일어나지 않는다.

이 그룹의 마지막 태그는 <fmt:timeZone> 액션이다.

<fmt:timeZone value="expression">
  body content
</fmt:timeZone>

<fmt:setTimeZone>처럼, 이 태그를 사용하여 다른 JSTL 태그에 의해 사용되도록 시간대(time zone)를 지정한다. <fmt:timeZone> 액션의 범위는 바디 콘텐트로 제한된다. <fmt:timeZone> 태그의 바디 내에서, 태그의 value 애트리뷰트에 의해 지정된 시간대는 JSP 환경의 시간대 설정을 오버라이드(Override) 한다.

<fmt:setTimeZone>의 경우 처럼, <fmt:timeZone> 태그의 value 애트리뷰트는 시간대의 이름이거나 java.util.TimeZone의 인스턴스이다. <fmt:timeZone>을 사용하는 방법에 대한 예제는 Listing 1을 참조하라.

데이터 태그

fmt 라이브러리에는 날짜와 시간과 상호 작동하는 두 개의 태그들이 있다. <fmt:formatDate><fmt:parseDate>이다. 이름에서 시사하듯, <fmt:formatDate>는 날짜와 시간을 포맷 및 디스플레이 하는데 사용되고(데이터 아웃풋(Output)), <fmt:parseDate>는 날짜와 시간 값을 파싱하는데 사용된다. (데이터 인풋(Input)).

<fmt:formatDate>용 신택스는 다음과 같다.

<fmt:formatDate value="expression" 
    timeZone="expression"
    type="field" dateStyle="style" 
    timeStyle="style"
    pattern="expression"
    var="name" scope="scope"/>

이 상황에서 오직 value 애트리뷰트만이 필요하며, 이것의 값은 java.util.Date 클래스의 인스턴스여야 하며, 포맷팅 및 디스플레이 될 날짜와 시간 데이터를 지정한다.

선택적인 timeZone 애트리뷰트는 날짜와 시간이 디스플레이 될 시간대를 가리킨다. 어떤 timeZone 애트리뷰트도 명확하게 지정되지 않으면, 주변의 <fmt:timeZone> 태그에 의해 지정된 시간대가 사용된다. <fmt:formatDate> 액션이 <fmt:timeZone> 태그의 바디에 인클로즈(enclose) 되지 않으면, <fmt:setTimeZone> 액션에 의해 설정된 시간대(time zone)가 사용된다. 관련된 <fmt:setTimeZone> 액션이 없다면, JVM의 기본 시간대 (로컬 OS용으로 지정된 시간대)가 사용된다.

type 애트리뷰트는 지정된 Date 인스턴스의 어떤 필드가 디스플레이 되어야 하는지, 그리고 time, date, 혹은 둘 모두 중 어떤 것이 되어야 하는지를 나타낸다. 이 애트리뷰트에 대한 기본 값은 date이기 때문에, 어떤 type 애트리뷰트도 존재하지 않으면, <fmt:formatDate> 태그는 태그의 value 애트리뷰트로 지정된, Date 인스턴스와 제휴된 날짜 정보만 디스플레이 할 것이다.

dateStyletimeStyle 애트리뷰트는 각각 날짜와 시간 정보가 포맷팅 되는 방식을 나타내며, 해당 유효 스타일은 default, short, medium, long, full이다. 기본 값은 당연히 default이며, 지역별로 정의된 스타일을 사용해야 한다는 의미이다. 다른 네 개의 스타일 값에 대한 의미는 java.text.DateFormat 클래스에 의해 정의된다.

기본 스타일에 의존하는 대신, pattern 애트리뷰트를 사용하여 커스텀 스타일을 지정할 수 있다. 규약이 존재한다면, 패턴 애트리뷰트의 값은 java.text.SimpleDateFormat 클래스의 규약을 따르는 패턴 스트링이어야 한다. 이러한 패턴들은 상응하는 날짜와 시간 필드와 관련하여 이 패턴 내에서 정해진 문자들로 대체된다. 예를 들어, MM/dd/yyyy 패턴은 두 자리 숫자로 된 달과 날짜 값과 네 자리 수로 된 연도 값이 디스플레이 되어야 하며, 포워드 슬래시로 구분되어야 한다는 것을 나타내고 있다.

var 애트리뷰트가 지정되면, 포맷된 데이터를 포함하고 있는 String 값은 네임드(named) 변수로 할당된다. 그렇지 않으면, <fmt:formatDate> 태그가 포맷팅 결과를 작성할 것이다. var 애트리뷰트가 있다면, scope 애트리뷰트는 결과 변수의 범위를 지정한다.

Listing 1(본 시리즈 Part 2의 Listing 8을 확장한 것임)에는 <fmt:formatDate> 태그의 두 가지 사용법이 나타나 있다. 첫 번째 사용법은 <fmt:formatDate>는 첫 번째 weblog 엔트리의 생성 타임스탬프에서 날짜 부분만 디스플레이 한다. 게다가, full의 값은 dateStyle 애트리뷰트용으로 지정되기 때문에 모든 날짜 필드들은 지역 고유의 포맷으로 디스플레이 될 것이다.


Listing 1. <fmt:formatDate> 태그를 사용하여 날짜와 시간 값 디스플레이 하기
				
<table>
<fmt:timeZone value="US/Eastern">
<c:forEach items="${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
        <tr><td align="left" class="blogDate">
          <fmt:formatDate value=
              "${blogEntry.created}" dateStyle="full"/>
        </td></tr>
      </c:if>
      <tr><td align="left" class="blogTitle">
        <c:out value="${blogEntry.title}" escapeXml="false"/>
      </td></tr>
    <tr><td align="left" class="blogText">
      <c:out value="${blogEntry.text}" escapeXml="false"/>
      <font class="blogPosted">
        [Posted <fmt:formatDate value="${blogEntry.created}"
                                pattern="h:mm a zz"/>]
      </font>
    </td></tr>
  </c:forEach>
  </fmt:timeZone>
</table>

<c:forEach> 루프의 바디 내에서, 두 번째 <fmt:formatDate> 액션은 각 엔트리의 생성 날짜 중 시간 부분만 디스플레이 할 때 사용된다. 이 경우, pattern 애트리뷰트는 시간 값의 포맷팅을 관리하면서, 가능할 경우 한 자리 숫자의 시간 디스플레이, 12-Hour 시계, 축약된 시간대의 결과를 지정한다. 결과는 그림 2에 나타나 있다.


그림 2. Listing 1에서 en_US의 결과
Listing 1에서 en_US의 결과

보다 정확히 말하자면, 그림 2에 나타난 결과는 사용자의 브라우저 설정이 English에 대한 선호도를 나타낼 때 나오는 결과이다. <fmt:formatDate>는 사용자의 로케일에 민감하기 때문에 브라우저 선호도에 있어 변경 사항이 생기면 다른 콘텐트가 생성될 것이다. 예를 들어, French 로케일이 주어졌다면 결과는 그림 3과 같을 것이다.


그림 3. Listing 1의 fr_CA의 결과
Listing 1의 fr_CA의 결과

<fmt:formatDate>java.util.Date 인스턴스를 로컬화 된 문자-스트링으로 나타내는 반면, <fmt:parseDate> 액션은 반대의 작동을 수행한다. 날짜/시간을 나타내는 문자 스트링이 있다면, 이것은 상응하는 Date 객체를 만들어 낸다. <fmt:parseDate> 액션에는 두 가지 폼이 있다.

<fmt:parseDate value="expression"
    type="field" dateStyle="style" timeStyle="style"
    pattern="expression"
    timeZone="expression" parseLocale="expression"
    var="name" scope="scope"/>

<fmt:parseDate
    type="field" dateStyle="style" timeStyle="style"
    pattern="expression"
    timeZone="expression" parseLocale="expression"
    var="name" scope="scope">
  body content
</fmt:parseDate>

첫 번째 폼의 경우, 오직 value 애트리뷰트만 필요하고, 이것의 값은 날짜, 시간 또는 이 두 가지를 결합한 문자 스트링이 되어야 한다. 두 번째 폼의 경우, 필요한 애트리뷰트가 없고, 파싱 될 값을 나타내는 문자 스트링은 <fmt:parseDate> 태그의 필수 바디 콘텐트로서 지정된다.

type, dateStyle, timeStyle, pattern, timeZone 애트리뷰트는 <fmt:parseDate>에 대해서도 <fmt:formatDate>에 대해 하는 것과 똑 같은 일을 수행한다. 단, 디스플레이 보다 날짜 값의 파싱을 관리한다는 점은 다르다. parseLocale 애트리뷰트는 태그의 값이 파싱 될 대상 로케일을 지정하는데 사용되고, 로케일의 이름 또는 Locale 클래스의 인스턴스가 되어야 한다.

varscope 애트리뷰트는 <fmt:parseDate>의 결과로서, Date 객체가 할당될 범위가 지정된 변수를 지정하는데 사용된다. var 애트리뷰트가 존재하지 않으면, 결과는 Date 클래스의 toString() 메소드를 사용하여 JSP 페이지에 작성된다. Listing 2는 <fmt:parseDate> 액션의 예제이다.


Listing 2. <fmt:parseDate> 태그를 사용하여 날짜와 시간 파싱하기
				
<c:set var="usDateString">4/1/03 7:03 PM</c:set>
<fmt:parseDate value="${usDateString}" parseLocale="en_US"
               type="both" dateStyle="short" timeStyle="short"
	       var="usDate"/>

<c:set var="gbDateString">4/1/03 19:03</c:set>
<fmt:parseDate value="${gbDateString}" parseLocale="en_GB"
               type="both" dateStyle="short" timeStyle="short"
	       var="gbDate"/>

<ul>
<li> Parsing <c:out value="${usDateString}"/> against the 
U.S. English
     locale yields a date of <c:out value="${usDate}"/>.</li>

<li> Parsing <c:out value="${gbDateString}"/> against the 
British English
     locale yields a date of <c:out value="${gbDate}"/>.</li>
</ul>

Listing 2의 결과는 그림 4와 같다.


그림 4. Listing 2의 결과
Listing 2의 결과

<fmt:parseDate>에 의해 수행된 파싱이 모두가 그렇게 관대한 것은 아니다. Listing 2에서 보았듯이, 파싱 될 값은 지정된 지역 고유 스타일이나 패턴을 엄격히 준수해야 한다. 물론, 이것은 오히려 제한적인 반면에 데이터의 파싱은 표현 레이어에 잘 맞는 태스크가 아니다. 실행 코드의 경우, 문자로된 입력의 유효성 검사와 변형은 JSP 커스텀 태그 보다는 서블릿과 같은 백엔드 코드에 의해 더욱 잘 핸들된다.

Number 태그

<fmt:formatDate><fmt:parseDate> 태그들이 날짜를 포맷팅 및 파싱하는데 사용되는 것처럼, <fmt:formatNumber><fmt:parseNumber> 태그들은 숫자 데이터를 포맷팅 및 파싱한다.

<fmt:formatNumber> 태그는 특정 로케일 방식으로 통화나 퍼센트를 포함한 숫자 데이터를 디스플레이 하는데 사용된다. <fmt:formatNumber> 액션은 정수와 소수점을 구분하는데 마침표나 콤마를 사용할 것인지의 여부를 로케일에서 결정한다. 다음은 신택스이다.

<fmt:formatNumber value="expression"
    type="type" pattern="expression"
    currencyCode="expression" currencySymbol="expression"
    maxIntegerDigits="expression" minIntegerDigits="expression"
    maxFractionDigits="expression" minFractionDigits="expression"
    groupingUsed="expression"
    var="name" scope="scope"/>

<fmt:formatDate>의 경우와 마찬가지로, value 애트리뷰트만 필요하다. 이것은 포맷팅 될 숫자 값을 지정하는데 사용된다. varscope 애트리뷰트는 <fmt:formatNumber>에 대해서 했던 것과 똑 같은 액션을 <fmt:formatDate> 액션에 대해서도 수행한다.

type 애트리뷰트의 값은 number, currency, percentage 중 하나이고, 어떤 유형의 숫자 값이 포맷팅 되는지를 가리킨다. 이 애트리뷰트에 대한 기본 값은 number이다. 이 pattern 애트리뷰트는 type 애트리뷰트 보다 우선하며, java.text.DecimalFormat 클래스의 패턴 규약을 따르는 숫자 값의 정밀한 포맷팅을 가능케 한다.

type 애트리뷰트가 currency의 값을 갖고 있을 경우, currencyCode 애트리뷰트는 디스플레이 될 숫자 값에 대해 통화(currency)를 지정하는데 사용된다. 언어와 국가 코드와 마찬가지로, 통화 코드는 ISO 표준으로 관리된다. (참고자료) 이 코드는 포맷팅 된 값의 일부로 디스플레이 할 통화 심볼을 결정하는데 사용된다.

currencyCode 대신 currencySymbol 애트리뷰트를 사용하여 통화 심볼을 명확하게 지정할 수 있다. JDK 1.4와 java.util.Currency의 도입 이후, <fmt:formatNumber> 액션의 currencyCode 애트리뷰트는 currencySymbol 애트리뷰트 보다 우선한다. 하지만 이전 버전의 JDK의 경우, currencySymbol 애트리뷰트가 우선한다.

maxIntegerDigits, minIntegerDigits, maxFractionDigits, minFractionDigits 애트리뷰트는 소수점 앞 뒤로 디스플레이 될 숫자를 관리하는데 사용되며,해당 애트리뷰트에는 정수 값이 필요하다.

groupingUsed 애트리뷰트는 Boolean 값을 취하고 소수점 앞에 있는 숫자들의 그룹핑을 관리한다. 예를 들어, English 로케일에서, 많은 숫자들은 3자리 수 마다 콤마로 분리하고 있다. 다른 로케일은 마침표나 공백으로 구분하고 있으며, 해당 애트리뷰트의 기본 값은 true이다.

Listing 3은 단순한 통화 예제로서, Listing 1을 확장한 것이다. 이 경우, currencyCode 또는 currencySymbol 애트리뷰트가 지정되지 않았다. 대신 로케일 설정에서 통화가 결정된다.


Listing 3. <fmt:formatNumber> 태그를 사용하여 통화 값 디스플레이 하기
				
<table>
<fmt:timeZone value="US/Eastern">
<c:forEach items="${entryList}" var="blogEntry" 
varStatus="status">
<c:if test="${status.first}">
        <tr><td align="left" class="blogDate">
          <fmt:formatDate value=
              "${blogEntry.created}" dateStyle="full"/>
        </td></tr>
      </c:if>
      <tr><td align="left" class="blogTitle">
        <c:out value="${blogEntry.title}" escapeXml="false"/>
      </td></tr>
    <tr><td align="left" class="blogText">
      <c:out value="${blogEntry.text}" escapeXml="false"/>
      <font class="blogPosted">
        [My <fmt:formatNumber value="0.02" type="currency"/>
         posted at <fmt:formatDate value="${blogEntry.created}"
                                   pattern="h:mm a zz"/>]
      </font>
    </td></tr>
  </c:forEach>
  </fmt:timeZone>
</table>

en_US 로케일의 출력은 그림 5와 같이 나타난다.


그림 5. Listing 3의 en_US 로케일 결과
Listing 3의 en_US 로케일 결과

fr_CA 로케일의 결과는 그림 6과 같이 나타난다.


그림 6. Listing 3의 fr_CA 로케일 결과
Listing 3의 fr_CA 로케일 결과

<fmt:parseNumber> 액션은 value 애트리뷰트나 바디 콘텐트를 통해서 제공된 숫자 값을 파싱하고, 그 결과를 java.lang.Number 클래스의 인스턴스로서 리턴한다. typepattern 애트리뷰트는 <fmt:parseNumber>에서 수행했던 일을 <fmt:formatNumber>에서도 수행한다. 마찬가지로, parseLocale, var, scope 애트리뷰트는 <fmt:parseNumber>에서 했던 것과 같은 일을 <fmt:parseDate>에 대해서도 수행한다.

<fmt:parseNumber value="expression"
    type="type" pattern="expression"
    parseLocale="expression"
    integerOnly="expression"
    var="name" scope="scope"/>

<fmt:parseNumber
    type="type" pattern="expression"
    parseLocale="expression"
    integerOnly="expression"
    var="name" scope="scope">
  body content
</fmt:parseNumber>

<fmt:parseDate>와 관련한 주석은 <fmt:parseNumber>에도 똑 같이 적용된다. 데이터 파싱은 프리젠테이션 레이어(Presentation Layer)에는 잘 맞지 않는다. 데이터의 파싱과 유효성 검사가 애플리케이션의 비즈니스 로직의 일부로 구현된다면 소프트웨어 관리는 단순화 될 것이다. 이와 같은 이유로, 실제로 실행되는 JSP 페이지에는 <fmt:parseDate><fmt:parseNumber>를 사용하지 않는 것이 좋다.

integerOnly 애트리뷰트만이 <fmt:parseNumber>에 유일한 것이다. 이 애트리뷰트는 Boolean 값을 사용하여 제공된 값의 정수 부분만 파싱 되는지의 여부를 나타낸다. 이 애트리뷰트의 값이 true이면, 파싱 되고 있는 문자 내의 소수점 뒤에 있는 숫자들은 무시되며, 해당 애트리뷰트의 기본 값은 false이다.

Message 태그

JSTL에서의 텍스트 로컬화는 <fmt:message> 태그를 사용하여 이루어진다. 이 태그는 지역 고유의 리소스 번들에서 텍스트 메시지들을 가져다가 이를 JSP 페이지에 디스플레이 한다. 이 액션은 java.text.MessageFormat 클래스에서 제공하는 기능을 활용하기 때문에, 매개변수화 된 값들은 이 같은 텍스트 메시지로 대체되어 로컬화 된 콘텐트를 동적으로 커스터마이징 한다.

지역 고유의 메시지에 대한 리소스 번들은 표준 네이밍 규약을 따르는 클래스나 프로퍼티 파일의 형태를 취하고, 베이스 네임(basename)은 로케일 이름과 결합된다. 예를 들어, com.taglib.weblog 패키지에 상응하는 하위 디렉토리의 weblog 애플리케이션의 classpath에 있는 Greeting.properties 파일을 생각해 보자. 프로퍼티 파일에서 나타난 리소스 번들을 English와 French로 로컬화 할 수 있다. 같은 디렉토리에 있는 두 개의 새로운 프로퍼티 파일을 지정하고, 알맞은 언어 코드를 붙여 이름을 짓는다. 특히, 이러한 두 개의 파일들에는 각각 Greeting_en.propertiesGreeting_fr.properties라는 이름이 붙는다. French의 캐나다 식 방언에 대한 추가 로컬화가 필요하다면, 이름에 해당 국가 코드를 포함하고 있는(Greeting_fr_CA.properties) 제 3의 프로퍼티 파일을 사용할 수 있다.

이러한 각 파일들은 같은 프로퍼티를 정의하지만, 그러한 프로퍼티에 대한 값은 해당 언어나 방언으로 커스터마이징 된다. 이러한 방식은 Listing 4와 5에 나타나있고, Greeting_en.propertiesGreeting_fr.properties 파일에 샘플 콘텐트를 제공하고 있다. 이 예제에서, 두 개의 로컬화 된 메시지가 정의된다. 이들은 com.taglib.weblog.Greeting.greetingcom.taglib.weblog.Greeting.return 키로 구분된다. 이러한 키들과 관련된 값들은 파일의 이름으로 구분된 언어에 대해 로컬화 되었다. com.taglib.weblog.Greeting.greeting 메시지에 대한 두 개의 값에 나타난 {0} 패턴은 매개변수화 된 값이 콘텐트 생성 동안 메시지로 삽입될 수 있도록 한다.


Listing 4. Greeting_en.properties의 내용
				
com.taglib.weblog.Greeting.greeting=Hello {0}, and welcome to the JSTL Blog.
com.taglib.weblog.Greeting.return=Return




Listing 5. Greeting_fr.properties의 내용
				
com.taglib.weblog.Greeting.greeting=Bonjour {0}, et bienvenue au JSTL Blog.
com.taglib.weblog.Greeting.return=Retournez

JSTL로 이와 같은 로컬화된 콘텐트를 디스플레이 하는 첫 번째 단계는 리소스 번들을 지정하는 것이다. fmt 라이브러리의 두 개의 커스텀 태그인, <fmt:setBundle><fmt:bundle>은 앞서 설명했던 <fmt:setTimeZone><fmt:timeZone> 태그와 비슷하다. <fmt:setBundle> 액션은 특정 범위 내에서 <fmt:message> 태그들에 의해 사용될 기본적인 리소스 번들을 설정하는 반면, <fmt:bundle>은 바디 콘텐트 안에 중첩된 모든 <fmt:message> 액션들에 의해 사용될 리소스 번들을 지정한다.

아래 코드는 <fmt:setBundle> 태그의 신택스이다. basename 애트리뷰트가 필요하고, 기본으로 설정될 리소스 번들을 구분하고 있다. basename 애트리뷰트용 값에는 로컬화 관련 접미사나 파일 이름 확장이 포함되어서는 안된다. Listing 4와 5에 제시된 예제 리소스 번들에 대한 베이스 네임은 com.taglib.weblog.Greeting이다.

<fmt:setBundle basename="expression"
    var="name" scope="scope"/>

선택사양인 scope 애트리뷰트는 기본 리소스 번들의 설정이 적용될 JSP 범위를 가리킨다. 이 애트리뷰트가 명확히 지정되지 않으면, page 범위로 간주된다.

var 애트리뷰트가 지정된다면, basename 애트리뷰트에 의해 구분된 리소스 번들은 이 애트리뷰트의 값에 의해 이름이 지정된 변수로 할당된다. 이 경우, scope 애트리뷰트는 변수의 범위를 지정한다. 어떤 기본 리소스 번들도 상응하는 JSP 범위에는 할당되지 않는다.

<fmt:bundle> 태그(아래 신택스 참조)를 사용하여 바디 콘텐트 범위 내에 기본 리소스 번들을 설정한다. <fmt:setBundle>과 마찬가지로, basename 애트리뷰트만 필요하다. prefix 애트리뷰트를 사용하여 중첩된 <fmt:message> 액션들의 key 값에 기본 접두사를 지정한다.

<fmt:bundle basename="expression" 
prefix="expression">
  body content
</fmt:bundle>

리소스 번들이 설정되면, 로컬화 된 메시지를 실제로 디스플레이 하는 것은 <fmt:message> 태그의 몫이다. 이 액션에는 중첩된 <fmt:param> 태그가 필요한지 여부에 따라, 두 개의 다른 신택스가 지원된다.

<fmt:message key="expression" bundle="expression"
    var="name" scope="scope"/>

<fmt:message key="expression" bundle="expression"
    var="name" scope="scope">
  <fmt:param value="expression"/>
  ...
</fmt:message>

<fmt:message>의 경우, key 애트리뷰트만 필요하다. key 애트리뷰트의 값은 리소스 번들에 정의된 메시지들 중 어떤 것이 디스플레이 될 것인지를 결정하는데 사용된다.

bundle 애트리뷰트를 사용하여 key 애트리뷰트에 의해 구분된 메시지를 검색하는 리소스 번들을 지정할 수 있다. 이 애트리뷰트의 값은 var 애트리뷰트가 지정될 때 <fmt:setBundle> 액션에 의해 할당된 것 처럼, 실제 리소스 번들이어야 한다. <fmt:bundle><fmt:setBundle>basename 애트리뷰트 값은 스트링 값은 <fmt:message>bundle 애트리뷰트로는 지원되지 않는다.

<fmt:message>var 애트리뷰트가 설정되면, JSP 페이지에 작성되기 보다는, 태그에 의해 생성된 텍스트 메시지가 네임드 변수에 할당된다. 일반적으로, scope 애트리뷰트가 var 애트리뷰트에 의해 이름이 정해진 변수의 범위를 지정한다.

<fmt:param> 태그를 사용하여 태그의 value 애트리뷰트를 통해 텍스트 메시지에 대한 매개변수화 된 값을 제공한다. 또는, 이 값은 <fmt:param> 태그의 바디 콘텐트로서 지정될 수 있는데, 이 경우, 애트리뷰트는 생략된다. <fmt:param> 태그를 사용하여 지정된 값들은, 매개변수화 된 값 패턴들이 메시지 텍스트에 나타날 때 마다, java.text.MessageFormat 클래스의 작동에 따라서 리소스 번들에서 검색된 메시지로 연결된다. 매개변수화 된 값들은 인덱스로 구분되기 때문에 중첩된 <fmt:param> 태그들의 순서는 중요하다.

<fmt:bundle>, <fmt:message>, <fmt:param> 태그들의 인터랙션은 Listing 6에 나타나있다. 여기에서, <fmt:bundle> 태그는 두 개의 중첩된 <fmt:message> 태그들에 의해서 검색될 로컬화 된 메시지들을 지정한다. 두 개의 <fmt:message> 태그들 중 첫 번째 태그는 상응하는 <fmt:param> 태그가 나타낼 하나의 매개변수화 된 값을 가진 메시지에 상응한다.


Listing 6. <fmt:message> 태그를 사용하여 로컬화 된 메시지 디스플레이 하기
				
<fmt:bundle basename="com.taglib.weblog.Greeting">
<fmt:message key="com.taglib.weblog.Greeting.greeting">
<fmt:param value="${user.fullName}"/>
</fmt:message>
  <br>
  <br>
  <center>
    <a href=
      "<c:url value='/index.jsp'/>"><fmt:message 
          key="com.taglib.weblog.Greeting.return"/></a>
  </center>
</fmt:bundle>

Listing 7은 <fmt:bundle>prefix 애트리뷰트의 사용 모습이다. prefix 애트리뷰트에 제공된 값은 중첩된 <fmt:message> 액션들의 모든 key 값 앞에 자동으로 붙는다. Listing 7은 Listing 6과 동일하지만, 두 개의 <fmt:message> 태그에 축약된 key 값을 사용한다는 점만 다르다.


Listing 7. <fmt:message> 태그에 대한 <fmt:bundle>의 prefix 애트리뷰트의 결과
				
<fmt:bundle basename="com.taglib.weblog.Greeting"
            prefix="com.taglib.weblog.Greeting.">
<fmt:message key="greeting">
<fmt:param value="${user.fullName}"/>
</fmt:message>
  <br>
  <br>
  <center>
    <a href="<c:url value='/index.jsp'/>"><fmt:message key="return"/></a>
  </center>
</fmt:bundle>

그림 7과 그림 8은 fmt 라이브러리의 메시지 관련 태그들의 실행 모습으로서, Listing 7의 결과와 Listing 4Listing 5의 로컬화 된 리소스 번들을 보여주고 있다. 그림 7은 브라우저 선호도가 English 로케일을 반영할 때 나타나는 결과를 보여준다.


그림 7. Listing 7의 en_US 로케일의 결과
Listing 7의 en_US 로케일의 결과

그림 8은 French를 지정한 로케일의 결과 모습이다.


그림 8. Listing 7의 fr_CA 로케일 결과
Listing 7의 fr_CA 로케일 결과

요약

JSTL fmt 라이브러리의 커스텀 태그를 사용하여 자바 플랫폼의 국제화 API에 간단하게 접근할 수 있다. 텍스트 메시지, 숫자 값, 날짜들 모두 로케일에 맞춰 디스플레이 되고, 시간 역시 특정 시간대에 맞춰 조정된다. 특정 사용자를 위한 로케일은 사용자의 브라우저 설정을 통해 자동으로 결정되거나, 페이지 작성자가 지정하기도 한다. fmt 라이브러리는 포맷된 데이터를 생성 및 디스플레이 하는 것 외에도, 숫자 데이터와 시간 데이터를 파싱하는 커스텀 태그들도 포함되어 있다.


관련글 더보기