IT_Programming/Android_Java

[펌] 안드로이드 문자열에 개수(단/복수)를 제대로 보여주자.

JJun ™ 2015. 7. 31. 12:13



 출처

 : http://sjava.net/2015/07/안드로이드-문자열에-개수단복수를-제대로-보여주/

 : https://developer.android.com/guide/topics/resources/string-resource?hl=ko






안드로이드(Android)에서 텍스트뷰(TextView)와 같은 위젯에 문자열(String)을 보여주는데 지역화(Localization)가 잘 되어 있다면 문자열 파일(/res/strings.xml)에서 문자열을 가져와서 보여줄 것이다. 이 문자열 파일을 사용해서 대부분의 지역화를 무리 없이 해결할 수 있다. 하지만 숫자가 문자열에 있는 경우 특정 언어(영어와 같은 단/복수가 구분된 경우)에서는 지역화가 문자열 파일만 사용해서는 로직이 복잡해지게 된다(한글의 경우에는 단/복수를 구분이 명확하지 않기에 비교적 간결한 편임). 그래서 안드로이드에서 어떻게 단/복수를 구분해서 사용하는지 살펴보자.


1. 스트링을 개수로 분리해서 사용하는 방법
– 이 형태를 사용하면 소스 코드에서 개수에 따른 분기가 필요해지는 단점이 있다.

01
02
<string name="item_1">I have one item.</string>
<string name="item_2">I have two items.</string>

– 아래와 같이 분기하는 로직이 필요하다.

01
02
03
04
05
if(count == 1)
    tv01.setText(getString(R.string.item_1));
 
if(count == 2)
      tv02.setText(getString(R.string.item_2));


2. 한 개의 스트링으로 여러 개수의 메시지를 처리하는 방법
– 이 형태를 사용하면 소스 코드는 간결해지나, 메시지가 모호해서 좋지 않은 UX를 제공하게 된다.
  필자의 경우 이런 형태로 처리한 경험이 있다.

01
<string name="item_count">I have %1$s item(s).</string>

– 분기하는 로직이 필요없어 간결하다.

01
tv01.setText(String.format(getString(R.string.item_count), count));


3. 스트링 배열의 형태로 plurals 요소를 사용하는 방법
– 이 형태를 사용하면 문자열 파일이 약간 복잡해지나, 이미 로직이 문자열 파일의 plurals 요소에 입력된 형태가 된다.
  따라서 로직이 2.번과 같은 모습을 띠고 있고 문자열은 명확하게 1.번의 모습으로 보여줄 수 있다.

01
02
03
04
05
06
07
08
09
<string name="item_count_one">I have %d item.</string>
<string name="item_count_other">I have %d items.</string>
 
<plurals name="item_counts">
    <item quantity="zero">@string/item_count_other</item>
    <item quantity=one">@string/item_count_one</item>
    <item quantity="two">@string/item_count_other</item>
    <item quantity="other">@string/item_count_other</item>
</plurals>

– 분기하는 로직이 필요 없어 간결하다. 아래의 소스 코드를 보면, getQuantityString 메서드를 사용해서 plurals 요소의 데이터,  
   plurals의 개수(quantity) 그리고 메시지에 보여주는 개수를 사용해서 완전한 메시지를 보여준다.
   getQuantityString() 메서드는 여기에서 확인하길 바란다.

01
tv01.setText(getResources().getQuantityString(R.plurals.item_counts, count, count));


이상 안드로이드에서 문자열에 개수를 표현하는 3가지 방법을 살펴봤다. 개인적으로는 plurals 요소를 사용하는 방법을 강력히 추천한다. 이 요소에 대한 자세한 내용은 https://developer.android.com/guide/topics/resources/string-resource.html#Plurals 에서 살펴볼 수 있다. 아래는 위 링크에서 plurals 요소가 사용할 수 있는 quantity 속성에서 사용할 수 있는 값들이다. 아래 값들에 대한 속성값이 어떤 의미를 가지는지 한 번만 읽어보고 사용하면 아주 쉽게 개수를 문자열에 보여줄 수 있겠다.

