Localization Sheets
One sheet holds your game's text in every language: rows are keys, columns are locales. A localization sheet is an ordinary SheetForge sheet in every way that matters — it imports, validates, exports, pushes and round-trips exactly like a data sheet — and when the Unity Localization package (com.unity.localization) is installed, every completed import also fills the package's own StringTable collections from it. Your runtime then consumes standard LocalizedString references while the sheet stays the single source of truth.
This page is about your game's text. The 10-language UI of the product itself is a separate subject — see Localization.
The sheet shape (@loc)
A sheet becomes a localization sheet by carrying an @loc marker row. Like @overlap and @style, it sits anywhere above the data; its cell in each column names that column's locale code:
@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 | 취소 | |- The
RecordIdkey column is required — each row's key value (ui.ok) is the localization key, and the tab name is the StringTable collection name: one tab = one collection. - A locale column is a string column whose
@loccell carries the code (en,ko,pt-BR— any identifier-like tag; SheetForge validates the spelling shape, not the code's existence, and two columns whose codes differ only by case are rejected). Write them asstring?: an untranslated cell is then a coverage gap, not an import error — see Coverage below. - The first locale column is the source locale. Its text is what a data sheet's reference cell previews inline, and what auto-minting writes.
- Two reserved optional columns, matched by name:
smart(a boolean — marks the entry as a Unity Localization Smart String) andcomment(a string — synchronized into the entry's comment metadata). When present, the sheet is the truth for those bits; when absent, the bridge leaves the corresponding table metadata untouched. A reserved column may not also carry a locale code. - At least one locale code is required, and a sheet cannot be both an enum sheet and a localization sheet (
@enum+@locis a conflict error, reported once).
Everything else is an ordinary sheet: staging and Ctrl+Z, structure editing, @style grouping, xlsx and Google round-trips, Push, and the web app all treat it as a normal table. What changes is the output: a localization tab emits no generated record class, no Database ScriptableObject and no Addressables address. Instead it feeds two things — the key constants and the bridge.
Referencing text from data sheets (LocRef@Tab)
A data sheet points at a localization entry with a LocRef reference column:
@name | codeName | displayName
@type | RecordId | LocRef@Strings
@desc | unique key | shown in UI
| item.sword | item.sword.nameLocRef@Strings behaves exactly like the built-in references you already know (RecordId@Tab):
- Integrity-validated at import — a key that does not exist in the
Stringstab is a structured error with a nearest-match suggestion; a typo dies at import, not at runtime. The target must be a localization sheet (LocRefTargetNotLocalizationSheetotherwise), andLocRefwithout@Targetis rejected with the correct spelling suggested. - Full reference rails — the searchable key picker, key-rename propagation (renaming a key rewrites every referencing cell in the same batch), graph edges on the record canvas, exported dropdown rules, and orphan detection all work, in the editor and the web app alike.
- Composes like any reference —
List<LocRef@Strings>and the optional formLocRef@Strings?(an empty cell is an empty reference) both work. - The cell shows the text, not just the key. A
LocRefcell previews the entry's source-locale text inline, so a sheet full of keys still reads as sentences. The record canvas does the same — reference rows carry the source text inline, and a truncated value always holds its full text in the tooltip. - Typing into an empty cell mints the entry. Type the source text into an empty
LocRefcell and SheetForge stages, as one gesture and one undo step: a new key in the target localization sheet (suggested from the record and field names — rename it freely later, propagation keeps every reference intact), the text you typed as its source-locale value, and the reference in the cell you typed into. Both hosts.
What lands in your code
Codegen emits the field as LocRef — a plain serializable struct (the target table and key) that lives in the SheetForge runtime assembly and compiles whether or not the Unity Localization package is installed; generated code and baked ScriptableObjects never contain a package type. With the package installed, one extension call bridges into it:
var text = definition.displayName.ToLocalizedString(); // UnityEngine.Localization.LocalizedStringToLocalizedString() exists only when the package is present (a version-define, SHEETFORGE_LOCALIZATION, turns the extension layer on — the same mechanism SHEETFORGE_ADDRESSABLES uses). Without the package the field is still a well-formed table/key pair you can consume yourself.
What an import generates
Alongside the regular outputs, an import writes one SheetForgeLocalizationKeys.cs for the whole project — a static class per localization tab (StringsKeys, …) holding one public const string per key, so game code can say StringsKeys.ui_ok instead of a bare "ui.ok" and get compile-time safety plus IDE completion.
- Member names are the keys sanitized to C# identifiers (characters outside ASCII letters, digits and
_become_; collisions get a deterministic numeric suffix). Keep keys ASCII if you want usable constants — a fully non-ASCII key sanitizes into an underscore soup. - Like the sheet-defined enum file, the constants file is a project-level output and always lands in the settings' generated-code folder — the same assembly note in Capabilities & Limits applies.
- A localization tab with no rows keeps an empty class, so clearing a sheet does not break code that references the type.
The Unity Localization bridge
With the package installed, SheetForge maintains one StringTable collection per localization tab — keys, values, and the smart/comment bits when those columns exist.
- When it runs: automatically, at the moment an import completes — the same standing Export and Push have as exits — plus a manual re-synchronize action for running it on demand.
- Direction: one way, sheet → tables. The sheet is canonical; the tables are output.
- Locales: a sheet locale with no matching
Localeasset in the project is created automatically and named in the report. A locale that exists only in the project is left untouched and reported as not covered by the sheet. - Key renames keep scene references alive. Renaming a key in the Data Studio goes through the same rename machinery every reference uses, and the bridge renames the table entry in place, preserving its internal id — a
LocalizedStringin a scene or prefab binds to that id, so it survives the rename. The honest boundary: a rename made outside the Studio — editing the sheet source directly in Google Sheets or Excel — is indistinguishable from deleting one key and adding another. The bridge will create a fresh entry (new id) and treat the old one as an orphan; scene references to the old entry keep pointing at the orphan. Rename keys in the Studio. - Metadata the sheet does not model is always preserved. Comments (when there is no
commentcolumn), excluded flags, and any other table metadata pass through every sync untouched.
External edits are asked about, never merged silently
The bridge stamps the tables it owns and remembers a fingerprint of the last sync. If a table changed since — someone edited it in the Localization Tables window, or pulled into it with Unity's own Google Sheets extension — the next sync stops and asks: overwrite from the sheet, or abort with a difference report. There is no silent merge and no silent overwrite. If you want a two-cockpit workflow, route the other cockpit through the sheet instead — that is what the translation export is for.
Orphan keys are preserved by default
A key that exists in the table but no longer in the sheet is an orphan: it is kept, listed in an orphan report, and removable through an explicit cleanup action (all at once or per key). A settings toggle switches to delete on sync if you want the table to mirror the sheet exactly. Nothing is ever destroyed as a side effect.
Without the package
The Unity Localization package is optional. Without it:
- Localization sheets are complete sheets — authoring, validation, coverage, Export, Push, xlsx, the web app, key constants and
LocReffields all work in full. - The one thing that waits is the StringTable sync exit, which shows an install notice (once per session) and stops — the same guidance pattern as Addressables, and likewise never installed programmatically.
- Every assembly and every line of generated code compiles without the package. Supported package version: 1.5 or newer.
Migrating existing tables into a sheet
Already using Unity Localization? A reverse importer turns an existing StringTable collection into a localization sheet, written straight into your import source and imported automatically — the same path sheet creation takes. Review and edit it afterwards like any other sheet. If the active source cannot be written from the editor, the file lands next to your export output with a note telling you to move it. When the bridge later syncs that sheet back into the same collection, entry ids are inherited by key-name matching — existing LocalizedString references in scenes and prefabs survive the migration unbroken.
Translation workflows
Coverage: untranslated cells are reported, not refused
An empty locale cell is not an error — the import reports per-locale coverage (how many keys each locale has translated, and which are missing), and every exit stays open. Text arrives incrementally; the sheet never blocks on an unfinished translation.
The locale lens
Working in one language at a time? A locale lens toggles which locale columns are visible. It is display metadata in the @style family — it never touches the import fingerprint, codegen, or any output. Both hosts.
Translation export and partial re-import
To hand a language off to a translator, export a translation workbook: pick the locales and get an xlsx of key + source text + comment + a status column, where an entry whose source text changed since the last export is marked outdated. When the file comes back, re-import it as a partial merge: rows are matched by key and only the locale columns are written — structure, other locales and everything else in the sheet stay untouched. Both hosts. One honest asymmetry: the status memory lives in a machine-local file next to the Unity project, so a workbook exported from the browser always says new in its status column; the "translated against older source text" notice on re-import compares against the source text the file itself carries, so it works in both hosts.
XLIFF and pseudo-locales
SheetForge deliberately does not re-implement XLIFF or pseudo-localization — the tables the bridge fills are ordinary Unity Localization tables, so the package's own XLIFF export/import and pseudo-locale tooling work on them as on any project. Remember the one-way authority, though: tool output written into the tables is an external edit the next sync will ask about. To keep translations in the source of truth, bring them back through the sheet (the translation workbook above) rather than into the tables.
Two related boundaries, stated honestly: the bridge covers string tables only — asset tables are an acknowledged backlog item — and SheetForge ships no language-specific Smart Format helpers (Korean grammatical particles, for example). The smart column marks entries as Smart Strings; formatters beyond what the package provides are yours to write through the package's own extension points.
In the web app
A localization sheet is an ordinary sheet in the browser: authoring, validation, coverage, the LocRef picker with inline source text, minting, the locale lens, and the translation workbook all work at web.sheetforge.workers.dev. The StringTable sync is the Unity editor's job — the browser has no Unity project to write tables into, and does not pretend to.
Related pages
- Sheet Syntax — the
@locmarker andLocRefin the notation reference - Localization — the product UI's own 10 languages
- Data Studio — the authoring window where minting and renames happen
- SheetForge Web — the browser companion
- Capabilities & Limits — the localization-sheet boundaries in the honest list