%@, %1$@, %lld: 번역에서 깨지는 형식 지정자

업데이트 · LocalePilot

변수가 들어간 iOS 문자열에는 %@나 %lld가 있습니다. 번역가는 그것을 올바른 위치에, 올바른 타입으로 남겨야 합니다. 계속 반복되는 실수(그중 하나는 프랑스어에만 해당)와 이를 찾아내는 방법을 소개합니다.

브라우저에서 바로 번역의 플레이스홀더를 검사해 보세요. 무료이고 가입이 필요 없으며, 아무것도 업로드되지 않습니다.

%는 어디서 오는가

iOS 앱에서 변수가 있는 현지화 문자열은 형식 문자열이 됩니다. SwiftUI에서는 암묵적으로 그렇게 됩니다.

Text("\(count) items left")                  // key: "%lld items left"
Text("Welcome back, \(name)!")               // key: "Welcome back, %@!"
String(localized: "Total: \(price) €")       // price is a Double: "Total: %lf €"

Int는 %lld, String은 %@, Double은 %lf가 됩니다. 예전 API에서는 형식을 직접 씁니다: String(format: NSLocalizedString("items_left", comment: ""), count). 이때 .strings 파일에는 %d나 %ld가 들어갑니다.

어느 쪽이든 번역가가 받는 텍스트에는 중간에 % 기호가 있고, 바로 거기서 문제가 생깁니다.

번역이 깨지는 세 가지 방식

지정자를 빠뜨리기. "Invitez %@ à modifier cette liste" 대신 "Invitez à modifier cette liste". 크래시는 없습니다. 인수가 무시되고 상대의 이름이 화면에서 사라질 뿐입니다. 가장 흔하면서도 눈에 잘 띄지 않는 실수입니다.

타입 바꾸기. %lld를 %@로, 또는 그 반대로 바꾸는 경우입니다. 형식은 인수를 잘못된 타입으로 읽습니다. 잘해야 엉뚱한 숫자, 최악이면 크래시입니다. %@는 객체를 기대하는데 정수가 들어오기 때문입니다.

조용히 순서 바꾸기. 어순이 다른 언어에서 "%@ shared %@"는 두 이름이 뒤바뀐 문장이 됩니다. 구문은 맞지만 의미가 틀립니다. 한국어는 어순이 영어와 크게 다르므로 특히 주의해야 합니다.

위치 지정으로 순서 바꾸기

순서를 바꾸려면 인수에 번호를 붙입니다. %1$@는 첫 번째 인수, %2$@는 두 번째 인수이며, 문장 어디에 있어도 됩니다.

/* source */      "%@ shared %@ with you" = "%1$@ shared %2$@ with you";
/* French */      "%@ shared %@ with you" = "%1$@ vous a partagé %2$@";
/* other order */ "%@ shared %@ with you" = "%2$@ : partagé par %1$@";

기억할 규칙은 하나입니다. 한 문자열 안에서는 모두 번호를 붙이거나 모두 붙이지 않아야 합니다. %1$@와 %@를 섞으면 동작이 정의되지 않습니다.

프랑스어 함정: "20 % de remise"

프랑스어 조판에서는 퍼센트 기호 앞에 공백을 넣습니다: "20 % de remise"(20% 할인). 형식 문자열에서는 이것이 문제가 됩니다. % d(퍼센트, 공백, d)가 유효한 지정자이기 때문입니다. 공백은 printf 플래그("양수 앞에 공백 채우기")이고 d는 정수 변환입니다. 다음 문장은

"Profitez de %lld % de remise"

인수 하나에 %lld와 % d, 두 개의 지정자를 갖게 됩니다. 두 번째 지정자는 존재하지 않는 값을 읽습니다. 올바른 형태는 기호를 두 번 쓰는 것입니다.

"Profitez de %lld %% de remise"

형식을 거치는 문자열, 즉 변수가 하나 이상 있는 문자열에만 해당합니다. 인수가 없는 고정된 "20 % de remise"는 쓴 그대로 표시됩니다. 하지만 번역은 계속 바뀌므로, 누군가 그 문장에 변수를 추가하면 홀로 있는 %는 버그가 됩니다.

복수형

"1 item / 3 items"를 위해 키 두 개와 코드의 if를 쓰지 마세요. String Catalog에서는 %lld items left 키에 복수형 변형(one, other, 언어에 따라 few나 many)을 두고, 각각에 %lld를 넣습니다. 한국어는 other 하나뿐입니다. 한 문장에 따로 변하는 숫자가 두 개 있으면 Xcode는 치환(%#@items@)을 씁니다. 드문 경우이며 Xcode에서 편집합니다.

출시 전 검사

기본 검사는 기계적입니다. 각 번역의 지정자 목록(타입과 위치)이 원문과 일치해야 합니다. LocalePilot의 QA가 .strings와 .xcstrings 파일에서 하는 일이 바로 이것입니다. %@, %1$@, %d, %ld, %lld, %.2f, %u, %i, %s를 인식하고, %%는 텍스트로 취급하며, 빠지거나 남는 지정자를 내보내기 전에 표시합니다. %e나 %x 같은 드문 변환은 바로 "20%en"을 오류로 오인하지 않기 위해 의도적으로 무시합니다. 위의 "% d" 경우는 사람이 검토해야 할 몫입니다. 프랑스어 번역에서 뒤에 공백이 오는 %를 검색해 보세요.

자주 묻는 질문

%d, %ld, %lld는 무엇이 다른가요?

기대하는 정수의 크기입니다. SwiftUI는 보간된 Int에 %lld를 씁니다. 직접 작성한 .strings 파일에서는 원문의 지정자를 그대로 유지하세요.

번역된 문자열에 % 기호를 쓰려면 어떻게 하나요?

변수가 있는 문자열에서는 %%로 두 번 씁니다. 그렇지 않으면 ‘% d’나 ‘% s’가 지정자로 읽힐 수 있습니다.

번역가가 변수 순서를 바꿀 수 있나요?

네, 위치 지정을 쓰면 됩니다(%2$@ 다음에 %1$@). 다만 그때는 문자열 전체에 번호를 붙여야 합니다.