VALUEDESCRIPTION
zeroWhen the language requires special treatment of the number 0 (as in Arabic).
oneWhen the language requires special treatment of numbers like one (as with the number 1 in English and most other languages; in Russian, any number ending in 1 but not ending in 11 is in this class).
twoWhen the language requires special treatment of numbers like two (as with 2 in Welsh, or 102 in Slovenian).
fewWhen the language requires special treatment of “small” numbers (as with 2, 3, and 4 in Czech; or numbers ending 2, 3, or 4 but not 12, 13, or 14 in Polish).
manyWhen the language requires special treatment of “large” numbers (as with numbers ending 11-99 in Maltese).
otherWhen the language does not require special treatment of the given quantity (as with all numbers in Chinese, or 42 in English).

* 레퍼런스
– https://developer.android.com/guide/topics/resources/localization.html
– https://developer.android.com/guide/topics/resources/string-resource.html







문자열 리소스

문자열 리소스는 옵션 사항인 텍스트 스타일 지정 및 서식 지정 기능과 함께 애플리케이션에 사용할 수 있는 텍스트 문자열을 제공합니다. 애플리케이션에 문자열을 제공할 수 있는 리소스 유형으로는 세 가지가 있습니다.

문자열
단일 문자열을 제공하는 XML 리소스입니다.
문자열 배열
문자열로 구성된 배열을 제공하는 XML 리소스입니다.
수량 문자열(복수형)
복수형 표시를 위해 여러 문자열을 포함하는 XML 리소스입니다.

모든 문자열은 몇 가지 스타일 지정 마크업 및 서식 지정 인수를 적용할 수 있습니다. 문자열의 스타일 및 서식을 지정하는 방법에 대한 자세한 내용은 서식 지정 및 스타일 지정 관련 섹션을 참조하세요.

문자열

애플리케이션 또는 다른 리소스(예: XML 출력)에서 참조할 수 있는 단일 문자열입니다.

참고: 문자열은 name 특성에 제공된 값(XML 파일의 이름이 아님)을 사용하여 참조되는 단순한 리소스입니다. 따라서, XML 파일의 단일 <resources> 요소 아래에 문자열 리소스를 다른 단순한 리소스와 결합할 수 있습니다.

파일 위치:
res/values/filename.xml
파일 이름은 무엇이든 가능합니다. <string> 요소의 name은 리소스 ID로 사용됩니다.
컴파일된 리소스 데이터 유형:
String에 대한 리소스 포인터입니다.
리소스 참조:
Java: R.string.string_name
XML: @string/string_name
구문:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<string
       
name="string_name"
       
>text_string</string>
</resources>
요소:
<resources>
필수. 이는 루트 요소여야 합니다.

특성은 없습니다.

<string>
스타일 지정 태그를 포함할 수 있는 문자열입니다. 아포스트로피와 따옴표는 이스케이프 처리해야 합니다. 문자열에 적절한 스타일과 서식을 지정하는 방법에 대한 자세한 내용은 아래에 나와 있는 서식 지정 및 스타일 지정을 참조하세요.

특성:

name
문자열. 문자열의 이름입니다. 이 이름은 리소스 ID로 사용됩니다.
예:
res/values/strings.xml에 저장된 XML 파일:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<string name="hello">Hello!</string>
</resources>

다음 레이아웃 XML은 뷰에 문자열을 적용합니다.

<TextView
   
android:layout_width="fill_parent"
   
android:layout_height="wrap_content"
   
android:text="@string/hello" />

다음 애플리케이션 코드는 문자열을 검색합니다.

String string = getString(R.string.hello);

getString(int) 또는 getText(int)를 사용하여 문자열을 검색할 수 있습니다. getText(int)는 문자열에 적용된 모든 서식 있는 텍스트 스타일을 유지합니다.

문자열 배열

애플리케이션에서 참조될 수 있는 문자열로 구성된 배열입니다.

