Mac 앱에서 반복되는 메시지를 통합하려면, 공유되는 의미마다 현지화 키 하나와 Swift 정의 하나를 지정한 다음 해당 메시지가 표시되는 모든 곳에서 사용하세요. 인수, 복수형 규칙, 번역가를 위한 맥락을 함께 유지하세요. 영어 문구만 같다고 충분한 것은 아닙니다.

이 안내서는 SwiftUI와 Interface Builder 윈도우 모두에서 선택 요약을 관리하는 Mac 개발자를 위한 것입니다. 구체적인 작업은 2 items selected in Batch A.와 같은 메시지를 XIB와 Swift 코드에서 별도의 문장 템플릿으로 관리하지 않고 일관되게 표시하는 것입니다. 아래의 모든 개수와 레이블은 예시용입니다.

어떤 반복 메시지를 하나로 묶을지 결정하기

카탈로그를 편집하기 전에 메시지 계약을 정의하세요. 문장의 의미, 허용하는 값, 표시 위치를 정합니다. 이 예시의 계약은 다음과 같습니다.

계약 요소 정의
의미 이름이 지정된 컬렉션에서 현재 선택된 항목의 개수
키 selection.summary
테이블 Localizable
번들 메인 앱 번들
인수 1 Int64 선택 개수
인수 2 Batch A와 같은 리터럴 컬렉션 레이블
변형 선택 개수에 따라 결정되는 복수형
맥락 SwiftUI와 AppKit 레이블에 표시되는 완전한 상태 문장

이 계약을 통합 기준으로 사용하세요. 의미, 인수, 문법적 역할이 일치하면 두 항목이 같은 메시지를 공유할 수 있습니다. 선택 항목 수와 전체 항목 수는 현재 둘 다 2 items로 표시되더라도 별개로 유지해야 합니다. Select라는 버튼에도 별도의 메시지가 필요합니다. 동작을 표현하기 때문입니다.

Apple은 텍스트의 의미를 구분하기 위해 기본값과 별도의 명시적 키를 지원합니다. 따라서 소스의 읽기 쉬운 문구를 유지하면서 메시지 이름을 목적에 맞게 지정할 수 있습니다. 주석은 맥락을 제공하고 키는 항목을 식별합니다. String의 명시적 키 이니셜라이저.

테이블과 번들도 조회 계약의 일부로 다루세요. 다른 테이블에 있는 키는 별도의 조회 대상입니다. Apple은 table 또는 tableName 매개변수로 테이블을 선택하는 방법을 설명하고, 현지화할 텍스트에는 LocalizedStringResource를 전달하도록 권장합니다. 이 예시에서는 도우미와 카탈로그를 앱 타깃에 두고 메인 번들을 사용합니다. 앱의 텍스트를 번역에 맞게 준비하기.

추출과 런타임 서식 지정의 경계 이해하기

추출은 Xcode 빌드 중에 이루어집니다. 지원되는 현지화 표현식을 찾아 카탈로그의 소스 텍스트와 동기화합니다. 런타임 현지화는 앱이 메시지를 확인하고 실제 값을 대체할 때 이루어집니다. 카탈로그에서 항목을 찾았다는 것은 추출이 해당 항목을 발견했다는 뜻일 뿐, 모든 컨트롤에 올바르게 표시된다는 뜻은 아닙니다.

Interface Builder에는 자체 리소스 작업 흐름이 있습니다. Apple은 스토리보드 또는 XIB와 연결되는 카탈로그를 설명하며, Swift 소스는 일반적으로 Localizable 테이블에 반영됩니다. 문서화된 작업 흐름에는 화면에 보이는 문구가 같다는 이유만으로 메시지를 병합하는 리소스 간 별칭이 설명되어 있지 않습니다. 지원되는 실용적인 방법은 인터페이스가 로드된 후 Swift에서 공유 현지화 메시지를 할당하는 것입니다. 이는 Apple의 문서화된 API를 바탕으로 한 구현입니다. 문자열 카탈로그 알아보기.

SwiftUI는 현지화 키 리터럴과 일반 문자열 값도 구분합니다. 예를 들어 Text("Summary")는 현지화를 요청하지만, String 변수를 전달하면 조회 없이 해당 값이 표시됩니다. 런타임 문자열을 LocalizedStringKey로 감싸면 조회가 요청되지만, 그 문자열에 가능한 값이 추출되었다는 증거로 혼동해서는 안 됩니다. LocalizedStringKey.

따라서 아래 설계에서는 추출 가능한 리터럴을 도우미 하나 안에 둡니다. 개수와 레이블을 LocalizedStringResource에 담은 뒤 완성된 문자열이 필요한 곳에서 해당 리소스를 확인합니다. Apple은 이 리소스 형식으로 현지화 조회를 지연할 수 있다고 설명합니다. macOS 13 이상에서 사용할 수 있으며, 이 예시의 최소 배포 대상도 macOS 13입니다. LocalizedStringResource.

