Phrase를 이용한 Android String Formatting

Phrase는 안드로이드용 텍스트 토큰 교체를 위한 마이크로 라이브러리입니다. Phrase는 미국의 유명한 결제 시스템 회사인 Squre에서 자사의 Squre Register라는 안드로이드 어플리케이션을 프랑스와 일본어로 번역하는 과정에서 겪은 문제점들을 해결하기 위해 만들어졌습니다.

다음과 같은 문자열 선언이 strings.xml에 정의되어있다고 가정해 보겠습니다.

<string name="greeting">
  Hello %1$s, today\'s cook yielded %2$d %3$s.
</string>

greeting에 정의된 포매팅은 간단한 형태를 띄고있습니다. 안드로이드의 Context는 정의되어있는 문자열을 가져오고 한번에 포맷팅할 수 있는 오버로딩된getString(…) 메소드를 제공하고 있습니다.

String name = "Walter";
int yield = 50;
String unit = "pounds";
String greeting = context.getString(R.string.greeting, name, yield, unit);

하지만 위에서 볼 수 있는 %1$s 와 같은 문법은 프로그래머가 아닌 사람들에게는 명확하게 이해하기 어렵습니다. 각각의 포매팅 지정자들이 번역될 때 이러한 이해하기 어려운 형태의 지정자들로 인해 오타등의 실수가 발생할 수 있습니다. 또한 지정자들이 또다른 지정자들 혹은 문자열에 인접해 있을 때 더 많은 문제가 발생했습니다.

또다른 문제는 %2$d와 같은 지정자의 의미를 해석하기 쉽지 않다는 점 입니다. 우리는 주변의 텍스트를 통해 여기에 들어갈 문자의 의미를 알아차릴 수 있을 것이고 Java 코드상의 버그를 피하기 위해 파라미터의 순서가 정확하게 일치해야만 합니다.

// 파라미터의 순서가 잘못되었으므로 버그 발생!
String greeting = context.getString(R.string.greeting, name, unit, yield);

마지막으로 Context.getString(…)은 볼드나 이탤릭같은 스타일이 적용된 텍스트를 처리할 수 없다는 점 입니다. 당신이 strings.xml에 간단한 HTML 태그를 포맷 지정자와 함께 사용하였다면 HTML 태그는 조용히 무시될 것입니다.

Phrase

Phrase를 사용하면 greeting은 다음과 같이 변화됩니다. 보시다 싶이 애매모호했던 지정자들은 읽기 쉽고 이해하기 쉬운 {name} 과 {yield} 로 변경되었습니다.

<string name="greeting">
  Hello {name}, today\'s cook yielded {yield} {unit}.
</string>

번역중 발생할 수 있는 오류를 줄이기 위한 첫번째 목표와 이것을 지키기 위한 룰은 간단합니다.

  • 키를 중괄호 {} 로 또한번 감싸야 할 경우(보여지기 위해) 두개의 {{ 를 사용하여 이스케이프 처리 할 수 있습니다.
  • 키는 소문자 영문자로 시작해야 하고 다음으로는 소문자와 언더스코어 _ 가 사용 가능합니다.

더 많은 유연성을 제공하는것은 복잡성을 증가시키기에 우리는 대문자, 숫자, 또는 밑줄 이외의 특수문자를 허용하지 않습니다.

포맷팅은 유연한 형태로 변경 되었고 순서가 없는 키/밸류 형태의 알기쉬운 키형태를 사용하게 되었기에 프로그래머의 인생이 좀 더 편안해 졌습니다. (과연?ㅎ)

// 순서와 상관없이 put(...)을 호출
CharSequence greeting = Phrase.from(context, R.string.greeting)
    .put("unit", unit)
    .put("name", name)
    .put("yield", yield)
    .format();

Phrase는 문자열에 삽입되어있는 HTML태그와 포맷지정자들을 모두 살리기 위해 String 대신에 CharSequence를 반환합니다.

<string name="did_you_learn">
  <!-- class_type is something like Chemistry. -->
  Did you learn <b>nothing</b> from my {class_type} class?
</string>

Validation

Phrase는 fail-fast 철학을 준수합니다. Phrase는 다음과 같은 실수가 발생할 경우 예외를 발생시킵니다.

  • 패턴의 파싱중에 잘못된 중괄호 패턴이나 키에 잘못된 문자열이 사용되었을 경우
  • put(…)을 호출할 때 키 또는 밸류에 null 이 들어갈 경우
  • 포매팅 패턴에 존재하지 않는 키를 put(…)으로 넣을 경우
  • format() 이 호출되는 시점에 모든 키에 대해 밸류가 채워지지 않은 경우

즉각적인 예외 또는 크래시 발생은 개발 과정에서의 실수를 줄여주고 당황스러운 번역 실수 또는 포맷팅 되지 않은 문자열이 표시되는 확률을 최소화 해줍니다. 왜냐하면 키들은 이해하기 쉽고 각각의 언어별로 strings.xml에 정의된 Phrase 키가 똑같기 때문에 유효성 스크립트를 제작하는것도 간단해집니다.

Example

간단하게 위의 내용을 테스트 해보도록 하겠습니다. 개발은 맥 환경의 Android Studio와 Gradle을 이용하였습니다. 먼저 간단한 프로젝트를 만들어 보도록 하겠습니다. 저는 HelloPhrase라는 이름의 프로젝트를 만들었습니다. 이후에 build.gradle 파일의 내용을 수정합니다.

1

dependencies 에 다음의 한줄을 추가하면 사용준비가 끝납니다. 현재 시점에서는 1.0.3이 최신버전입니다.

dependencies {
  compile 'com.squareup.phrase:phrase:(insert latest version)'
}

strings.xml에는 다음을 추가하였습니다.

<string name="greeting">
Hello <b>{name}</b>, today\'s cook yielded <u>{yield}</u> {unit}.
</string>

적절한 텍스트뷰에 위의 결과물을 출력해 보겠습니다.

TextView tv = (TextView) rootView.findViewById(R.id.text);

CharSequence greeting = Phrase.from(getActivity(), R.string.greeting)
    .put("unit", "pounds")
    .put("name", "Walter")
    .put("yield", 50)
    .format();

tv.setText(greeting);

device-2014-01-29-110001

정상적으로 결과물이 출력되는것을 보았습니다. 실제로 개발중에 느낀건데 조금이라도 값이 안맞거나 하면 바로 크래시가 나는군요. 서버에서 출력할 메시지를 받는 경우 잘못하면 앱이 죽어버리는 문제를 야기할 수 도 있을것 같습니다. 사용에 유의하시기 바랍니다.

참고 : http://corner.squareup.com/2014/01/phrase.html