참고: 문자열 배열은 name 특성에 제공된 값(XML 파일의 이름이 아님)을 사용하여 참조되는 단순한 리소스입니다. 따라서, XML 파일의 단일 <resources> 요소 아래에 문자열 배열 리소스를 다른 단순한 리소스와 결합할 수 있습니다.

파일 위치:
res/values/filename.xml
파일 이름은 무엇이든 가능합니다. <string-array> 요소의 name은 리소스 ID로 사용됩니다.
컴파일된 리소스 데이터 유형:
String 배열에 대한 리소스 포인터입니다.
리소스 참조:
Java: R.array.string_array_name
구문:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<string-array
       
name="string_array_name">
       
<item
           
>text_string</item>
   
</string-array>
</resources>
요소:
<resources>
필수. 이는 루트 요소여야 합니다.

특성은 없습니다.

<string-array>
문자열로 구성된 배열을 정의합니다. 하나 이상의 <item> 요소를 포함합니다.

특성:

name
문자열. 배열의 이름입니다. 이 이름은 배열을 참조하는 리소스 ID로 사용됩니다.
<item>
스타일 지정 태그를 포함할 수 있는 문자열입니다. 값은 다른 문자열 리소스에 대한 참조일 수 있습니다. <string-array> 요소의 하위 요소여야 합니다. 아포스트로피와 따옴표는 이스케이프 처리해야 합니다. 문자열에 적절한 스타일과 서식을 지정하는 방법에 대한 자세한 내용은 아래에 나와 있는 서식 지정 및 스타일 지정을 참조하세요.

특성은 없습니다.

예:
res/values/strings.xml에 저장된 XML 파일:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<string-array name="planets_array">
       
<item>Mercury</item>
       
<item>Venus</item>
       
<item>Earth</item>
       
<item>Mars</item>
   
</string-array>
</resources>

다음 애플리케이션 코드는 문자열 배열을 검색합니다.

Resources res = getResources();
String[] planets = res.getStringArray(R.array.planets_array);

수량 문자열(복수형)

언어마다 수량과 관련한 문법적 일치에 대해 각기 다른 규칙을 가집니다. 예를 들어, 영어의 경우 수량 1은 특별한 케이스입니다. "1 book"이라고 쓰지만, 다른 수량은 'n books'라고 씁니다. 단수형과 복수형 간의 이러한 구별은 매우 일반적인 것이며, 다른 언어에서는 더 미세하게 구별하기도 합니다. Android에서 지원하는 전체 집합은 zeroonetwofewmany 및 other입니다.

지정된 언어 및 수량에 사용할 케이스를 결정하기 위한 규칙은 매우 복잡할 수 있으므로, Android는 적합한 리소스를 선택하는 getQuantityString()과 같은 메서드를 제공합니다.

예전부터 '수량 문자열'(그리고 API에서도 여전히 이렇게 부름)이라고 불렀지만 수량 문자열은 오직 복수형에만 사용되어야 합니다. 예를 들어, 읽지 않은 메시지가 있는지 여부에 따라 표시되는 Gmail의 '받은 편지함' 및 '받은 편지함(12)'과 같은 항목을 구현하기 위해 수량 문자열을 사용하는 것은 올바르지 않을 수 있습니다. if 문 대신 수량 문자열을 사용하는 것이 편해 보일 수 있지만, 일부 언어(예: 중국어)의 경우 이러한 문법적 구분을 전혀 하지 않으므로 항상 other 문자열을 사용해야 합니다.

사용할 문자열을 선택할 때는 문법적인 측면의 필요성만 기준으로 해야 합니다. 영어의 경우, zero에 대한 문자열은 수량이 0인 경우에도 무시됩니다. 그 이유는 0이 2 또는 1을 제외한 다른 숫자와 문법적으로 다르지 않기 때문입니다('zero books', 'one book', 'two books' 등). 역으로, 한국어에는 오직 other 문자열만 사용됩니다.