Swift에서 공유 메시지를 한 번만 정의하기

SwiftUI를 사용하는 macOS 앱 프로젝트를 만들고, 배포 대상을 macOS 13 이상으로 설정한 다음 Localizable.xcstrings를 앱 타깃에 추가하세요. 현재 Xcode 문서에 따르면 생성 경로는 File > New > File from Template입니다. string을 검색해 String Catalog를 선택하세요. 문자열 카탈로그를 사용해 텍스트 현지화 및 변형하기.

생성된 앱 진입점을 아래의 전체 프로그램으로 바꾸세요. 타깃에 @main 선언이 하나만 남도록 원래 앱 진입점을 제거하세요. SwiftUI 윈도우는 XIB와 독립적으로 실행됩니다. 다음 섹션에서 기존 Interface Builder 레이블을 적용할 때 포함된 AppKit 하위 클래스를 사용합니다.

import AppKit
import Foundation
import SwiftUI

enum SelectionMessages {
    static func summary(
        count: Int64,
        collectionLabel: String
    ) -> LocalizedStringResource {
        LocalizedStringResource(
            "selection.summary",
            defaultValue: "\(count) items selected in \(collectionLabel).",
            table: "Localizable",
            comment: "Complete selection-status sentence used in SwiftUI and an AppKit label. Argument 1 is the selected-item count; argument 2 is a literal collection label. Pluralize by argument 1."
        )
    }
}

struct SelectionDemoView: View {
    private let counts: [Int64] = [0, 1, 2]
    private let collectionLabel = "Batch A"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            ForEach(counts, id: \.self) { count in
                Text(String(localized: SelectionMessages.summary(
                    count: count,
                    collectionLabel: collectionLabel
                )))
            }
        }
        .padding(24)
        .frame(minWidth: 360, minHeight: 160)
    }
}

final class SelectionSummaryField: NSTextField {
    override func awakeFromNib() {
        super.awakeFromNib()
        // Synthetic initial state for the Interface Builder example.
        updateSelection(count: 2, collectionLabel: "Batch A")
    }

    func updateSelection(count: Int64, collectionLabel: String) {
        stringValue = String(localized: SelectionMessages.summary(
            count: count,
            collectionLabel: collectionLabel
        ))
    }
}

@main
struct SelectionDemoApp: App {
    var body: some Scene {
        WindowGroup {
            SelectionDemoView()
        }
    }
}

정적 키는 메시지를 식별하고, 삽입된 기본값은 소스 문장과 런타임 인수를 제공합니다. 이니셜라이저는 테이블, 번들, 번역가용 주석도 받습니다. 기본 번들은 메인 번들입니다. LocalizedStringResource의 명시적 키 이니셜라이저.

레이블 Batch A는 문장에 삽입되는 예시용 콘텐츠입니다. 별도의 메시지 키가 아닙니다. 이 구분을 통해 컬렉션 레이블을 주변 인터페이스 문구와 분리할 수 있습니다. 실제 앱에서 사전 정의된 현지화 컬렉션 이름을 사용한다면, 여기에 전달하기 전에 해당 이름을 자체 정의를 통해 현지화하세요.

문장을 개수, items selected in, 레이블처럼 각각 현지화된 조각으로 조합하지 마세요. 완전한 문장을 계약에 포함하면 현지화 담당자가 문구와 인수 순서를 함께 조정할 수 있습니다.

Interface Builder 레이블을 같은 정의로 연결하기

기존 XIB 또는 스토리보드에서는 요약에 정적 NSTextField 레이블을 사용하세요. 사용자 지정 클래스를 SelectionSummaryField로 설정하고 모듈에는 앱 타깃을 지정하세요. 이 레이블은 이제 공유 Swift 정의에서 텍스트를 받으므로 디자인 시 문자열 값을 지우세요.

인터페이스가 로드되면 하위 클래스가 super.awakeFromNib()을 호출하고 예시용 초기 요약을 할당합니다. Apple은 awakeFromNib()를 아웃렛 및 액션 연결이 설정된 뒤 호출되는 후크로 문서화하며, 슈퍼클래스 구현을 호출하도록 요구합니다. awakeFromNib().

실제 윈도우에서는 선택이 바뀔 때마다 연결된 레이블에 updateSelection(count:collectionLabel:)을 호출하고, SwiftUI에 제공하는 것과 동일한 모델 값을 사용하세요. 예시용 초기 할당을 윈도우의 실제 초기화 경로로 바꾸세요. 로드 중 한 번만 할당하면 이후 선택 변경을 반영할 수 없습니다.

AppKit 어댑터는 stringValue을 설정합니다. 이는 NSTextField의 텍스트를 할당하는 문서화된 방법입니다. 이렇게 하면 레이아웃은 Interface Builder에 두면서 메시지 관리는 Swift로 옮길 수 있습니다. 완전한 문장이 들어가도록 충분한 너비를 확보하거나 줄 바꿈을 허용하세요. NSTextField.

