현지화 시트
한 장의 시트가 모든 언어로 된 게임 텍스트를 담는다: 행은 키, 컬럼은 로케일이다. 현지화 시트는 중요한 모든 면에서 평범한 SheetForge 시트다 — 데이터 시트와 정확히 똑같이 임포트, 검증, export, push, 라운드트립되며 — Unity Localization 패키지(com.unity.localization)가 설치되어 있으면, 완료된 모든 임포트가 그로부터 패키지 자체의 StringTable 컬렉션도 채운다. 그러면 사용자의 런타임은 표준 LocalizedString 참조를 소비하는 동안에도 시트가 단일 진실 공급원으로 남는다.
이 페이지는 게임 텍스트를 다룬다. 제품 자체의 10개 언어 UI는 별도의 주제다 — 현지화 참고.
시트 형태 (@loc)
시트는 @loc 마커 행을 가짐으로써 현지화 시트가 된다. @overlap, @style과 마찬가지로 데이터 위 어디에나 위치할 수 있으며, 각 컬럼의 셀이 그 컬럼의 로케일 코드를 이름 붙인다:
@loc | | en | ko | |
@name | codeName | en | ko | smart | comment
@type | RecordId | string? | string? | bool? | string?
@desc | key | source text | Korean | |
| ui.ok | OK | 확인 | false | Confirm button
| ui.cancel | Cancel | 취소 | |RecordId키 컬럼이 필수다 — 각 행의 키 값(ui.ok)이 현지화 키이며, 탭 이름이 StringTable 컬렉션 이름이다: 탭 하나 = 컬렉션 하나.- 로케일 컬럼은 문자열 컬럼이며, 그
@loc셀이 코드(en,ko,pt-BR— 식별자 형태의 태그라면 무엇이든)를 담는다; SheetForge는 코드의 존재 여부가 아니라 철자 형태를 검증하며, 대소문자만 다른 두 코드는 거부된다.string?으로 작성하라: 그러면 번역되지 않은 셀은 임포트 에러가 아니라 커버리지 공백이 된다 — 아래 커버리지 참고. - 첫 번째 로케일 컬럼이 소스 로케일이다. 그 텍스트가 데이터 시트의 참조 셀이 인라인으로 미리 보여주는 것이며, 자동 민팅이 써넣는 것이다.
- 이름으로 매칭되는 두 개의 예약된 선택 컬럼:
smart(불리언 — 항목을 Unity Localization Smart String으로 표시)와comment(문자열 — 항목의 comment 메타데이터로 동기화됨). 이 컬럼이 있으면 시트가 그 부분의 진실이 되고, 없으면 브리지는 대응하는 테이블 메타데이터를 건드리지 않은 채 둔다. 예약된 컬럼은 로케일 코드를 동시에 가질 수 없다. - 최소 하나의 로케일 코드가 필요하며, 한 시트가 enum 시트이면서 동시에 현지화 시트일 수는 없다(
@enum+@loc은 충돌 에러이며 한 번 보고된다).
그 밖의 모든 것은 평범한 시트다: 스테이징과 Ctrl+Z, 구조 편집, @style 그룹화, xlsx와 Google 라운드트립, Push, 웹 앱 모두 이를 평범한 테이블로 취급한다. 달라지는 것은 출력이다: 현지화 탭은 생성된 레코드 클래스도, Database ScriptableObject도, Addressables 주소도 내보내지 않는다. 대신 두 가지를 공급한다 — 키 상수와 브리지.
데이터 시트에서 텍스트 참조하기 (LocRef@Tab)
데이터 시트는 LocRef 참조 컬럼으로 현지화 항목을 가리킨다:
@name | codeName | displayName
@type | RecordId | LocRef@Strings
@desc | unique key | shown in UI
| item.sword | item.sword.nameLocRef@Strings는 이미 알고 있는 내장 참조(RecordId@Tab)와 정확히 똑같이 동작한다:
- 임포트 시점에 무결성 검증된다 —
Strings탭에 존재하지 않는 키는 최근접 일치 제안이 딸린 구조화된 에러가 된다; 오타는 런타임이 아니라 임포트에서 죽는다. 대상은 현지화 시트여야 하며(그렇지 않으면LocRefTargetNotLocalizationSheet),@Target없는LocRef는 올바른 표기 제안과 함께 거부된다. - 완전한 참조 레일 — 검색 가능한 키 피커, 키 이름 변경 전파(키 이름을 변경하면 같은 배치에서 참조하는 모든 셀이 재작성된다), 레코드 캔버스의 그래프 엣지, export되는 드롭다운 규칙, 고아 탐지 모두 에디터와 웹 앱에서 동일하게 동작한다.
- 다른 참조와 똑같이 조합된다 —
List<LocRef@Strings>와 선택 형태LocRef@Strings?(빈 셀은 빈 참조) 둘 다 동작한다. - 셀은 키만이 아니라 텍스트를 보여준다.
LocRef셀은 그 항목의 소스 로케일 텍스트를 인라인으로 미리 보여주므로, 키로 가득한 시트도 여전히 문장처럼 읽힌다. 레코드 캔버스도 같다 — 참조 줄이 원문을 곁들여 보여주고, 잘린 값은 언제나 툴팁이 전문을 든다. - 빈 셀에 입력하면 항목이 민팅된다. 빈
LocRef셀에 소스 텍스트를 입력하면 SheetForge는 하나의 제스처이자 하나의 undo 단계로 다음을 스테이징한다: 대상 현지화 시트의 새 키(레코드와 필드 이름에서 제안됨 — 나중에 자유롭게 이름을 바꿔도 전파가 모든 참조를 온전하게 유지한다), 입력한 텍스트를 그 소스 로케일 값으로, 그리고 입력한 셀의 참조를. 두 호스트 모두.
코드에 무엇이 남는가
코드젠은 필드를 **LocRef**로 내보낸다 — SheetForge 런타임 어셈블리에 있는, 대상 테이블과 키를 담은 평범한 직렬화 가능 구조체이며 Unity Localization 패키지 설치 여부와 무관하게 컴파일된다; 생성된 코드와 베이크된 ScriptableObject는 결코 패키지 타입을 담지 않는다. 패키지가 설치되어 있으면, 확장 호출 하나가 그 안으로 다리를 놓는다:
var text = definition.displayName.ToLocalizedString(); // UnityEngine.Localization.LocalizedStringToLocalizedString()은 패키지가 있을 때만 존재한다(버전 정의 SHEETFORGE_LOCALIZATION이 확장 레이어를 켠다 — SHEETFORGE_ADDRESSABLES가 쓰는 것과 같은 메커니즘). 패키지가 없어도 그 필드는 여전히 사용자가 직접 소비할 수 있는, 형식이 온전한 테이블/키 쌍이다.
임포트가 생성하는 것
일반적인 출력들과 함께, 임포트는 프로젝트 전체를 위한 SheetForgeLocalizationKeys.cs 하나를 작성한다 — 현지화 탭별 정적 클래스(StringsKeys, …)가 키마다 하나의 public const string을 담아서, 게임 코드가 맨 "ui.ok" 대신 StringsKeys.ui_ok라고 쓰고 컴파일 타임 안전성과 IDE 자동완성을 얻을 수 있게 한다.
- 멤버 이름은 C# 식별자로 정제된 키다(ASCII 문자·숫자·
_밖의 문자는_가 된다; 충돌에는 결정적인 숫자 접미사가 붙는다). 사용 가능한 상수를 원한다면 키를 ASCII로 유지하라 — 완전히 비-ASCII인 키는 밑줄 범벅으로 정제된다. - 시트에서 정의된 enum 파일과 마찬가지로, 상수 파일은 프로젝트 수준 출력이며 항상 설정의 생성 코드 폴더에 놓인다 — 기능 및 제한의 동일한 어셈블리 주의사항이 적용된다.
- 행이 없는 현지화 탭은 빈 클래스를 유지하므로, 시트를 비워도 그 타입을 참조하는 코드가 깨지지 않는다.
Unity Localization 브리지
패키지가 설치되어 있으면, SheetForge는 현지화 탭당 하나의 StringTable 컬렉션을 유지한다 — 키, 값, 그리고 그 컬럼들이 존재할 때의 smart/comment 정보까지.
- 언제 실행되는가: 자동으로, 임포트가 완료되는 순간 — Export와 Push가 출구로서 갖는 것과 동일한 위상이다 — 여기에 원할 때 실행할 수 있는 수동 재동기화 액션이 더해진다.
- 방향: 단방향, 시트 → 테이블. 시트가 정본이고, 테이블은 출력이다.
- 로케일: 프로젝트에 대응하는
Locale에셋이 없는 시트 로케일은 자동으로 생성되고 리포트에 이름이 실린다. 프로젝트에만 존재하는 로케일은 건드리지 않고 남겨진 채 시트에 커버되지 않음으로 보고된다. - 키 이름 변경은 씬 참조를 살려둔다. 데이터 스튜디오에서 키 이름을 변경하면 모든 참조가 사용하는 것과 동일한 이름 변경 메커니즘을 거치며, 브리지는 테이블 항목을 제자리에서, 내부 id를 보존한 채 이름 변경한다 — 씬이나 프리팹의
LocalizedString은 그 id에 결속되므로 이름 변경에도 살아남는다. 정직한 경계: 스튜디오 밖에서의 이름 변경 — Google Sheets나 Excel에서 시트 소스를 직접 편집하는 것 — 은 하나의 키를 삭제하고 다른 키를 추가하는 것과 구별할 수 없다. 브리지는 새 항목(새 id)을 만들고 옛 항목을 고아로 취급한다; 옛 항목을 향한 씬 참조는 여전히 그 고아를 가리킨다. 키 이름은 스튜디오에서 변경하라. - 시트가 모델링하지 않는 메타데이터는 항상 보존된다. 댓글(
comment컬럼이 없을 때), 제외 플래그, 그 밖의 테이블 메타데이터는 모든 동기화를 건드리지 않은 채 통과한다.
외부 편집은 물어볼 뿐, 결코 조용히 병합하지 않는다
브리지는 자신이 소유한 테이블에 스탬프를 찍고 마지막 동기화의 핑거프린트를 기억한다. 그 이후 테이블이 바뀌었다면 — 누군가 Localization Tables 창에서 편집했거나, Unity 자체 Google Sheets 확장으로 끌어왔다면 — 다음 동기화는 멈추고 물어본다: 시트로부터 덮어쓸지, 차이 리포트와 함께 중단할지. 조용한 병합도, 조용한 덮어쓰기도 없다. 두 조종석 워크플로우를 원한다면, 다른 조종석을 시트를 경유하도록 하라 — 번역 export가 바로 그것을 위해 존재한다.
고아 키는 기본적으로 보존된다
테이블에는 존재하지만 더 이상 시트에는 없는 키는 고아다: 유지되고, 고아 리포트에 나열되며, 명시적인 정리 액션(한 번에 모두 또는 키별로)을 통해서만 제거할 수 있다. 테이블이 시트를 정확히 미러링하기를 원한다면 설정 토글로 동기화 시 삭제로 전환할 수 있다. 부작용으로 파괴되는 것은 결코 없다.
패키지 없이
Unity Localization 패키지는 선택 사항이다. 없다면:
- 현지화 시트는 완전한 시트다 — 작성, 검증, 커버리지, Export, Push, xlsx, 웹 앱, 키 상수,
LocRef필드 모두 온전히 동작한다. - 기다리는 것은 오직 StringTable 동기화 출구뿐이며, 설치 안내(세션당 한 번)를 표시하고 멈춘다 — Addressables와 동일한 안내 패턴이며, 마찬가지로 결코 프로그래밍 방식으로 설치되지 않는다.
- 모든 어셈블리와 생성 코드의 모든 줄은 패키지 없이도 컴파일된다. 지원 패키지 버전: 1.5 이상.
기존 테이블을 시트로 마이그레이션하기
이미 Unity Localization을 쓰고 있는가? 역방향 임포터는 기존 StringTable 컬렉션을 현지화 시트로 바꿔 임포트 소스에 곧바로 쓰고 자동으로 임포트한다 — 시트 만들기가 밟는 것과 같은 경로다. 검토와 수정은 그 뒤에 다른 시트와 똑같이 하면 된다. 활성 소스에 에디터에서 쓸 수 없다면, 파일은 Export 산출물 옆에 떨어지고 옮기라는 안내가 함께 붙는다. 이후 브리지가 그 시트를 같은 컬렉션으로 다시 동기화할 때, 항목 id는 키 이름 매칭으로 상속된다 — 씬과 프리팹의 기존 LocalizedString 참조는 마이그레이션을 깨지지 않고 살아남는다.
번역 워크플로우
커버리지: 번역되지 않은 셀은 거부가 아니라 보고된다
빈 로케일 셀은 에러가 아니다 — 임포트는 로케일별 커버리지(각 로케일이 번역한 키 개수와 누락된 키)를 보고하며, 모든 출구는 열린 채로 남는다. 텍스트는 점진적으로 도착한다; 시트는 미완성 번역 때문에 결코 막히지 않는다.
로케일 렌즈
한 번에 한 언어로 작업하고 있는가? 로케일 렌즈는 어느 로케일 컬럼이 보이는지를 토글한다. 이는 @style 계열의 표시 메타데이터다 — 임포트 핑거프린트, 코드젠, 어떤 출력에도 결코 영향을 주지 않는다. 두 호스트 모두.
번역 export와 부분 재임포트
한 언어를 번역가에게 넘기려면, 번역 워크북을 export한다: 로케일을 선택하면 키 + 소스 텍스트 + 댓글 + 상태 컬럼으로 이루어진 xlsx를 얻으며, 마지막 export 이후 소스 텍스트가 바뀐 항목은 오래됨으로 표시된다. 파일이 돌아오면 부분 병합으로 재임포트한다: 행은 키로 매칭되고 로케일 컬럼만 기록된다 — 구조, 다른 로케일, 시트의 그 밖의 모든 것은 건드리지 않은 채 남는다. 두 호스트 모두. 정직한 비대칭 하나: 상태 기억은 Unity 프로젝트 옆의 기계 로컬 파일에 살기 때문에, 브라우저에서 export한 워크북의 상태 컬럼은 언제나 new다. 재임포트할 때의 "옛 소스 텍스트 기준 번역" 안내는 파일이 실어 온 소스 텍스트와 비교하므로 두 호스트 모두에서 동작한다.
XLIFF와 pseudo-locale
SheetForge는 XLIFF나 pseudo-localization을 의도적으로 재구현하지 않는다 — 브리지가 채우는 테이블은 평범한 Unity Localization 테이블이므로, 패키지 자체의 XLIFF export/import와 pseudo-locale 도구가 다른 어떤 프로젝트에서와 마찬가지로 그 위에서 동작한다. 다만 단방향 권위를 기억하라: 도구가 테이블 안으로 쓴 출력은 다음 동기화가 물어볼 외부 편집이 된다. 번역을 정본 안에 유지하려면, 테이블이 아니라 시트를 경유해(위의 번역 워크북) 되돌려라.
관련된 두 가지 경계를 정직하게 밝힌다: 브리지는 문자열 테이블만 다룬다 — 에셋 테이블은 인정된 백로그 항목이다 — 그리고 SheetForge는 언어별 Smart Format 헬퍼를 제공하지 않는다(예를 들어 한국어 조사). smart 컬럼은 항목을 Smart String으로 표시한다; 패키지가 제공하는 것을 넘어서는 포매터는 패키지 자체의 확장 지점을 통해 사용자가 직접 작성해야 한다.
웹 앱에서
현지화 시트는 브라우저에서도 평범한 시트다: 작성, 검증, 커버리지, 인라인 소스 텍스트가 딸린 LocRef 피커, 민팅, 로케일 렌즈, 번역 워크북 모두 web.sheetforge.workers.dev에서 동작한다. StringTable 동기화는 Unity 에디터의 몫이다 — 브라우저에는 써넣을 Unity 프로젝트가 없으며, 그런 척하지도 않는다.
관련 페이지
- 시트 문법 — 표기 참조에서의
@loc마커와LocRef - 현지화 — 제품 UI 자체의 10개 언어
- 데이터 스튜디오 — 민팅과 이름 변경이 일어나는 작성 창
- SheetForge Web — 브라우저 컴패니언
- 기능 및 제한 — 정직한 목록 속 현지화 시트의 경계