two가 수량 2에만 적용될 수 있는 것처럼 들리는 사실에 현혹되지 마세요. 2, 12, 102(기타 등등)가 모두 서로 같게 취급되지만 다른 수량과는 다르게 취급되도록 요구하는 언어가 있을 수 있습니다. 번역자에게 문의하여 언어에서 실제로 어떠한 구분이 요구되는지를 파악해야 합니다.

수량에 중립적인 표현(예: "Books: 1")을 사용하여 수량 문자열 사용을 피할 수 있는 경우가 많이 있습니다. 이것이 애플리케이션에 적합한 스타일인 경우, 이렇게 하면 여러분과 여러분 번역자의 삶이 한결 수월해질 것입니다.

참고: 복수형 모음은 name 특성에 제공된 값(XML 파일의 이름이 아님)을 사용하여 참조되는 단순한 리소스입니다. 따라서, XML 파일의 단일 <resources> 요소 아래에 복수형 리소스를 다른 단순한 리소스와 결합할 수 있습니다.

파일 위치:
res/values/filename.xml
파일 이름은 무엇이든 가능합니다. <plurals> 요소의 name은 리소스 ID로 사용됩니다.
리소스 참조:
Java: R.plurals.plural_name
구문:
<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<plurals
       
name="plural_name">
       
<item
           
quantity=["zero" | one" | "two" | "few" | "many" | "other"]
           
>text_string</item>
   
</plurals>
</resources>
요소:
<resources>
필수. 이는 루트 요소여야 합니다.

특성은 없습니다.

<plurals>
문자열 모음이며, 수량에 따라 그중 특정 문자열이 제공됩니다. 하나 이상의 <item> 요소를 포함합니다.

특성:

name
문자열. 문자열 쌍의 이름입니다. 이 이름은 리소스 ID로 사용됩니다.
<item>
복수형 또는 단수형 문자열입니다. 값은 다른 문자열 리소스에 대한 참조일 수 있습니다. <plurals> 요소의 하위 요소여야 합니다. 아포스트로피와 따옴표는 이스케이프 처리해야합니다. 문자열에 적절한 스타일과 서식을 지정하는 방법에 대한 자세한 내용은 아래에 나와 있는 서식 지정 및 스타일 지정을 참조하세요.

특성:

quantity
키워드. 이 문자열이 사용되는 시기를 나타내는 값입니다. 유효한 값(괄호 안에 일부 예가 포함되어 있음):
설명
zero언어가 숫자 0에 대한 특수한 처리를 요구하는 경우(예: 아랍어).
one언어가 1과 같은 숫자에 대한 특수한 처리를 요구하는 경우(예: 영어 및 기타 대부분의 언어의 경우 숫자 1. 러시아어의 경우 1로 끝나지만 11로 끝나지 않는 숫자(이 클래스에 나와 있음)).
two언어가 2와 같은 숫자에 대한 특수한 처리를 요구하는 경우(예: 웨일스어의 경우 2 또는 슬로베니아어의 경우 102).
few언어가 '작은' 숫자에 대한 특수한 처리를 요구하는 경우(예: 체코어의 경우 2, 3, 4 또는 폴란드어의 경우 2, 3 또는 4로 끝나지만 12, 13 또는 14로 끝나지 않는 숫자).
many언어가 '큰' 숫자에 대한 특수한 처리를 요구하는 경우(예: 몰타어의 경우 11-99로 끝나는 숫자).
other언어가 지정된 수량에 대한 특수한 처리를 요구하는 경우(예: 중국어의 경우 모든 숫자 또는 영어의 경우 42).
예:
res/values/strings.xml에 저장된 XML 파일:

<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<plurals name="numberOfSongsAvailable">
       
<!--
             As a developer, you should always supply one" and "other"
             strings. Your translators will know which strings are actually
             needed for their language. Always include %d in one" because
             translators will need to use %d for languages where one"
             doesn't mean 1 (as explained above).
          -->

       
<item quantity=one">%d song found.</item>
       
<item quantity="other">%d songs found.</item>
   
</plurals>
</resources>

res/values-pl/strings.xml에 저장된 XML 파일:

<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<plurals name="numberOfSongsAvailable">
       
<item quantity=one">Znaleziono %d piosenkę.</item>
       
<item quantity="few">Znaleziono %d piosenki.</item>
       
<item quantity="other">Znaleziono %d piosenek.</item>
   
</plurals>
</resources>

Java 코드:

int count = getNumberOfsongsAvailable();
Resources res = getResources();
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);

