Beginner-Friendly Tutorials Jul 15, 2026

Your First Localization String Freeze Receipt Before October Next Fest PO Import - One Evening - 2026

One-evening localization string freeze receipt for October Next Fest—locale manifest, Weblate commit pin, PO export proof, store parity, player smoke, and localization_freeze_ok.

By GamineAI Team

Your First Localization String Freeze Receipt Before October Next Fest PO Import - One Evening - 2026

Pixel-art hero for localization string freeze receipt before October Next Fest PO import

August and September 2026 are when micro-studios keep saying “strings are frozen” while Weblate components still accept commits, translators push “one more polish pass,” and October Next Fest demo builds ship without the languages listed on the Steam page. The store claims Simplified Chinese, Japanese, and Korean. The executable boots English-only. The refund thread quotes your Supported languages row back at you.

This tutorial is deliberately scheduled in mid-July 2026 because a freeze receipt is useless if you invent it during upload week. You need a repeatable evening ritual that locks which locales are in scope, pins the TMS commit that owns the freeze, proves a clean PO export, checks store-versus-game language parity, smoke-tests an installed build, and writes localization_freeze_ok into the project BUILD_RECEIPT only when every gate is green.

Direct answer: spend one evening creating localization_freeze_receipt_v1.json plus a locked locale_manifest_v1.json. Run gates L1–L6. Pin the Weblate (or Crowdin) commit hash at freeze time. Require fuzzy-zero PO export for every fest locale. Prove store Supported languages match the in-game selector. Boot an installed build on English plus two fest locales. Set BUILD_RECEIPT.localization_freeze_ok: true only after the receipt is filed.

This is not a Ren'Py selector wiring guide, not an eighteen-tool QA listicle, and not a merge-conflict fire drill. Those siblings stay linked below. This URL owns the engine-agnostic string freeze receipt before October PO import reopenings.

Why this matters now

Localization freezes fail in 2026 for boring, avoidable reasons that show up hardest under Next Fest upload pressure.

  1. Verbal freeze is not evidence. Slack announcements do not survive a weekend. Reviewers and partners need a JSON receipt with a timestamp and a commit hash.
  2. Weblate still accepts pushes after “freeze.” If write access stays open, someone will merge a string that never reaches the build you uploaded.
  3. Store language lists and game language lists diverge. Steam Supported languages is a marketing claim; the Preferences menu is a shipping claim. Players treat both as the product.
  4. PO import after a soft freeze reopens the blast radius. Importing stale or fuzzy PO into Unity String Tables, Godot CSV, or Ren'Py tl/ without a freeze gate turns “localization progress” into “locale lottery.”
  5. October queues punish late discoveries. Upload week is the wrong week to learn Simplified Chinese UI strings still point at English fallbacks.

