iOS Localization

Xcode 15 신기능 스트링 카탈로그

woozoobro
8 min readDec 15, 2023

안녕하세요 Steve 입니다!

12월의 마케팅 목표를 최소 1일 1다운로드 되게 하기! 로 계획을 세우게 됐어요. 만다린 프로젝트는 현재 국내 앱스토어에서만 다운이 가능합니다. 미국 앱스토어에서도 다운이 가능하다면 더 많이 다운로드 수를 늘릴 수 있지 않을까 싶더라구요.

그래서 드디어!!
Localization을 적용해보기로 했습니다.

애플에서 발표한 WWDC를 참고하시는 것도 좋을 것 같구

KWDC에서도 발표했던 내용도 있더라구요. Global로 배포를 준비하시는 분들은 요렇게 두 개 영상이 도움이 될 것 같아요.

저는 예시랑 함께 직접 코드단으로 보는 게 좋아서!

이 영상을 많이 참고했습니다:)

실제 구현

먼저 프로젝트에 Localization 추가 해줬구요!
스트링 카탈로그 두두둥장

새로운 파일을 만들어주시고 String으로 검색해주세요. Xcode 15부터 추가된 기능입니다. String Catalog라는 파일을 만들 수 있어요. (옛날 버전들에도 적용 가능👴🏻. 마이그레이션도 쉬움.) 파일 이름은 그대로 만들어도 돼요.

자 이제 어떻게 스트링 카탈로그에 Key가 추가가 되냐~

앱을 실행하면 자동으로 String을 인식합니다! (런타임 시에 String 값을 알 수 있다면)

소스 코드로 볼까요

import SwiftUI

struct FindProject: View {
@State private var text: String = ""
let placeholder: String = "이게 바로 플레이스 홀더"

var body: some View {
VStack {
Text("얘는 알아서 되는데 말이죠").font(.largeTitle)

TextField(placeholder, text: $text)
.roundedStyle()

projectFilters
}
}
}

private
extension FindProject {
var projectFilters: some View {
HStack {
ForEach(ProjectFilter.allCases) { filter in
Button {
print("Do Something")
} label: {
Text(filter.rawValue)
.roundedStyle()
}
}
.frame(maxWidth: .infinity)
}
.padding(.horizontal)
}
}

enum ProjectFilter: String, Identifiable, CaseIterable {
var id: Self { self }

case project = "프로젝트"
case study = "스터디"
case meetup = "밋업"
}

extension View {
func roundedStyle() -> some View {
padding().frame(maxWidth: .infinity)
.background(Color.gray.clipShape(RoundedRectangle(cornerRadius: 10).stroke()))
}
}

#Preview {
FindProject()
.environment(\.locale, .init(identifier: "ko"))
}

이대로 실행을 해보면 Text 뷰에 다이렉트로 박힌 String은 스트링 카탈로그가 인식을 합니다. 이게 어떻게 가능하냐!

Text initializer 중 LocalizedStringKey로 받는 게 가능해요

그래서 현재 처럼 코드가 작성되어 있다면 Text(“얘는 알아서 되는데 말이죠") 는 자동으로 스트링 카탈로그에 추가가 됩니다. 그치만 변수로 빼준 String 값은 인식을 못해요. 지금 같은 경우엔 placeholder 상수가 되겠네요. 왜 인식을 못하냐! 이 String 값이 디버그 프린트문에 쓰이는 건지 실제 유저에게 보이는 String인지 판단을 할 수 없어서 그렇다고 해요.

그래서 지금같은 경우엔 String의 타입을 바꿔줘야 합니다. LocalizedStringKey 혹은 LocalizedStringResource 타입으로 변경이 필요해요.

쉽게 생각해본다면 SubView로 빼줄 때는 타입 자체가 LocalizedString이 되야 한다고 명심하고 있으면 될 것 같습니다. LocalizedStringResource는 iOS 16부터 지원 되는 새로 추가된 표현이고 이 타입은 init할 때 파라미터로 다른 내용들도 받을 수 있습니다. (번역가를 위한 코멘트 같은 걸 추가한다던가 Bundle의 정보를 넣는다던가!)

placeholder의 타입을 바꿔볼게요!
대부분 LocalizedStringKey 혹은 LocalizedStringResource를 둘 다 사용 가능한데

SwiftUI 컴포넌트 중에 LocalizedStringResource 타입을 사용하지 못하는 경우가 있어요. 이건 TextField의 init을 보시면 알 수 있습니다.

!!

매번 init할 때마다 LocalizedStringKey가 뭘까 궁금했는데 정체가 밝혀졌네요.

enum rawValue로 쓰고 있던 Text 뷰는 어떻게 하지…

Text에 들어가는 값을 rawValue 그 자체로 쓰고 있던 경우엔 rawValue를 LocalizedStringKey나 LocalizedStringResource 타입으로 만들어줘도 되고

rawValue랑 얽힌 로직이 많다면…
(예를 들면 selected 와 바인딩된 String 비교 로직 같은게 버튼에 들어가 있다!)

새로 프로퍼티 파주는게 맘 편합니다.

요렇게 말이죠

enum ProjectFilter: String, Identifiable, CaseIterable {
var id: Self { self }

case project = "프로젝트"
case study = "스터디"
case meetup = "밋업"

/// 추가해줘야함
var title: LocalizedStringResource {
switch self {
case .project: return "프로젝트"
case .study: return "스터디"
case .meetup: return "밋업"
}
}
}

기존 메소드가 String을 반환하고 있었다면

LocalizedStringResource 타입으로 반환되게 바꿔주기!
stringLiteral 안으로 기존의 String 넣어주면 됩니다.
(위에서 selectedStudyType은 String? 타입이에요)

아.. 별짓 다 해봤는데 카탈로그로 추가가 안된다

ForEach의 경우 LocalizedString Resource 이렇게 했는데 추가 안된다!
그냥 String Catalog에 + 버튼 누르고 텍스트 직접 입력해서 키 추가해주면 됩니다.

테스트는 스킴 edit 해주면 편하게 할 수 있고

scheme에서 App Language를 바꿔주면 시뮬에서 적용됨

프리뷰를 쓸 수 있는 환경이라면

#Preview {
FindProject()
.environment(\.locale, .init(identifier: "ko"))
}

버그가 있긴 하네요

Localizable 스트링 카탈로그를 삭제해도 Preview에선 번역한 내용이 안 사라집니다. 키가 어딘가에 박혀있는 듯

--

--