The Source Revision Graph
The hash is the review contract
A translation review is meaningless unless you know what was reviewed. “German is approved” is a sentence with a missing clause: approved for which English sentence, with which speaker and context, under which normalization rules?
LocalizeShip answers that by turning the source into a revision hash and binding everything to it.

What a source revision contains
SourceRevision stores:
public StableEntryId EntryId;
public SourceRevisionHash RevisionHash;
public string RawText;
public string NormalizedText;
public string Speaker;
public string ContextDescription;
public string SceneOrFixture;
public int NormalizationVersion;
public long TimestampTicks;
public List<string> Placeholders;
The fields that feed the hash are normalized text, speaker and context description, plus the normalization policy version.
How the hash is computed
NormalizationPolicy.ComputeHash builds a payload and runs SHA-256:
string payload = $"{Version}|{normalizedText}|{context ?? string.Empty}";
using (var sha = SHA256.Create())
{
byte[] bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(payload));
var sb = new StringBuilder(16);
for (int i = 0; i < 8; i++)
{
sb.Append(bytes[i].ToString("x2"));
}
return sb.ToString();
}
And the constructor passes the combined context:
string combinedContext = $"{Speaker}:{ContextDescription}";
string hash = policy.ComputeHash(NormalizedText, combinedContext);
So the effective payload is:
<policyVersion>|<normalizedText>|<speaker>:<contextDescription>
The digest is truncated to the first 8 bytes and rendered as 16 lowercase hex characters. It is a compact collaboration key, not a cryptographic signature.
The prompt-level description says “SHA-256 of the normalized text”. The implementation is more precise: SHA-256 over a versioned payload that includes normalized text, speaker and context. That is intentional — changing the speaker or context is a real review change even when the words are identical.
What changes the hash
| Change | Hash changes? |
|---|---|
| Source text changes | Yes |
| Speaker changes | Yes |
| Context description changes | Yes |
| Normalization policy version changes | Yes |
| Whitespace at the ends (with default policy) | No |
| CRLF vs LF (with default policy) | No |
| Reordering entries | No |
| Moving an entry without changing its key | No |
| Translation text changes | No — that is the translation hash |
| Voice clip changes | No |
| Screenshot changes | No |
One current revision, plus event history
The name “graph” is aspirational. In 1.0.0 the workspace stores one current SourceRevision per entry:
public SourceRevision GetSourceRevision(StableEntryId entryId)
{
for (int i = 0; i < SourceRevisions.Count; i++)
{
if (SourceRevisions[i].EntryId == entryId) return SourceRevisions[i];
}
return null;
}
When the hash changes, RevisionTracker.ProcessSourceEntry removes the old revision and adds the new one. Previous raw text is not retained in the revision list.
What is retained is the approval event history. Each ApprovalEvent stores:
- the entry and locale;
- the bound source and translation revision hashes at the time of the event;
- the old and new review state;
- reviewer label, role, rationale and timestamp.
That gives you an auditable chain of decisions (Approved → Stale, with both hashes) even though the full source text history is not stored. If you need complete text archaeology, keep the project under version control as well.
What the revision binds
Approval
ApprovalRecord.ApplyReview stores ApprovedSourceHash and ApprovedTranslationHash when the new state is Approved:
if (newState == ReviewState.Approved)
{
ApprovedSourceHash = currentSourceHash;
ApprovedTranslationHash = currentTransHash;
}
CheckSourceChange compares the approved source hash with the new one and, if it differs and the record is currently approved, moves it to Stale and appends a system event.
Voice takes
VoiceEntry stores both BoundSourceHash and BoundTranslationHash. RecheckStatus evaluates, in order:
- no audio path →
Missing; - source hash differs →
StaleSourceRevision; - translation hash differs →
StaleTranslationRevision; - duration difference greater than 2.0 s →
DurationMismatch; - otherwise →
BoundCurrent.
Subtitle cues
SubtitleCue stores BoundSourceHash. The impact engine flags a cue when its bound hash differs from the current source hash.
Merge base
HandoffEntry.BaseSourceHash is the source hash at export time. The merge planner compares it with the current source hash to detect StaleSourceConflict.
Translation revisions
Translations get their own hash through RevisionTracker.ComputeTranslationHash:
public TranslationRevisionHash ComputeTranslationHash(string translationText)
{
string norm = _policy.Normalize(translationText);
string hash = _policy.ComputeHash(norm, "translation");
return new TranslationRevisionHash(hash);
}
The context string is the literal "translation". The same normalization policy applies.
In the Release Board’s manual approval path,
ApprovedTranslationHashis passed asdefault, not as a computed translation hash. This means voice entries that depend on the translation hash can reportStaleTranslationRevisionafter a manual approval. Compute and pass the translation hash in your integration if you rely on that binding.
Worked example
Source: "Collect {count} herbs."
Speaker: "Herbalist"
Context: "Quest objective"
Policy: v1
Payload: "1|Collect {count} herbs.|Herbalist:Quest objective"
Hash: "9f2c1a7b3d4e5f60" (illustrative)
A writer changes it to "Collect {count} rare herbs.":
Payload: "1|Collect {count} rare herbs.|Herbalist:Quest objective"
Hash: "41ab90ce77d2015b" (illustrative)
The approval bound to 9f2c… is no longer valid. It moves to Stale, the history gets an event, the impact engine reports the translation, and any voice or subtitle bound to the old hash is flagged.
Integration advice
- Always process source lines through
RevisionTrackerrather than writingSourceRevisionobjects directly, if you want invalidation to run. - Pass speaker and context from your provider when you have them. Empty context is valid but produces a weaker review contract.
- Do not change
NormalizationPolicy.Versioncasually. It is part of the hash and will invalidate every existing approval when new revisions are processed. - Store the workspace asset in version control. The hashes and approval records are the evidence.
Next: Review States.