Official references worth keeping open while you run the evening: Weblate documentation, Steamworks localization / supported languages, and your engine’s localization docs (Unity Localization package, Godot translation files, Ren'Py translation chapter). Use the vendor pages as truth for APIs; use this receipt as truth for your release decision.

Early in the first half of this article, pair the freeze with sibling lanes already on GamineAI: the 18 free localization QA tools list for validators, the Weblate merge-conflict help when export is blocked, and the Ren'Py language selector evening when the freeze is green but the Preferences screen is not.

Who this one-evening ritual is for

Reader What you should leave with
First-time localizer A plain checklist that turns “we froze strings” into files you can find next week
Solo developer A receipt that stops post-freeze PO imports from silently shipping
Producer / lead L1–L6 decision table with fail actions and owners
Localization contractor A documented freeze commit they can invoice against without guessing the scope
Steam upload owner A localization_freeze_ok latch before depot promotion

Time: 90–120 minutes the first evening; 35–50 minutes once the scripts and folder layout exist.

Prerequisites:

  • A translation management system (Weblate preferred in examples; Crowdin or Lokalise works if you can pin a revision).
  • A machine-readable list of locales you intend to claim on the store and in-game (BCP-47 or steam language codes—pick one system and stick to it).
  • Ability to export PO / XLIFF / CSV for each fest locale.
  • Access to Steamworks (or itch) store language configuration for parity checks.
  • An installed development or release-candidate build that can switch languages (Editor-only smoke is not enough for L5).
  • A project BUILD_RECEIPT.json habit, or willingness to start with Your First BUILD_RECEIPT JSON and Upload Log.

Format ladder - where this article fits

Format Job Boundary
This tutorial Lock freeze evidence and set localization_freeze_ok Owns L1–L6 and the freeze receipt
18 free localization QA tools Curate validators and TMS guards Owns tool discovery, not the freeze latch
Ren'Py language selector evening Wire Preferences after PO import Owns engine selector L-gates, consumes freeze when present
Weblate merge-conflict help Unblock PO export when conflicts block freeze Owns the fix path when L3 cannot start
14 free Weblate string-freeze PO tools Tool list for export stacks Owns bookmarks, not the receipt
Steam short description 300-char CJK help Fix storefield overflow after export Owns store copy length, not freeze scope
Forward #4 / #8 / #11 in this cluster Challenge, weekly jq, Friday capstone Consume this receipt later; do not reinvent L1–L6

Non-repetition note: the Ren'Py evening owns selector wiring after PO. The listicle owns tools. Help owns merge conflicts and empty String Tables. This URL owns the cross-engine freeze receipt and localization_freeze_ok latch before October Next Fest PO import reopenings.

Gates L1–L6

Do not mark the freeze green because translators said they were done. Each gate answers a distinct release question.

Gate Question Required evidence Fail action
L1 Which locales are in scope for this freeze? Locked locale_manifest_v1.json with codes, store names, engine names, and owners Stop. Decide the list before exporting anything.
L2 What TMS revision owns the freeze? Weblate commit / revision hash, freeze UTC timestamp, writer who closed write access Stop. Do not export from a floating main without a pin.
L3 Is the PO (or XLIFF/CSV) export clean for fest locales? Export manifest with fuzzy_count: 0 and translated percent threshold per locale Resolve fuzzies or remove the locale from L1 before continuing.
L4 Do store Supported languages match the in-game language list? Side-by-side table: Steam / itch claim vs Preferences / language API Fix store or game list; never ship a one-sided claim.
L5 Does an installed build smoke EN + two fest locales? Player log or screenshot set proving boot and UI language switch Fix missing tables, cache, or selector before setting freeze ok.
L6 Is evidence filed and cross-linked into BUILD_RECEIPT? localization_freeze_receipt_v1.json + localization_freeze_ok: true Block promotion and any post-freeze PO import automation.

A gate may be PASS, FAIL, or NOT_RUN. Never use “partial pass.” If an exception is required, keep the gate FAIL with an owner, expiry, and explicit promotion decision so the waiver cannot look like a green latch.

Evening overview - four short blocks

Block Minutes Output
1 — lock the locale manifest 20 locale_manifest_v1.json
2 — pin TMS revision and close writes 25 freeze timestamp + commit hash
3 — export proof + store parity 30 clean PO + L4 table
4 — installed smoke + BUILD_RECEIPT 35–45 L5 logs + localization_freeze_ok

Create an evidence folder once and reuse it every freeze:

release-evidence/
  localization/
    2026-09-12-october-fest-freeze/
      locale_manifest_v1.json
      localization_freeze_receipt_v1.json
      weblate_export_manifest.json
      store_language_parity.md
      smoke/
        en-main-menu.png
        ja-main-menu.png
        zh-CN-main-menu.png
        player-smoke.log

Block 1 - lock locale_manifest_v1.json (L1)

Beginners often start by exporting whatever Weblate has. Start by writing the list of languages you are willing to defend on October upload day.

{
  "schema": "locale_manifest_v1",
  "project": "example-october-demo",
  "freeze_label": "october-next-fest-2026",
  "locked_utc": null,
  "locales": [
    {
      "id": "en",
      "steam_language": "english",
      "engine_code": "en",
      "store_claim": true,
      "in_game_required": true,
      "owner": "design-lead"
    },
    {
      "id": "zh-Hans",
      "steam_language": "schinese",
      "engine_code": "zh_CN",
      "store_claim": true,
      "in_game_required": true,
      "owner": "lqa-contractor"
    },
    {
      "id": "ja",
      "steam_language": "japanese",
      "engine_code": "ja",
      "store_claim": true,
      "in_game_required": true,
      "owner": "lqa-contractor"
    },
    {
      "id": "ko",
      "steam_language": "koreana",
      "engine_code": "ko",
      "store_claim": true,
      "in_game_required": true,
      "owner": "lqa-contractor"
    }
  ],
  "out_of_scope_notes": [
    "fr and de deferred to post-fest full release",
    "do not add store claims for out-of-scope locales"
  ]
}

Beginner rule: if a locale is not in this file, it is not frozen, not exported for this receipt, and not claimed on the store for this build.

Developer rule: map Steam language identifiers separately from engine codes. Ren'Py chinese, Unity zh-Hans, and Steam schinese are the same player language with three different spellings. Ambiguity here creates L4 failures later.

L1 passes when the manifest is committed (or stored as a reviewed artifact) and every in_game_required: true locale has an owner. Set locked_utc only after L2 closes writes—until then keep it null so you do not claim a lock you have not made.

Block 2 - pin Weblate commit and close write access (L2)

A freeze without a revision pin is a mood. Document the exact TMS state.

{
  "tms": "weblate",
  "project_slug": "example-october-demo",
  "components": [
    "game-ui",
    "game-dialogue",
    "store-copy"
  ],
  "revision": "a1b2c3d4e5f678901234567890abcdef12345678",
  "revision_url": "https://weblate.example.com/projects/example-october-demo/#commits",
  "freeze_utc": "2026-09-12T18:30:00Z",
  "write_access_closed": true,
  "closed_by": "release-owner",
  "reopen_policy": "exception ticket + new freeze receipt only"
}

Practical steps:

  1. Snapshot or tag the Weblate repository revision for every component listed in L1.
  2. Disable translator write access or switch to read-only until after the October build ships (or until a numbered exception ticket reopens scope).
  3. Record who closed access and the UTC timestamp.
  4. Copy the revision into both the freeze receipt and any export automation config so CI cannot pull “latest” quietly.

If merge conflicts block the snapshot, stop and run the Weblate merge-conflict help before inventing a fake L2 hash. L2 fails closed.

Also update locale_manifest_v1.json:

{
  "locked_utc": "2026-09-12T18:30:00Z",
  "tms_revision": "a1b2c3d4e5f678901234567890abcdef12345678"
}

L2 passes when revision, timestamp, and write_access_closed: true are present and reviewable.

Block 3 - PO export proof and store parity (L3 + L4)

L3 - clean export for every fest locale

Export from the pinned revision only. Do not export from a working tree that drifted after L2.

{
  "export_utc": "2026-09-12T18:45:00Z",
  "source_revision": "a1b2c3d4e5f678901234567890abcdef12345678",
  "format": "po",
  "locales": [
    {
      "id": "zh-Hans",
      "file": "exports/zh_CN.po",
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "fuzzy_count": 0,
      "untranslated_count": 0,
      "translated_percent": 100
    },
    {
      "id": "ja",
      "file": "exports/ja.po",
      "sha256": "c0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1",
      "fuzzy_count": 0,
      "untranslated_count": 0,
      "translated_percent": 100
    },
    {
      "id": "ko",
      "file": "exports/ko.po",
      "sha256": "d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2",
      "fuzzy_count": 0,
      "untranslated_count": 0,
      "translated_percent": 100
    }
  ]
}

Beginner rule: any fuzzy_count > 0 on a fest locale fails L3. Resolve in the TMS; do not “quick-edit” fuzzy msgids in the game repo.

Developer rule: hash the export files. If CI later imports a different bit pattern under the same filename, the freeze receipt must fail a verify script instead of silently accepting drift.

Threshold policy for incomplete locales: either remove the locale from L1 store claims, or keep L3 red. Do not leave a 92% Korean file in the freeze as if it were complete.

Helper tooling lives in the 14 free Weblate string-freeze PO tools resource and the 18 free localization QA listicle. This tutorial only requires that your export is attributable to L2’s revision.

L4 - store versus in-game language parity

Build a short table and keep it next to the receipt:

Locale ID Steam Supported languages In-game Preferences / API Parity
en english en PASS
zh-Hans schinese zh_CN PASS
ja japanese ja PASS
ko koreana ko PASS
fr (not claimed) (not listed) PASS

Common failure modes:

  • Steam claims schinese but Preferences only lists English.
  • Preferences lists French while Steam does not claim it (players ask where French went).
  • Engine uses zh_CN while store screenshots show traditional Chinese characters from a misplaced export.

L4 passes when every store_claim: true locale appears in-game and every in-game fest locale appears on the store claim for this build. For store field length traps after CJK export, use Steam short description 300-char CJK help as a separate fix lane—not a substitute for L4.

Block 4 - installed build smoke and BUILD_RECEIPT (L5 + L6)

L5 - installed player smoke

Import the pinned export into your engine path after L3 passes:

  • Unity Localization / String Tables or Addressables string groups
  • Godot .po / .csv translation files
  • Ren'Py tl/<lang>/ (then follow the selector evening if Preferences are empty)

Build an installed package. Editor Play Mode is a developer diagnostic only.

Minimum smoke matrix:

Check EN Locale A Locale B
Boot to main menu without traceback required required required
UI strings not English fallback (except intentionally untranslated debug) required required required
Language switch updates UI without crash required required required
Screenshot filed in smoke/ required required required

Choose Locale A / B from in_game_required: true fest locales (usually ja and zh-Hans for October East Asia play).

Capture a short player log snippet:

[loc] active_locale=ja
[loc] source_revision=a1b2c3d4e5f678901234567890abcdef12345678
[loc] missing_key_count=0
[loc] smoke=main_menu_ok

If Unity String Tables appear empty after import, use Unity String Tables empty Addressables help as the failure-first sibling. Keep L5 red until the installed build proves otherwise.

L6 - file the freeze receipt and crosswalk BUILD_RECEIPT

Write localization_freeze_receipt_v1.json:

{
  "schema": "localization_freeze_receipt_v1",
  "freeze_label": "october-next-fest-2026",
  "created_utc": "2026-09-12T19:20:00Z",
  "locale_manifest": "release-evidence/localization/2026-09-12-october-fest-freeze/locale_manifest_v1.json",
  "tms_revision": "a1b2c3d4e5f678901234567890abcdef12345678",
  "write_access_closed": true,
  "export_manifest": "release-evidence/localization/2026-09-12-october-fest-freeze/weblate_export_manifest.json",
  "store_parity_doc": "release-evidence/localization/2026-09-12-october-fest-freeze/store_language_parity.md",
  "smoke_dir": "release-evidence/localization/2026-09-12-october-fest-freeze/smoke",
  "gates": {
    "L1": "PASS",
    "L2": "PASS",
    "L3": "PASS",
    "L4": "PASS",
    "L5": "PASS",
    "L6": "PASS"
  },
  "localization_freeze_ok": true,
  "post_freeze_po_import_allowed": false,
  "notes": "Any new string requires exception ticket + new freeze receipt."
}

Crosswalk the project root receipt:

{
  "localization_freeze_ok": true,
  "localization_freeze_receipt": "release-evidence/localization/2026-09-12-october-fest-freeze/localization_freeze_receipt_v1.json",
  "localization_freeze_revision": "a1b2c3d4e5f678901234567890abcdef12345678"
}

L6 passes only when the receipt exists on disk, gate values are not NOT_RUN, and the BUILD_RECEIPT pointers resolve. Promotion scripts should refuse October upload when localization_freeze_ok is missing or false.

verify_localization_freeze.sh sketch

Keep the verifier boring. A short bash sketch is enough to fail CI on silent drift:

#!/usr/bin/env bash
set -euo pipefail

RECEIPT="${1:-release-evidence/localization/latest/localization_freeze_receipt_v1.json}"
MANIFEST=$(jq -r .locale_manifest "$RECEIPT")
EXPORT=$(jq -r .export_manifest "$RECEIPT")

test "$(jq -r .localization_freeze_ok "$RECEIPT")" = "true"
test "$(jq -r .write_access_closed "$RECEIPT")" = "true"
test "$(jq -r .gates.L1 "$RECEIPT")" = "PASS"
test "$(jq -r .gates.L2 "$RECEIPT")" = "PASS"
test "$(jq -r .gates.L3 "$RECEIPT")" = "PASS"
test "$(jq -r .gates.L4 "$RECEIPT")" = "PASS"
test "$(jq -r .gates.L5 "$RECEIPT")" = "PASS"
test "$(jq -r .gates.L6 "$RECEIPT")" = "PASS"

REV=$(jq -r .tms_revision "$RECEIPT")
test "$(jq -r .source_revision "$EXPORT")" = "$REV"

for row in $(jq -c '.locales[]' "$EXPORT"); do
  fuzzy=$(echo "$row" | jq -r .fuzzy_count)
  test "$fuzzy" -eq 0
  file=$(echo "$row" | jq -r .file)
  expected=$(echo "$row" | jq -r .sha256)
  actual=$(sha256sum "$file" | awk '{print $1}')
  test "$actual" = "$expected"
done

echo "localization_freeze_ok"

PowerShell cousin for Windows-only teams:

param(
  [string]$Receipt = "release-evidence/localization/latest/localization_freeze_receipt_v1.json"
)

$r = Get-Content $Receipt -Raw | ConvertFrom-Json
if (-not $r.localization_freeze_ok) { throw "localization_freeze_ok false" }
foreach ($g in @("L1","L2","L3","L4","L5","L6")) {
  if ($r.gates.$g -ne "PASS") { throw "Gate $g not PASS" }
}
Write-Host "localization_freeze_ok"

Forward cluster rows (#4 five-day challenge, #8 weekly jq locale diff, #11 Friday freeze capstone) should call this verifier rather than inventing a second freeze schema.

Proof table - what each artifact proves

Artifact Proves Does not prove
locale_manifest_v1.json Scope of freeze Translation quality
Weblate revision pin Exact TMS state at freeze Player UI wiring
Export SHA-256 rows Bit-identical PO files Engine import success
Store parity table Claim consistency Capsule art localization
Installed smoke screenshots Player-visible languages Every dialogue line quality
localization_freeze_ok Release latch for promotion Forever freeze if writes reopen

Scenarios A–G

A - “Translators finished” but Weblate still writable

Symptom: Dashboard shows 100%; new commits appear tomorrow.
Action: Fail L2 until write access is closed and revision pinned.

B - Fuzzy strings hidden in one component

Symptom: Overall percent looks green; game-ui still has fuzzies.
Action: Export per component; L3 fails until fuzzies are zero for fest locales.

C - Steam claims Korean, Preferences does not

Symptom: Store Supported languages includes koreana; build has no ko.
Action: Fail L4. Either add Korean to the build and smoke it, or remove the store claim.

D - PO imported after freeze without new receipt

Symptom: CI pulls latest Weblate nightly after L6.
Action: Keep post_freeze_po_import_allowed: false. Require exception ticket + new freeze receipt.

E - Editor smoke passes, installed build fails

Symptom: Play Mode shows Japanese; Steam build falls back to English.
Action: Fail L5. Check packing, Addressables string groups, Ren'Py cache, or missing streaming assets.

F - French accidentally left in Preferences

Symptom: Incomplete French listed beside complete fest locales.
Action: Fail L4 or remove French from the shipped selector for this build.

G - Merge conflicts block export at freeze time

Symptom: Weblate cannot export clean PO.
Action: Do not fake L3. Follow merge-conflict help, then restart from L2.

Common mistakes

  • Calling Slack “freeze” without locale_manifest_v1.json.
  • Pinning a branch name instead of a commit / revision hash.
  • Exporting after someone reopened Weblate writes.
  • Accepting fuzzy PO “just for demo day.”
  • Claiming five Steam languages while smoking only English.
  • Setting localization_freeze_ok before L5 screenshots exist.
  • Treating the QA tools listicle as a freeze receipt.
  • Treating Ren'Py selector wiring as proof that strings are frozen.
  • Allowing automated PO import jobs to run with post_freeze_po_import_allowed: false unset.
  • Mixing store copy freeze with dialogue freeze without separate component rows when owners differ.

Troubleshooting

“Weblate percent is 100% but export has fuzzies.” Percentages can hide component-level fuzzies or outdated statistics. Always inspect the export file counts in L3.

“Which code goes on Steam versus Unity?” Maintain the three-column mapping in L1. Do not rely on memory during upload week.

“Can we freeze store copy but keep dialogue open?” Only if locale_manifest_v1.json and the receipt components list document the split, and the BUILD_RECEIPT states which components are frozen. Ambiguous half-freezes become disputed promotion blockers.

“Our contractor needs one more string.” Open an exception ticket. Reopen writes for a bounded window. Produce a new freeze receipt. Do not edit the old JSON in place and keep the same freeze_utc.

“Crowdin / Lokalise instead of Weblate.” Keep the same gates. Replace revision pin with that product’s equivalent (revision ID, package version, or Git commit of the synced repo).

“L5 fails only on Deck / low-spec.” Still fail L5 for the target platform you ship. Platform-specific missing tables are a localization freeze issue when players on that platform are expected.

Schedule pairing for August–September 2026

Cadence Pair with
Freeze evening (this article) L1–L6 receipt
After freeze, engine import Ren'Py selector evening or Unity/Godot import runbooks
Conflict fire Weblate merge-conflict help
Tooling refresh 14 free Weblate PO tools
Weekly after freeze Forward blog #8 localization_freeze jq locale row diff
Friday before October upload Forward blog #11 Friday freeze capstone row review
Community ritual Forward blog #4 five-day localization freeze gate challenge

Key takeaways

  1. A spoken freeze is not a release latch—file localization_freeze_receipt_v1.json.
  2. Lock locale_manifest_v1.json before you export anything (L1).
  3. Pin a TMS revision and close write access (L2).
  4. Require fuzzy-zero exports hashed to that revision (L3).
  5. Prove Steam / itch language claims match in-game language lists (L4).
  6. Smoke an installed build on EN plus two fest locales (L5).
  7. Set BUILD_RECEIPT.localization_freeze_ok only after L1–L6 pass (L6).
  8. Keep post_freeze_po_import_allowed: false until a new freeze receipt exists.
  9. Use help/listicle/Ren'Py siblings for their jobs; do not duplicate them here.
  10. Run a small verifier so CI cannot invent a green freeze.
  11. Exception reopenings need a new receipt, not an edited old timestamp.
  12. Forward challenge, jq weekly, and Friday capstone rows should consume this schema.

FAQ

What is a localization string freeze receipt?

It is a dated evidence pack that locks which locales are in scope, pins the translation system revision, proves clean exports and store-versus-game parity, documents installed build smoke, and sets a BUILD_RECEIPT latch so October promotion cannot proceed on a verbal freeze.

Do I need Weblate specifically?

No. You need a revision you can pin and an export you can hash. Weblate is the example stack because many indie Next Fest teams already use it with PO files.

Is this the same as the Ren'Py language selector tutorial?

No. The Ren'Py evening wires Preferences after PO import. This article freezes scope and evidence before (and independent of) engine-specific selector work.

Can Editor Play Mode satisfy L5?

Use it as a developer shortcut. L5 requires an installed build because packaging, caches, and Addressables string loads differ from the Editor.

What if one language is only 90% done?

Remove it from store_claim and in_game_required for this freeze, or keep L3/L4 red. Do not claim the language on Steam while failing the export gate.

How does this relate to the 18 free localization QA tools article?

That listicle helps you choose validators. This tutorial produces the freeze receipt those tools should protect.

When should we reopen Weblate writes?

Only under an exception ticket with an owner and expiry, followed by a new freeze receipt. Do not reopen silently overnight before October upload.

What goes wrong if we skip L4?

Players refund when the store lists languages the demo does not actually switch to. L4 exists to catch that before the festival traffic spike.

Related reads

Closing

October Next Fest upload week is a terrible teacher. If “strings are frozen” only exists in chat, you will rediscover missing locales while SteamPipe queues and store copy freezes collide. Spend one evening locking the manifest, pinning Weblate, proving exports and parity, smoking an installed build, and writing localization_freeze_ok only when L1–L6 are honest green.

Then keep the receipt next to your depot evidence. When someone asks for one more string the night before upload, you have a documented answer: new exception, new freeze, or no change. That is the whole point of a localization string freeze receipt.