getQuantityString() 메서드를 사용할 때는 문자열에 숫자로 문자열 서식 지정이 포함된 경우 count를 두 번 전달해야 합니다. 예를 들어, 문자열 %d songs found에 대해 첫 번째 count 매개변수는 적절한 복수형 문자열을 선택하고 두 번째 count 매개변수는 %d 자리표시자에 삽입됩니다. 복수형 문자열에 문자열 서식 지정이 포함되어 있지 않은 경우에는 이 매개변수를 getQuantityString에 전달할 필요가 없습니다.

서식 지정 및 스타일 지정

다음은 문자열 리소스에 올바른 서식 및 스타일을 지정하는 방법에 대해 알아야 할 몇 가지 중요 사항입니다.

아포스트로피 및 따옴표 이스케이프 처리

문자열에 아포스트로피(')가 있으면 이를 백슬래시(\')로 이스케이프 처리하거나 이 문자열을 큰따옴표("")로 묶어야 합니다. 예를 들어, 작동하는 문자열과 작동하지 않는 문자열 몇 가지가 여기에 나와 있습니다.

<string name="good_example">This\'ll work</string>
<string name="good_example_2">"This'll also work"</string>
<string name="bad_example">This doesn't work</string>
   
<!-- Causes a compile error -->

문자열에 큰따옴표가 있으면 이를 (\")로 이스케이프 처리해야 합니다. 문자열이 작은따옴표로 묶인 경우 작동하지 않습니다.

<string name="good_example">This is a \"good string\".</string>
<string name="bad_example">This is a "bad string".</string>
   
<!-- Quotes are stripped; displays as: This is a bad string. -->
<string name="bad_example_2">'This is another "bad string".'</string>
   
<!-- Causes a compile error -->

문자열 서식 지정

String.format(String, Object...)를 사용하여 문자열의 서식을 지정해야 할 경우, 문자열 리소스에 서식 인수를 추가하여 이렇게 할 수 있습니다. 예를 들면, 다음 리소스를 사용합니다.

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

이 예제에서는 서식 문자열에 두 개의 인수가 있습니다. %1$s는 문자열이고 %2$d는 10진수입니다. 다음과 같이 애플리케이션에서 인수를 사용하여 문자열의 서식을 지정할 수 있습니다.

Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);

HTML 마크업을 사용하여 스타일 지정

HTML 마크업을 사용하여 문자열에 스타일을 추가할 수 있습니다. 예:

<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<string name="welcome">Welcome to <b>Android</b>!</string>
</resources>

지원되는 HTML 요소는 다음과 같습니다.

  • <b> - 굵은 텍스트를 지정할 때 사용합니다.
  • <i> - 기울임꼴 텍스트를 지정할 때 사용합니다.
  • <u> - 텍스트에 밑줄 처리를 합니다.

경우에 따라 서식 문자열로도 사용되는 스타일이 지정된 텍스트 리소스를 생성하고자 할 수 있습니다. 보통, String.format(String, Object...) 메서드가 문자열에서 모든 스타일 정보를 제거하므로 이는 작동하지 않습니다. 이에 대한 해결 방법은 이스케이프 처리된 항목을 사용하여 HTML 태그를 쓰는 것입니다. 그러면 서식 지정이 수행된 후 이 항목이 fromHtml(String)으로 복구됩니다. 예:

  1. 스타일이 지정된 텍스트 리소스를 HTML로 이스케이프 처리된 문자열로 저장합니다.
    <resources>
     
    <string name="welcome_messages">Hello, %1$s! You have <b>%2$d new messages</b>.</string>
    </resources>

    이 서식이 지정된 문자열에 <b> 요소가 추가됩니다. 여는 괄호는 HTML에서 < 표기법을 사용하여 이스케이프 처리됩니다.

  2. 그런 다음 평상시처럼 문자열의 서식을 지정하고 fromHtml(String)을 호출하여 이 HTML 텍스트를 스타일이 지정된 텍스트로 변환합니다.
    Resources res = getResources();
    String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);
    CharSequence styledText = Html.fromHtml(text);

