주요 내용으로 이동하기

Swift 복수(Plural) 로컬라이징이 안 되는 문제

stringsdict 파일의 one, few, many 값이 적용되지 않고 other만 적용될 때

로컬라이징이 아니라 로컬라이제이션(localization)이 맞는 말이다. 그러나 그렇게 쓰면 제목이 너무 길어지기도 하고, 검색에 잘 걸리기 위해 다들 쓰는 것처럼 로컬라이징이라고 썼다.

문제

Apple 생태계에서 개발 시 Xcode의 프로젝트 설정에서 Localizations를 설정하면 앱의 언어를 시스템 언어에 맞추도록 해줄 수 있다.

다만 원문-번역의 일대일 대응으로만 번역을 처리하기에는 언어별로 수를 지칭하는 말이 달라 부자연스러운 경우도 생긴다.

예를 들어 “1 issue”는 맞지만 여러 개인 경우 “2 issues”처럼 복수형 접미사를 붙여야 하는 언어들이 있다. “0 story”는 말이 되지 않으니 “no story”라고 써야 하는 것도 이 경우에 해당한다.

.stringsdict 파일을 사용하면 이러한 단어의 복수(Plural)를 효과적으로 처리할 수 있다. 이 내용은 문서에도 이미 잘 정리되어 있다.

Xcode Documentation (Apple Developer)
Localizing strings that contain plurals
<!-- /en.lproj/Localizable.stringsdict -->
<dict>
    <key>test</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@test@</string>
        <key>test</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>zero</key>
            <string>%d → Zero</string>
            <key>one</key>
            <string>%d → One</string>
            <key>two</key>
            <string>%d → Two</string>
            <key>few</key>
            <string>%d → Few</string>
            <key>many</key>
            <string>%d → Many</string>
            <key>other</key>
            <string>%d → Other</string>
        </dict>
    </dict>
</dict>
<!-- /ko.lproj/Localizable.stringsdict -->
<dict>
    <key>test</key>
    <dict>
        <key>NSStringLocalizedFormatKey</key>
        <string>%#@test@</string>
        <key>test</key>
        <dict>
            <key>NSStringFormatSpecTypeKey</key>
            <string>NSStringPluralRuleType</string>
            <key>NSStringFormatValueTypeKey</key>
            <string>d</string>
            <key>zero</key>
            <string>%d → 없음</string>
            <key>one</key>
            <string>%d → 하나</string>
            <key>two</key>
            <string>%d → 둘</string>
            <key>few</key>
            <string>%d → 조금</string>
            <key>many</key>
            <string>%d → 많이</string>
            <key>other</key>
            <string>%d → 기타</string>
        </dict>
    </dict>
</dict>
import SwiftUI

private func testString(count: UInt) -> String {
    let formatString : String = NSLocalizedString("test", comment: "")
    let resultString : String = String.localizedStringWithFormat(formatString, count)
    return resultString
}

struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text(testString(count: 0))
            Text(testString(count: 1))
            Text(testString(count: 2))
            Text(testString(count: 3))
            Text(testString(count: 4))
            Text(testString(count: 5))
        }
        .padding()
    }
}

위 Apple 개발자 문서에 따라 만들어본 코드이다. 0 → Zero, 없음 / 1 → One, 하나 / 2 → Two, 둘 / 3, 4, 5 → Few, 조금과 같은 결과를 주면 제일 좋겠지만… 실제로는 그렇지 않다.

왜 2는 Two가 아니고 3은 Few가 아닌 걸까?

한국어는 0을 제외하고는 전부 Other(기타)로 처리되어 있는 것을 볼 수 있다. 😇

원인

이 Stack Overflow 답변에 따르면 이는 애플이 Unicode CLDR 표준에 따라 구현했기 때문이라고 한다.

CLDR v42.0의 한국어 Plural Rules 항목을 보면, “other” 외의 필드가 없는 것을 볼 수 있다. 영어도 마찬가지로 서수(ordinal)가 아닌 세는 수(cardinal)의 경우 “one”, “other” 둘로만 구분되어 있고 “two”, “few” 등의 구현이 없다.

일단 저 표준의 목적이 숫자에 따라 명사의 형태가 바뀌는지 여부를 파악하기 위해서인 것 같은데, 그렇게 생각해보면 1일이든 2일이든 똑같이 이라고 부르는 한국어의 경우 따로 구현 사항이 없는 게 맞는 것 같기도 하다. 물론 1일 대신 하루라고 부르기도 하지만, 모든 단어에 적용되는 것은 아니기 때문에 (사과 1개 대신 쓰는 또다른 단어가 있는 것은 아닌 것처럼), 이 정도면 표준의 구현 방식이 납득 가능해 보인다.

애플이 표준을 100% 준수했다면 영어와 한국어 모두 zero 항목이 없기에 사실 zero도 other로 처리되어야 말이 될 것 같은데, 그렇지 않은 이유가 궁금하긴 하다. 개발 편의를 위해 표준을 약간 비틀어서 적용한 거라면 그냥 언어에 관계없이 zero, one, two, few, many, other 6가지 다 사용 가능하게 하면 어떨까 싶기는 하다. 그렇게 한다면 여러 앱에서 더 다채로운 번역을 제공할 수도 있을 텐데 아쉽다.

검색하다 보니 Twitter의 twitter-cldr-js에서도 Unicode CLDR 규칙을 따르고 있는 것을 확인했다. 참고해볼 만해 보인다.

const TwitterCldr = require("twitter_cldr");

console.log("en:", TwitterCldr.load("en").PluralRules.all()); // en: [ 'one', 'other' ]
console.log("ko:", TwitterCldr.load("ko").PluralRules.all()); // ko: [ 'other' ]
console.log("uk:", TwitterCldr.load("uk").PluralRules.all()); // uk: [ 'few', 'many', 'one', 'other' ]

해결

자체 Localization 기능의 한계이니 꼭 필요하다면 Swift 코드를 뜯어서 숫자별, 언어별로 분기를 나누는 게 가장 속 시원한 해결법일 것 같다.

글을 쓰게 된 배경

이 글을 쓰게 된 이유는, 최근 Ice Cubes라는 멋진 오픈소스 iOS 마스토돈 클라이언트에 작은 기여를 조금씩 하고 있기 때문이다.

Swift 개발이라고는 간단한 macOS 색상 추출 도구 하나 만들어본 게 전부이기에, 무작정 Swift 코드를 작성할 수는 없고 한국어 번역 및 앱 아이콘 제작을 하고 있다. 🙂

여러 사용자가 나의 글 하나를 좋아요했을 때 나타나는 x님 외 n명이 좋아요함 같은 문구를 매끄럽게 번역하고자 복수형 번역에 대한 정보를 찾게 되었다. 그러나 한국어의 경우 위와 같은 한계가 있었고, 결국 Swift 코드를 손보지 않는 이상 해결 방안이 없어서, 일단은 보류하고 가능한 한 괜찮은 번역을 올려둔 상태이다. 코드를 뜯을 수 있으면 좋았겠지만 아직 Swift를 잘 모르기도 하고, 이미 언어 15개를 지원하고 있어서 그 점도 고려해야 한다…

아무튼 Ice Cubes는 멋진 앱이니, 한국어 화자들이 이 앱으로 마스토돈(및 Fediverse)에서 즐거운 시간을 보냈으면 좋겠다.