Lesson 256: Construct Weblate CSV UTF-8 Handoff Receipt on BUILD_RECEIPT (2026)
Direct answer: Before October string-freeze week ingests Discord playtest CSV into Weblate, promote weblate_csv_utf8_handoff_receipt_v1.json proving UTF-8 export (not Excel UTF-16), BOM strip on facilitator files, Poedit re-export hashes match weblate_import_manifest_v1.json, and a README row documents the facilitator who approved the handoff—distinct from Lesson 202 (Unity String Tables in player build) and Lesson 241 (GA4 allowlist). Pair the Construct CSV UTF-8 Weblate preflight (Guide #19) and Weblate freeze tools.

Why this matters now (October string-freeze CSV handoff)
October 2026 facilitators export Discord thread tables as CSV from Excel defaults (UTF-16 LE + BOM). Weblate import shows mojibake on CJK feedback strings, Poedit re-export drifts from the facilitator sheet, and producers blame translators when the root cause was encoding on Tuesday ingest. Lesson 202 proves Unity player tables; 256 proves CSV → Weblate → Poedit bytes before Construct HTML5 string keys update.
The Construct CSV UTF-8 preflight is the ninety-second gate (W1–W6 shipped May 27, 2026)—256 is the BUILD_RECEIPT milestone. When the Tuesday Discord CSV ingest ritual publishes, run it the same week—this lesson owns the receipt column, that blog owns the facilitator rhythm.
Beginner path (Discord CSV → UTF-8 → Weblate)
| Step | Action | Success check |
|---|---|---|
| 1 | Export Discord sheet UTF-8 (no Excel double-click save) | W1 pass |
| 2 | Strip BOM / validate with file -bi or PowerShell |
W2 pass |
| 3 | Import to Weblate component | No import errors |
| 4 | Poedit re-export PO/CSV per locale | W4 hash match |
| 5 | Add facilitator README row | W5 pass |
| 6 | File receipt + BUILD_RECEIPT | weblate_csv_utf8_ok: true |
Time: ~44 minutes first string-freeze week; ~12 minutes when UTF-8 export template exists.
Developer path (gates W1–W6)
| Gate | Check | Fail when |
|---|---|---|
| W1 | Source CSV encoding | UTF-16 LE from Excel default |
| W2 | BOM stripped / absent | EF BB BF breaks Weblate |
| W3 | weblate_import_manifest_v1.json |
Locale list mismatch |
| W4 | Poedit re-export sha256 | Drift vs manifest |
| W5 | Facilitator README row | Missing approver email |
| W6 | Receipt + BUILD_RECEIPT | Promote before W4 GREEN |
W1 — cousin receipt crosswalk
| Field | Cousin (202 Unity) | Cousin (241 Construct) | This lesson (256) |
|---|---|---|---|
| Schema | string_table_receipt_v1 |
construct_playtest_analytics_receipt_v1 |
weblate_csv_utf8_handoff_receipt_v1 |
| Scope | Player build tables | GA4 allowlist | Discord CSV → Weblate bytes |
| Engine | Unity Addressables | Construct HTML5 analytics | Construct + Weblate TMS |
W2 — BOM strip (PowerShell example)
$bytes = [System.IO.File]::ReadAllBytes("playtest-feedback.csv")
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
[System.IO.File]::WriteAllBytes("playtest-feedback-utf8.csv", $bytes[3..($bytes.Length-1)])
}
Log bom_stripped: true in receipt.
weblate_import_manifest_v1.json
{
"schema": "weblate_import_manifest_v1",
"freeze_tag": "october-fest-2026-rc2",
"source_csv_path": "localization/ingest/playtest-feedback-utf8.csv",
"source_csv_sha256": "aa11…",
"encoding_detected": "utf-8",
"locales": ["en", "ja", "zh-Hans"],
"poedit_exports": {
"ja": { "path": "localization/po/ja.po", "sha256": "bb22…" },
"zh-Hans": { "path": "localization/po/zh_Hans.po", "sha256": "cc33…" }
}
}
facilitator_readme_row (W5)
| Column | Example |
|---|---|
handoff_id |
UUID |
facilitator_email |
[email protected] |
discord_thread_url |
https://discord.com/channels/… |
approved_at_utc |
ISO-8601 |
encoding_fix_applied |
bom_strip_v1 |
weblate_csv_utf8_handoff_receipt_v1.json
{
"schema": "weblate_csv_utf8_handoff_receipt_v1",
"build_label": "october-fest-2026-rc2",
"manifest_path": "localization/ingest/weblate_import_manifest_v1.json",
"bom_stripped": true,
"encoding_final": "utf-8",
"facilitator_readme_row": "localization/ingest/FACILITATOR_README.csv",
"cousin_receipts": {
"string_table_smoke": "release-evidence/localization/STRING_TABLE_RECEIPT.json",
"construct_playtest_analytics": "release-evidence/html5/CONSTRUCT_PLAYTEST_ANALYTICS_RECEIPT.json"
},
"gates": {
"W1_source_utf8": "pass",
"W2_bom_strip": "pass",
"W3_manifest": "pass",
"W4_poedit_hash": "pass",
"W5_facilitator_row": "pass",
"W6_build_receipt": "pass"
},
"weblate_csv_utf8_ok": true,
"string_freeze_promotion_allowed": true
}
Pin under release-evidence/localization/WEBLATE_CSV_UTF8_HANDOFF_RECEIPT.json.
BUILD_RECEIPT row (W6)
| Column | Pass when |
|---|---|
weblate_csv_utf8_handoff |
weblate_csv_utf8_ok: true |
string_table_receipt |
Cousin Lesson 202 independent |
construct_playtest_analytics |
Cousin Lesson 241 independent |
ALTER TABLE release_publish_gate ADD COLUMN IF NOT EXISTS
weblate_csv_utf8_handoff_blocked BOOLEAN NOT NULL DEFAULT false;
Thursday row review — Weblate CSV UTF-8 line: encoding + manifest hash Y/N.
Key takeaways
- Excel “Save As CSV” is UTF-16 by default on Windows—W1 fails without conversion.
- BOM strip is not optional for Weblate CSV components.
- Poedit re-export hash must match manifest—W4 catches silent drift.
- Facilitator README row is audit evidence, not bureaucracy.
- Weblate merge freeze help — TMS conflicts after import.
- Lesson 255 Deck geometry is parallel—string freeze is separate.
- Cousin: Lesson 257 Addressables
jqinventory audit—parallel Unity strip column. - 14 Weblate freeze tools — PO export lane.
- OBS concat help — Tuesday ingest rhythm cousin.
- October capstone 265 wires 254–264 including this row.
Common mistakes
- Importing UTF-16 CSV directly into Weblate—mojibake on CJK (W1).
- Skipping BOM strip because “it looked fine in Notepad” (W2).
- Filing 202 while CSV handoff still UTF-16—player build ≠ ingest bytes.
- Missing facilitator row—cannot trace who approved strings (W5).
- Merging
weblate_csv_utf8_handoff_receiptintostring_table_receipt.
Troubleshooting
| Symptom | Lane |
|---|---|
| Mojibake on import | W1/W2 encoding |
| Weblate rejects CSV | Delimiter / quoting + encoding |
| Poedit hash mismatch | W4 re-export from wrong source file |
| Strings OK in editor, wrong in HTML5 | Lesson 202 |
| Merge conflicts post-import | Weblate freeze help |
Mini exercise (35 minutes)
- Save sample sheet as UTF-16—confirm W1 fail.
- Re-export UTF-8 + strip BOM.
- Write
weblate_import_manifest_v1.jsonfor two locales. - Add facilitator README row.
- File receipt; BUILD_RECEIPT GREEN.
Continuity — October–Q4 2026 fest ops truth (254–265)
| Lesson | Receipt focus |
|---|---|
| 255 | Godot Deck letterbox reset |
| 256 (this) | Construct Weblate CSV UTF-8 handoff |
| 257 | Unity Addressables jq audit (queued) |
| 265 | October ops capstone (queued) |
Previous: Lesson 255 — Godot Deck letterbox Gamescope reset
Next: Lesson 258 — OBS replay buffer uniform fragments (queued)
FAQ
Same as Lesson 202?
202 = Unity String Tables in installed player build; 256 = Discord CSV encoding into Weblate.
Same as the Construct guide chapter?
Guide = W1–W6 checklist; 256 = BUILD_RECEIPT promotion.
Need PO files instead of CSV?
Team policy—receipt documents source encoding; PO hashes still required in W4.
Construct-only studio?
Yes—Unity 202 is cousin reference only; do not file Unity receipts for HTML5-only SKUs.
String-freeze week fails when Discord CSV stays UTF-16—strip BOM, hash Poedit exports, log facilitator approval, then weblate_csv_utf8_ok before Weblate merge.