본문으로 건너뛰기
SheetForge

현지화 시트

한 장의 시트가 모든 언어로 된 게임 텍스트를 담는다: 행은 키, 컬럼은 로케일이다. 현지화 시트는 중요한 모든 면에서 평범한 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.name

LocRef@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.LocalizedString

ToLocalizedString()은 패키지가 있을 때만 존재한다(버전 정의 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 프로젝트가 없으며, 그런 척하지도 않는다.

관련 페이지