한 차례 릴리스 스프린트에서 개발 브랜치에 UI 문구가 십여 개 추가됐습니다. 아카이브 작업은 정상적으로 끝났지만, 테스터가 언어를 전환하자 영어로 대체되거나 텍스트가 비어 있는 버튼이 나타났습니다. 문제는 컴파일러가 아니라 파이프라인이 현지화 완전성을 실패 가능한 검사 항목으로 다루지 않았다는 데 있었습니다. 상시 운영되는 클라우드 Mac에서는 사람이 모든 화면을 일일이 확인할 때까지 기다리기보다, 빌드할 때마다 시작 전에 .xcstrings를 직접 검사하는 방식이 가장 효과적입니다.
게이트가 차단할 항목부터 정의하기
String Catalog는 JSON 파일이지만, “파싱할 수 있다”는 것이 곧 “출시할 수 있다”는 뜻은 아닙니다. 실용적인 게이트라면 최소한 세 가지 문제를 검사해야 합니다. 프로젝트에서 요구하는 대상 언어가 없는 경우, 번역 상태가 translated가 아닌 경우, 최종 문자열이 비어 있는 경우입니다. 복수형이나 기기별 차이와 같은 내용은 variations 아래에도 들어가므로, 최상위 stringUnit만 읽으면 문제를 놓칠 수 있습니다.
승인 범위는 빌드 노드의 임시 설정에 남겨 두지 말고 저장소에 기록해야 합니다. 프로젝트의 원본 언어가 영어이고 현재 간체 중국어, 일본어, 프랑스어가 필수라면 대상 언어를 스크립트 인수로 전달할 수 있습니다.
| 검사 항목 | 실패 조건 | 처리 방식 |
|---|---|---|
| 대상 언어 | localizations에 해당 키가 없음 |
빌드 차단 |
| 번역 상태 | 하나 이상의 리프 노드가 translated가 아님 |
해당 키와 언어 반환 |
| 문자열 내용 | value가 비어 있거나 공백만 있음 |
빌드 차단 |
| 번역 제외 | shouldTranslate가 false |
명시적으로 건너뜀 |
이 게이트는 Catalog의 현지화 리소스가 완전한지만 판단합니다. UI 잘림, 동적 매개변수, 의미의 정확성을 확인하는 수동 검수를 대체하지는 않습니다.
외부 의존성 없는 검사기 작성하기
스크립트를 Scripts/check_xcstrings.py로 저장합니다. 이 스크립트는 macOS에 기본으로 포함된 Python 실행 환경만 사용합니다. 팀에서 별도의 Python 경로를 고정해 사용한다면 대화형 shell과 CI의 PATH가 달라지는 문제를 방지하도록 빌드 설정에 경로를 명시해야 합니다.
#!/usr/bin/env python3
import argparse
import json
import pathlib
import sys
def units(node):
found = []
if isinstance(node, dict):
unit = node.get("stringUnit")
if isinstance(unit, dict):
found.append(unit)
for key, value in node.items():
if key != "stringUnit":
found.extend(units(value))
elif isinstance(node, list):
for value in node:
found.extend(units(value))
return found
parser = argparse.ArgumentParser()
parser.add_argument("root")
parser.add_argument("--locale", action="append", required=True)
args = parser.parse_args()
failures = []
files = sorted(pathlib.Path(args.root).rglob("*.xcstrings"))
if not files:
failures.append("no .xcstrings files found")
for path in files:
with path.open(encoding="utf-8") as handle:
catalog = json.load(handle)
for key, entry in catalog.get("strings", {}).items():
if entry.get("shouldTranslate") is False:
continue
localizations = entry.get("localizations", {})
for locale in args.locale:
localized = localizations.get(locale)
if localized is None:
failures.append(f"{path}:{key}:{locale}:missing locale")
continue
leaves = units(localized)
if not leaves:
failures.append(f"{path}:{key}:{locale}:missing stringUnit")
continue
for index, unit in enumerate(leaves):
state = unit.get("state")
value = unit.get("value", "")
if state != "translated":
failures.append(
f"{path}:{key}:{locale}:{index}:state={state}"
)
if not value.strip():
failures.append(
f"{path}:{key}:{locale}:{index}:empty value"
)
for failure in failures:
print(failure, file=sys.stderr)
sys.exit(1 if failures else 0)
저장소 루트 디렉터리에서 실행합니다.
python3 Scripts/check_xcstrings.py . \
--locale zh-Hans \
--locale ja \
--locale fr
스크립트가 0이 아닌 상태로 종료되면 일반적인 작업 오케스트레이터에서 이후 단계를 중단할 수 있습니다. 출력에는 파일, 문자열 키, 언어, 리프 노드 번호가 포함되므로 수정 담당자가 전체 빌드 로그부터 살펴볼 필요가 없습니다.
컴파일 전에 클라우드 Mac 파이프라인에 연결하기
검사는 의존성 해결이 끝난 뒤, xcodebuild build 또는 archive를 실행하기 전에 배치하는 것이 좋습니다. 의존성 준비 단계가 끝나면 저장소의 모든 내용이 디스크에 준비됐는지 확인할 수 있습니다. 동시에 검사를 앞당기면 명백히 기준을 충족하지 못한 커밋에 컴파일, 테스트, 아카이브 작업을 수행하는 낭비를 피할 수 있습니다.
set -euo pipefail
mkdir -p build/reports
python3 Scripts/check_xcstrings.py Sources \
--locale zh-Hans \
--locale ja \
--locale fr \
2> build/reports/localization-errors.txt
xcodebuild \
-project App.xcodeproj \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
build
set -o pipefail은 중요합니다. 나중에 출력을 tee로 전달할 경우 이 설정이 없으면 파이프라인이 마지막 명령의 성공만 확인해 검사기의 실패 상태를 놓칠 수 있습니다. 클라우드 Mac에서 이전 실행이 남긴 파일을 이번 결과로 잘못 인식하지 않도록 보고서 디렉터리도 작업 시작 시 다시 생성해야 합니다.
내보낸 결과로 수동 검수 단계 보완하기
.xcstrings를 직접 파싱하는 방식은 자동 게이트에 적합하지만, 출시 전에는 현지화 패키지도 내보내 개발 언어, 주석, 컨텍스트가 모두 갖춰졌는지 확인해야 합니다. 대상 언어별로 다음 명령을 실행할 수 있습니다.
rm -rf build/xcloc
mkdir -p build/xcloc
xcodebuild -exportLocalizations \
-project App.xcodeproj \
-localizationPath build/xcloc \
-exportLanguage zh-Hans
xcodebuild -exportLocalizations \
-project App.xcodeproj \
-localizationPath build/xcloc \
-exportLanguage ja
내보내기에 실패했을 때 곧바로 재시도해 기존 정보를 덮어쓰지 마세요. 먼저 전체 표준 오류를 보존한 뒤 scheme, 프로젝트 경로, 대상 언어 식별자를 확인해야 합니다. 언어 코드는 Catalog의 키와 일치해야 합니다. 예를 들어 zh-Hans와 zh-Hant는 서로 독립된 두 대상이므로 모호한 접두사로 대체할 수 없습니다.
내보낸 .xcloc는 현지화 담당자의 표본 검수에 적합하지만, 빌드 산출물을 메인 브랜치에 커밋하는 것은 권장하지 않습니다. 저장소에는 원본 .xcstrings, 검사 스크립트, 대상 언어 목록을 보관하고 내보내기 디렉터리는 개별 작업의 산출물로 취급해야 합니다.
자주 발생하는 오탐을 처리하고 검수 완료하기
첫 번째 오탐은 번역할 필요가 없는 기술 문자열에서 발생합니다. 스크립트에서 키 접두사를 기준으로 임의로 제외하지 말고, Catalog에 shouldTranslate: false를 명시해 규칙이 리소스 자체를 따르도록 해야 합니다. 두 번째는 복수형 분기에서 one만 번역하고 other를 누락하는 경우입니다. 모든 stringUnit을 재귀적으로 읽으면 누락된 위치를 정확히 찾을 수 있습니다.
세 번째는 개발자가 언어를 추가한 뒤 CI 인수를 갱신하지 않는 경우입니다. 대상 언어 목록은 프로젝트 스크립트의 배열이나 파이프라인 변수처럼 단일 소스에서만 관리하는 것이 좋습니다. 여러 작업에서 목록을 각각 따로 관리하지 마세요. 네 번째는 공백, 줄바꿈, 플레이스홀더를 유효한 번역으로 잘못 판단하는 경우입니다. 현재 스크립트는 공백만 있는 값을 거부합니다. %@, %d 같은 형식 매개변수는 원본 문자열과 대상 문자열의 플레이스홀더 집합을 비교하는 검사를 추가할 수도 있습니다. 다만 이스케이프와 위치 지정 매개변수를 먼저 처리한 뒤 차단 조건으로 활성화해야 합니다.
병합 전에는 다음 순서로 검수합니다.
- 대상 언어 하나를 의도적으로 삭제하고 작업이 실패하는지 확인합니다.
- 번역 하나를 공백으로 바꾸고 보고서가 정확한 키를 가리키는지 확인합니다.
- 완료되지 않은 복수형 분기를 만들고 재귀 검사가 이를 찾는지 확인합니다.
- 리소스를 복원한 뒤 다시 실행해 이전 보고서가 결과에 영향을 주지 않는지 확인합니다.
- 마지막으로 Release 구성으로 빌드해 게이트와 정식 작업이 동일하게 체크아웃된 내용을 사용하는지 검증합니다.
이렇게 구축한 것은 일회성 번역 검사가 아니라 클라우드 Mac에서 안정적으로 재현할 수 있는 엔지니어링 제약 조건입니다. 리소스가 불완전하면 조기에 실패시키고, 리소스를 수정한 뒤에만 컴파일, 테스트, 아카이브 단계로 진행합니다.
자주 묻는 질문
String Catalog 검사는 CI의 어느 단계에 배치해야 하나요?
의존성 준비가 끝난 뒤, 컴파일과 아카이브를 시작하기 전에 배치하는 것이 좋습니다. 오류가 있으면 비용이 큰 빌드 작업 전에 즉시 중단할 수 있습니다.
state 값이 translated인지 확인하는 것만으로 충분한가요?
아닙니다. 대상 언어의 존재 여부와 빈 값도 확인하고, 복수형 variations 안에 포함된 모든 stringUnit을 재귀적으로 검사해야 합니다.
다음 빌드를 클라우드 Mac에서 실행하세요.
세 가지 Apple Silicon 구성을 비교하고, 현재 워크플로에 맞는 리전을 5개의 해외 노드 중에서 선택하세요.