관련 없는 정적 인터페이스 텍스트는 기존 리소스 작업 흐름에 두세요. 이번 이전의 대상은 공유 메시지 하나이므로 모든 레이블을 Swift로 옮길 필요는 없습니다. 또한 바인딩이나 다른 컨트롤러 할당이 이후에 요약을 이전 문구로 덮어쓰지 않는지 확인하세요.

인수를 바꾸지 않고 복수형 변형 구성하기

Product > Build를 선택하고 Localizable.xcstrings를 연 다음 selection.summary을 찾으세요. 항목을 Control-클릭하고 Vary by Plural을 선택한 뒤, Xcode가 변형할 인수를 묻는다면 숫자 개수 인수를 선택하세요. Apple은 보간된 메시지에 대해 이 작업 흐름을 문서화하고 있습니다. 문자열 카탈로그를 사용해 텍스트 현지화 및 변형하기.

영어 소스 현지화에서는 단수형이 1 item selected in Batch A.가 되고 다른 변형이 2 items selected in Batch A.와 같은 문장이 되도록 하세요. Xcode가 제공한 두 인수 플레이스홀더를 모두 유지하세요. 플레이스홀더를 예시 개수나 레이블로 대체하지 말고 주변 문구를 편집하세요.

예시 입력 복수형 구성 후 예상되는 영어 결과
개수 0, 레이블 Batch A 0 items selected in Batch A.
개수 1, 레이블 Batch A 1 item selected in Batch A.
개수 2, 레이블 Batch A 2 items selected in Batch A.
개수 2, 레이블 Batch B 2 items selected in Batch B.

이는 예시용 승인 기준이며 측정된 결과가 아닙니다. 복수형을 구성하기 전에는 기본 소스 문장에 모든 개수에 대해 items이 포함되어 있습니다. 추출은 보간된 메시지를 보존하지만 문법을 별도로 수정하지는 않습니다.

마지막 행에서 레이블이 바뀌는 것은 진단에 도움이 됩니다. 표시가 계속 Batch A라면 변형에 예시 값이 하드코딩되었거나 호출자가 오래된 상태를 전달하고 있을 수 있습니다. 레이블이 사라진다면 도우미의 시그니처를 바꾸기 전에 변형의 플레이스홀더를 확인하세요.

기존 항목을 제거하기 전에 모든 표시 경로 확인하기

SwiftUI 윈도우와 실제 Interface Builder 윈도우를 각각 테스트하세요. 양쪽에 같은 개수와 레이블을 사용하세요. 초기 로드, 이후 선택 변경, Batch A Extended와 같은 더 긴 예시 레이블을 확인하세요. 문구가 일치하고 문장을 읽기 쉬운지 확인하세요.

지원되는 각 현지화에 대해 Product > Scheme > Edit Scheme을 선택하고 Run을 선택한 다음 Options를 열어 App Language를 설정하세요. 앱을 다시 실행하고 두 표시 경로를 모두 확인하세요. Apple은 시스템 전체의 언어를 바꾸지 않고 이러한 스킴 설정으로 현지화를 테스트하는 방법을 문서화하고 있습니다. 앱 실행 시 현지화 테스트하기.

결과가 잘못되었을 때는 다음 순서로 확인하세요.

  1. 항목이 없음: 카탈로그가 앱 타깃에 포함되어 있는지 확인하고 관련 스킴을 빌드하세요. Use Compiler to Extract Swift Strings가 활성화되어 있는지 확인하세요.
  2. 소스 문구가 예상치 않게 표시됨: 읽기 쉬운 대체 텍스트가 성공한 것이라고 판단하기 전에 키, 테이블, 번들, 번역을 확인하세요.
  3. Interface Builder 레이블만 다름: 사용자 지정 클래스, 모듈, 로딩 경로, 이후 할당을 확인하세요.
  4. 이후 값만 다름: 두 인터페이스가 업데이트된 모델 값을 받는지 확인하세요.
  5. 복수형 문구가 잘못됨: 변형 대상으로 선택한 숫자 인수와 해당하는 모든 변형을 확인하세요.

Apple은 컴파일러 추출 설정을 문서화하고, 소스에서 더 이상 발견되지 않는 번역 항목이 Stale로 표시될 수 있다고 설명합니다. 이 상태는 정리 신호일 뿐 리소스가 사용되지 않는다는 증거는 아닙니다. 항목과 번역을 삭제하기 전에 이전 인터페이스 할당과 Swift 호출부를 검토하세요. 문자열 카탈로그 알아보기.

두 인터페이스가 하나의 정의에서 동일한 맥락의 메시지를 가져오고, 인수가 그대로 유지되며, 이전 문장 템플릿을 사용하는 호출부가 더 이상 없으면 통합이 완료된 것입니다. 향후 문구 변경에도 두 윈도우가 공유하는 의미가 유지되도록 계약과 승인 기준을 도우미 옆에 두세요.

출처