fromHtml(String) 메서드가 모든 HTML 항목의 서식을 지정하므로 htmlEncode(String)를 사용하여 서식이 지정된 텍스트에 사용할 문자열에서 모든 가능한 HTML 문자를 이스케이프 처리해야 합니다. 예를 들어, 문자열 인수를 '<' 또는 '&'와 같은 문자를 포함할 수 있는 String.format()에 전달할 경우 서식이 지정된 문자열이 fromHtml(String)을 통해 전달될 때 문자가 원래 쓰여진 방식대로 표시되도록 서식이 지정되기 전에 이스케이프 처리되어야 합니다. 예:

String escapedUsername = TextUtil.htmlEncode(username);

Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), escapedUsername, mailCount);
CharSequence styledText = Html.fromHtml(text);

Spannable을 사용한 스타일 지정

Spannable은 색상 및 글꼴 두께와 같은 서체 속성을 사용하여 스타일을 지정할 수 있는 텍스트 객체입니다. SpannableStringBuilder를 사용하여 텍스트를 빌드한 후 android.text.style 패키지에 정의된 스타일을 텍스트에 적용할 수 있습니다.

다음과 같은 도우미 메서드를 사용하여 스팬 가능한 텍스트를 생성하는 작업의 대부분을 설정할 수 있습니다.

/** * Returns a CharSequence that concatenates the specified array of CharSequence * objects and then applies a list of zero or more tags to the entire range. * * @param content an array of character sequences to apply a style to * @param tags the styled span objects to apply to the content * such as android.text.style.StyleSpan * */ private static CharSequence apply(CharSequence[] content, Object... tags) { SpannableStringBuilder text = new SpannableStringBuilder(); openTags(text, tags); for (CharSequence item : content) { text.append(item); } closeTags(text, tags); return text; } /** * Iterates over an array of tags and applies them to the beginning of the specified * Spannable object so that future text appended to the text will have the styling * applied to it. Do not call this method directly. */ private static void openTags(Spannable text, Object[] tags) { for (Object tag : tags) { text.setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK); } } /** * "Closes" the specified tags on a Spannable by updating the spans to be * endpoint-exclusive so that future text appended to the end will not take * on the same styling. Do not call this method directly. */ private static void closeTags(Spannable text, Object[] tags) { int len = text.length(); for (Object tag : tags) { if (len > 0) { text.setSpan(tag, 0, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { text.removeSpan(tag); } } }

다음 bolditalic 및 color 메서드는 도우미 메서드를 호출하여 android.text.style 패키지에 정의된 스타일을 적용하는 방법을 보여줍니다. 다른 유형의 텍스트 스타일 지정을 수행하는 유사한 메서드를 생성할 수 있습니다.

/** * Returns a CharSequence that applies boldface to the concatenation * of the specified CharSequence objects. */ public static CharSequence bold(CharSequence... content) { return apply(content, new StyleSpan(Typeface.BOLD)); } /** * Returns a CharSequence that applies italics to the concatenation * of the specified CharSequence objects. */ public static CharSequence italic(CharSequence... content) { return apply(content, new StyleSpan(Typeface.ITALIC)); } /** * Returns a CharSequence that applies a foreground color to the * concatenation of the specified CharSequence objects. */ public static CharSequence color(int color, CharSequence... content) { return apply(content, new ForegroundColorSpan(color)); }

다음은 이러한 메서드를 연결하여 개별 단어에 적용된 스타일 유형이 각각 다른 문자 시퀀스를 생성하는 방법에 대한 예시입니다.

// Create an italic "hello, " a red "world", // and bold the entire sequence. CharSequence text = bold(italic(res.getString(R.string.hello)), color(Color.RED, res.getString(R.string.world)));