Localization
앱이 고객의 언어로 말할 수 있게 만드는 것
유저는 OS 설정에서 자신이 선호하는 언어를 순서대로 지정할 수 있다.
언어와는 별개로 지역도 지정할 수 있다.
iOS 9부터는 숫자 시스템도 유저가 지정할 수 있다.
iOS 9에 새로 추가된 키보드
타이핑 예측 지원 추가
특정 언어(ex. Telugu)는의 키보드가 입력하는 단위가 실제 보여지는 것과 차이가 날 수 있다.
유저가 보는 문자열을 번역하려면 어떻게 해야 할까?
.strings 파일 포맷
주석은 C 스타일(/* */)
개발용 키값은 왼쪽, 타겟 언어 값은 오른쪽
lproj 디렉토리에 저장된다.
Xcode 6부터 localization을 일반적으로 쓰는 XLIFF 형태로 내보낼 수 있게 되었고, 이를 다시 .strings 파일로 바꿔서 import할 수도 있다.
개발자가 코드레벨에서 쓰기 위해서는 NSLocalizedString을 써야 한다.
Objective-C에서는 매크로였지만, Swift에서는 일급 함수로 다뤄진다.
func NSLocalizedString(
_ key: String,
tableName: String? = nil,
bundle: Bundle = Bundle.main,
value: String = "",
comment: String
) -> String
포매팅이 필요한 문자열은 다음 메소드를 쓴다.
static func localizedStringWithFormat(
_ format: String,
_ arguments: CVarArg...
) -> String
조합해서 다음과 같이 많이 쓴다.
String.localizedStringWithFormat(NSLocalizedString("%@ %d", comment: ...), ...)
이 때 주의할 점은 인자로 주어진 값이 항상 동일하지 않을 수 있다는 것이다.
특정 언어에서는 이 순서가 뒤집할 수 있다.
이럴 때 인자의 순서를 별도로 지정해 줄 수 있다.
인자가 무조건 순서대로 들어올 것이라는 가정은 금지.
이걸 알고나면 왠지 직접 접근하고 싶은 마음이 들수도 있다.
let lang = NSLocale.perferredLanguages().firstObject!
let lprojPath = lange.stringByAppendingPathExtension("lproj")
let filePath = NSBundle.mainBundle().pathForResourece("stopSign", ofType: "png", inDirectory: lprojPath)
하지만 이렇게 하면 유저가 원하는 언어가 없을 때 fallback을 하는 로직이 없어진다.
내부적으로 스마트한 fallback 로직이 있다.
그래서 다음과 같이 쓰는 것을 권장한다.
NSBundle.mainBundle().imageForResource("stopSign")
NSBundle.mainBundle().pathForSoundResource("greeting")
NSBundle.mainBundle().URLForResource("help", withExtension: "pdf")
근데 코드로만 할 수 있는 작업이 여전히 있지 않을까? 복수형 처리라던지.
if numDays == 1 {
daysString = String.localizedStringWithFormat(
NSLocalizedString("%d day remaining",
comment: "Single day remaining")
numDays)
} else {
daysString = String.localizedStringWithFormat(
NSLocalizedString("%d days remaining",
comment: "number of days remaining")
numDays)
}
근데 이것도 언어마다 달라진다.
.stringdict는 근본적으로 프로젝트 안에 있는 plist 파일이다.
localized 리소스기 때문에 lproj 디렉토리 안에 있게 된다.
key가 있고, 이 키를 숫자 인자와 조합해서 다른 값을 찾아올 수 있다.
iOS 9부터 stringdict에 VariableWidth 기능이 추가 됐다.
가용한 width를 기준으로 내용을 변경하는 것
단위는 시스템 폰트의 대문자 M → 왜 이런 애매한 단위를…?
NSLocalizedString을 써서 표준 UIKit control에 넣으면 이를 적용해준다.
macOS에서는 이를 수동으로 적용해야 한다.
let formatted = NSLocalizedString("Welcome", comment: "Welcome the user")
let widthFormattedString = formatted.variantFittingPresentationWidth(20)
Formatting
숫자, 날짜, 시간, 이름등의 포매팅도 지역마다 다를 수 있다.
let pi = String(format: "%.3f", M_PI) // 3.142
// 독일에서는 저걸 3142로 읽는다. 숫자에서 점과 쉼표를 쓰는 방식이 반대기 때문.
이를 위해서는 역시 localizedStringWithFormat을 써야 한다.
let pi = String.localizedStringWithFormat("%.3f", M_PI)
내부적으로 NSNumberFormatter를 쓰는데, 여기도 몇가지 변경 사항이 있다.
날짜도 지역마다 다르다.
let date = String(format: "%d/%d/%d, %d:%d:%d", 6, 12, 2015, 9, 0, 0)
// 6/12/2015, 9:00
// 미국에서는 6월 12일인데, 이탈리아에서는 12월 6일이 된다.
NSDateFormatter를 써서 문제를 해결할 수 있지만, 잘못된 방법으로 사용할 수 있다.
let df = NSDateFormatter()
df.dateFormat = "MM/dd/yyyy, h:mm a"
print(df.stringFromDate(NSDate())) // 06/12/2015, 9:00 AM
제대로 쓰기 위해서는 dateStyle과 timeStyle을 사용해야 한다.
let df = NSDateFormatter()
df.dateStyle = .ShortStyle
df.timeStyle = .ShortStyle
print(df.stringFromDate(NSDate()))
// 06/12/2015, 9:00 AM
// 이탈리아 지역에서는 12/06/2015, 9:00 AM
근데 이것도 커스텀으로 하고 싶은 경우가 있을 수 있다. 그래서 새로운 API를 추가했다.
let df = NSDateFormatter()
df.setLocalizedDateFormatFromTemplate("yyyyMMddjjmmss")
print(df.stringFromDate(NSDate()))
단위
순진한 해결책은 직접 바꿔주는거다.
let weight = String.localizedStringWithFormat(
NSLocalizedString("%d pounds",
comment: "Weight"),
6)
// it.lproj/Localizable.strings
"%d pounds" = "%d chilogrammi"
이 때는 NSMassFormatter를 써야 한다.
let weight = 20.0 // 원하는 단위의 숫자를 지정(여기서는 kg)
let massFormatter = NSMassFormatter()
massFormatter.unitStyle = .Long
let formatted = massFormatter.stringFromKilograms(weight)
iOS 9부터 nameFormatter가 추가되었다.
지역마다 first name과 last name의 위치가 다르다. middle name의 존재여부도 다르다.
이를 위해서 나온게 NSPersonNameComponents와 NSPersonNameComponentsFormatter다.
let components = NSPersonNameComponents()
components.givenName = "Grace"
components.middleName = "Murray"
components.familyName = "Hopper"
let formatter = NSPersonNAmeComponentsFormatter()
formatter.style = .Short
formatter.stringFromPersonNameComponents(components)
지원 스타일
Handling Text(Characters, Case Changes, Searching, Transforms)
Character란 무엇인가?
이모지 하나는 몇개의 Character로 이루어져 있을까?
보기에는 하나로 보여도, 실제로는 여러 Character의 합성물이다.
이모지가 포함된 문자열을 enumerate 하는 과정에서 실수 할 수 있다.
NSString *str = @"test 👨❤️💋👨"
for(int i = 0; i < str.length; i++) {
NSLog(@"%C", [str characterAtIndex: i]);
}
// test ????❤️????
제대로 하는 방법 → 이제 Swift는 알아서 자동으로 해주니까 상관은 없을 듯
let str = @"test 👨❤️💋👨"
str.enumerateSubstringsInRange(str.startIndex..<str.endIndex,
options: .ByComposedCharacterSequences) {
(substring, substringRange, enclosingRange, stop) -> in
print("\\(substring)")
}
Case Change
let str = "istanbul"
print(str.capitalizedString) // 영어권에서는 괜찮지만 다른 지역에서는 문제가 된다.
print(str.localizedCapitalizedString)
print(str.localizedUppercaseString)
print(str.localizedCapitalizedString)
문자열 검색