Lesson 289: Ren'Py Localization String Freeze PO Import Receipt on BUILD_RECEIPT (2026)

Direct answer: Before August–September 2026 October Next Fest runway treats “we froze strings” as GREEN, promote localization_freeze_receipt_v1.json with L1–L6—pin locale_manifest_v1.json, close Weblate writes, walk every required game/tl/ path, prove Preferences ↔ Steam language parity, smoke an installed build, and fail-close localization_freeze_ok on BUILD_RECEIPT so verbal freeze cannot leave selectors stuck on English. Pair Guide #49, freeze evening, and Help #54 for post-import recovery.

Lesson hero for Ren'Py localization string freeze PO import receipt

Why this matters now (August–September freeze before October upload)

October Next Fest store language claims ship while Weblate stays writable and Preferences still boot English. Lesson 288 closed the February→March Whisper arc; 289 opens the August–October ops spine for the RPG track—localization freeze is the first BUILD_RECEIPT latch before dual-SKU (290) and soft-launch (293).

Guide #49 is the ninety-second Ren'Py PO / tl/ gate; this lesson is the course-project BUILD_RECEIPT milestone + verify script.

Beginner path (55-minute freeze latch)

Step Action Success check
1 File locale_manifest_v1.json Required locales listed with tl_path
2 Pin Weblate freeze commit writes_closed: true
3 Walk every required game/tl/ dir No MISSING lines
4 Fill Preferences ↔ Steam parity table All fest locales Y
5 Installed smoke EN + 2 locales Screenshots in smoke/
6 File freeze receipt + run verify Exit 0
7 Latch BUILD_RECEIPT localization_freeze_ok Boolean after verify

Time: ~55 minutes first freeze week; ~20 minutes when manifest already pinned.
Prerequisites: Ren'Py project with game/tl/; Weblate (or TMS) export access; Steam Supported languages draft; jq + Bash/Git Bash; Guide #49 bookmarked.

Developer path (gates L1–L6)

Gate Check Fail when
L1 Locale manifest pinned Verbal “we support JP” without tl_path
L2 TMS / Weblate writes closed Writers still open after “freeze” Slack
L3 Required tl_path dirs exist Empty or missing locale folders
L4 Preferences ↔ store parity Steam lists language Ren'Py cannot select
L5 Installed player smoke Editor-only Language() click
L6 Receipt + verify localization_freeze_ok without verify

Cross-cutting: Do not set po_import_reopen_allowed: true without a numbered exception. Do not treat Lesson 225 PO-hash / language wiring as freeze proof.

Cousin receipt crosswalk

Field Lesson 225 PO-hash / language Freeze evening blog This lesson (289)
Schema PO-hash / language selector localization_freeze_receipt_v1 (ritual) localization_freeze_receipt_v1
Window Hash + selector probe One-evening L1–L6 BUILD_RECEIPT latch
Pass field language / hash ok evening localization_freeze_ok localization_freeze_ok

Reference cousins in cousin_receipts—do not flatten schemas.

L1–L3 — Manifest + Weblate pin + tl walk (course project)

MAN="release-evidence/localization/october-next-fest-2026/locale_manifest_v1.json"
jq -e '.locales | map(select(.in_game_required==true)) | length >= 1' "$MAN"

# After Weblate freeze commit:
jq -n --arg c "$(git -C weblate-mirror rev-parse HEAD)" \
  '{writes_closed:true, weblate_freeze_commit:$c}' \
  > release-evidence/localization/october-next-fest-2026/weblate_freeze_pin.json

jq -r '.locales[] | select(.in_game_required==true) | .tl_path' "$MAN" \
| while IFS= read -r p; do
    test -d "$p" || { echo "MISSING tl: $p"; exit 3; }
  done

L4 — Preferences freeze latch (store parity)

Steam Supported language Ren'Py Preferences / Language() id Pass?
English english Y/N
Japanese japanese Y/N
Simplified Chinese schinese / project id Y/N

Add a one-line Preferences freeze note: no new Language() buttons without L1 manifest bump.

L5 — Installed smoke (not editor-only)

Build distribution → install → boot English + two fest locales → capture main-menu screenshots into release-evidence/localization/october-next-fest-2026/smoke/. Editor-only Language() does not pass L5.

L6 — Receipt + verify + BUILD_RECEIPT

{
  "schema": "localization_freeze_receipt_v1",
  "freeze_label": "october-next-fest-2026",
  "locked_utc": "2026-09-12T18:00:00Z",
  "locale_manifest_path": "release-evidence/localization/october-next-fest-2026/locale_manifest_v1.json",
  "locale_manifest_sha256": "REPLACE",
  "weblate_freeze_commit": "abc123def",
  "writes_closed": true,
  "language_map_path": "release-evidence/localization/language_map_v1.json",
  "store_parity_path": "release-evidence/localization/october-next-fest-2026/store_language_parity.md",
  "smoke_dir": "release-evidence/localization/october-next-fest-2026/smoke",
  "cousin_receipts": {
    "language_map_lesson_225": "release-evidence/localization/PO_HASH_LANGUAGE_RECEIPT.json"
  },
  "gates": {
    "L1_manifest": "pass",
    "L2_tms_pin": "pass",
    "L3_tl_paths": "pass",
    "L4_store_parity": "pass",
    "L5_installed_smoke": "pass",
    "L6_receipt": "pass"
  },
  "localization_freeze_ok": true,
  "po_import_reopen_allowed": false,
  "notes": "Post-freeze PO requires exception list + new freeze label"
}

Pin at release-evidence/localization/october-next-fest-2026/LOCALIZATION_FREEZE_RECEIPT.json.

./scripts/verify_localization_freeze.sh \
  release-evidence/localization/october-next-fest-2026/LOCALIZATION_FREEZE_RECEIPT.json

Minimum verify sketch:

#!/usr/bin/env bash
set -euo pipefail
REC="${1:-release-evidence/localization/october-next-fest-2026/LOCALIZATION_FREEZE_RECEIPT.json}"
jq -e '.schema == "localization_freeze_receipt_v1"' "$REC"
jq -e '.localization_freeze_ok == true' "$REC"
jq -e '.writes_closed == true' "$REC"
MAN="$(jq -r '.locale_manifest_path' "$REC")"
test -f "$MAN"
while IFS= read -r p; do
  test -d "$p" || exit 3
done < <(jq -r '.locales[] | select(.in_game_required==true) | .tl_path' "$MAN")
echo "localization_freeze verify: OK"

BUILD_RECEIPT latch:

{
  "localization_freeze_ok": true,
  "localization_freeze_receipt": "release-evidence/localization/october-next-fest-2026/LOCALIZATION_FREEZE_RECEIPT.json",
  "po_import_reopen_allowed": false
}

Relationship to Guide / evening / Help / language map

Artifact Role
Guide #49 Ninety-second Ren'Py freeze gate
Freeze evening blog Full L1–L6 ritual depth
This lesson BUILD_RECEIPT milestone
Lesson 225 PO-hash / language cousin
Help #54 Selector stuck English recovery
Resource #52 forward Weblate freeze tool bookmarks

Order: language map GREEN (225) → Guide #49 L1–L6 → this lesson latch → dual-SKU Lesson 290. If Preferences stay English after unauthorized reopen, run Help #54 before flipping OK again.

Common mistakes

  • Slack “freeze” while Weblate writers still open.
  • Flipping localization_freeze_ok without tl_path walk.
  • Editor-only Language() as L5.
  • Steam Supported languages listing locales Ren'Py cannot select.
  • Treating Lesson 225 map GREEN as freeze proof.
  • Reopening PO import without exception list (po_import_reopen_allowed).

Troubleshooting

Symptom Fix
Selector stuck English after PO Help #54tl_path walk, cache rebuild, Preferences probe
MISSING tl_path Re-export PO into correct game/tl/<locale>/; bump manifest
Weblate still writable Re-run L2 pin; do not latch L6
Merge-conflict export block Weblate merge help / freeze evening conflict branch
Capstone later needs freeze child Lesson 300 will require 289 (or waiver)

Arc continuity (289–294 high spine)

Lesson Milestone
289 (this) Ren'Py localization string freeze
290 (queued) Godot itch/Steam dual-depot dress rehearsal
291 (queued) Unity April Addressables warm catalog
292 (queued) Photoshop Summer Sale capsule sticker
293 (queued) Construct soft-launch wishlist window
294 (queued) GameMaker IARC age-rating packet

Previous: Lesson 288 — OBS post-fest Whisper clips_index handoff
Next: Lesson 290 — Godot itch/Steam dual-depot dress rehearsal (queued)

FAQ

Does 289 replace the freeze evening blog?
No—evening owns full ritual depth; 289 latches BUILD_RECEIPT for the RPG course project.

Can we reopen Weblate after L6?
Only with po_import_reopen_allowed + numbered exception and a new freeze label—then re-run L1–L6.

Is Help #54 required first?
Only when selector already stuck after a bad import—prevention is Guide #49 + this lesson.

Is Lesson 225 enough?
No—language map wiring ≠ writes-closed freeze + installed smoke.

Key takeaways

  1. Verbal freeze is not localization_freeze_ok.
  2. L1–L3 pin manifest, Weblate close, and tl_path walk.
  3. L4–L5 require store parity + installed smoke.
  4. Guide #49 is the ninety-second gate; 289 is the BUILD_RECEIPT milestone.
  5. Help #54 recovers stuck-English after unauthorized reopen.
  6. Next spine lesson is dual-SKU 290.

Related GamineAI reads


October store language claims without a freeze receipt are soft-launch debt—close Weblate, walk game/tl/, smoke installed Preferences, file localization_freeze_receipt_v1.json, then latch localization_freeze_ok only when L1–L6 are honestly green.