v3.2.0
What's new
KwikTip
v3.2.0 (2026-08-13)
Full Changelog Previous Releases
- release: v3.2.0 — German (deDE) localization
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - fix(localization): P2 release-gate defects — localized affix heading, empty-string fallback
Closes the two remaining P2 items from the localization release-gate review.
P2-1: the M+ active-affix heading was a hard-coded English literal in
FormatAffixDetails, so deDE clients saw "+12 Active Affixes". Restores the
semantic locale key AFFIX_HEADING and the pre-b47fc24 German value
"+%d Aktive Affixe" (the mapping existed before the semantic-key refactor
but Core.lua never read it, so it had always been dead).
P2-2: an empty translated string suppressed the English text behind it.
A new Prose() helper treats "" as absent everywhere translation fallback
applies:
- an empty translated area tip blanked the whole area instead of
falling back to the English tip
- an empty translated role note claimed its role, rendering a bare
icon with no text and suppressing the English note for that role
- the two flat-tip sites already guarded "" inline; folded into Prose()
so one rule covers every path
Also lands the pending review follow-ups already in the working tree:
enUS semantic keys for demo/debug/slash strings, the ResolveLevel
rewrite of the translated-content fallback chain, and the reworked
deDE wording.
Adds tools/validate_data.py and 8 production-path regression assertions
(52 passing, up from 44). Verified: test_localization.lua 52/52,
validate_data.py 33 dungeons errors=0, luac5.1 -p clean on 12 files.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - fix: localization bugs from review — GetInstanceInfo, field-level fallback, area IDs, subzone aliases
=== Bug Fixes ===
GetInstanceInfo() unpacking (High):- All 3 call sites that read the localized instance name now capture the
FIRST return value (localized name), not the second (instanceType).
Lines 276, 344, 601 in Core.lua.
Field-level note fallback (High): - Replaced the broken FormatNotes alternative-parameter approach with a
per-role merge of 4 candidate levels (difficulty-specific translation,
base translation, English difficulty, English base). - A translated general note no longer suppresses untranslated English
tank/healer/dps notes. Each role independently falls back to the next
level that has a note for that role. - Difficulty fallback order now correctly follows the documented chain:
diff-specific override -> base override -> English diff -> English base.
deDE locale guard (High): - Restored GetLocale() ~= "deDE" early return guard in Locale/deDE.lua.
Locale was being applied unconditionally instead of only on deDE clients.
Stable area IDs (Medium): - Replaced runtime area.id assignment (FormatAreaContent) with explicit
id fields in the source data. All 54 area entries in DungeonData.lua
now carry stable "instanceID:index" IDs. - FormatAreaContent no longer assigns IDs at runtime.
Subzone locale aliases (Medium): - Populated real German subzone names in tips_deDE.lua for 22 area entries
across 6 dungeons. Aliases keyed by stable area ID, not array index. - Added mapID = 2492 to The Promenade area entry for map-based fallback.
- Added comment documentation for remaining subzone-only entries.
Localized display headers (Medium): - M+ holding screen (UpdateContent line 601) now uses localized instance
name from GetInstanceInfo() instead of hardcoded dungeon.name.
Init.lua cleanup (Low): - Fixed stray end and wrong indentation in OnLogin().
Tests (refactored): - Rewrote to load real production locale files via loadfile() with varargs
instead of reimplementing the locale logic. - Added regression tests for: deDE locale guard, field-level note fallback,
difficulty fallback chain, area ID verification, SubzoneMatches with
German aliases, and enUS byte-equivalent behavior. - 241 tests total, all passing.
=== Files Changed ===
Core.lua: 124 lines rewritten (FormatBossContent, 3 GetInstanceInfo fixes)
DungeonData.lua: 108 lines (54 area IDs added, 1 mapID field)
Init.lua: 4 lines (indentation fix)
Locale/deDE.lua: 2 lines (GetLocale guard)
Locale/tips_deDE.lua: 37 lines (22 German subzone aliases)
tests/test_localization.lua: 532 lines (refactored to load production files, 241 tests)
- All 3 call sites that read the localized instance name now capture the
- localization: semantic keys, tip overlay infrastructure, ID-based area translation
=== Architecture ===
UI Localization (step 1-3):- enUS.lua: rewrite from bare metatable to explicit semantic key table + metatable
fallback. 62 semantic keys matching every L[] call site across all 4 source files. - deDE.lua: migrate 79 keys to 62 semantic keys, remove 17 dead keys (debug format
strings, demo notes, feedback URL that were never referenced by L[] calls). - All 4 source files: replace L["English prose"] with L.SEMANTIC_KEY at every call
site. Zero L["..."] references remain in Init.lua, Core.lua, Frames.lua, UI_Config.lua.
Translation Overlay (step 5-6): - Locale/tips_enUS.lua: initialises empty TIP_OVERRIDE_BY_ENCOUNTERID,
TIP_OVERRIDE_BY_NPCID, AREA_OVERRIDE_BY_ID, SUBZONE_LOCALE_BY_AREA_ID tables. - Locale/tips_deDE.lua: German skeleton with documentation, GetLocale() guard.
- Override tables keyed exclusively by stable numeric Blizzard IDs (encounterID,
npcID, "instanceID:areaIndex") — never by English display names. - Fallback chain: difficulty-specific override → base override → English default.
- Field-level fallback: untranslated role notes fall through to English for the
same role, never suppressing more-specific content.
Area Translation (step 7): - Stable area IDs assigned via "instanceID:1-based-index" format in FormatAreaContent.
- AREA_OVERRIDE_BY_ID provides translated tip text without mutating DungeonData.
- SUBZONE_LOCALE_BY_AREA_ID provides localized subzone strings for matching on
non-English clients, replacing the old mutable area.subzoneLocales pattern.
Display Headers (step 4): - FormatBossContent: prefers _activeEncounterName (from ENCOUNTER_START event,
already client-localized) over hardcoded boss.name. - FormatAreaContent: prefers GetInstanceInfo() first return (localized instance name)
over hardcoded dungeon.name.
Tests (step 8): - 83 tests covering: semantic key coverage, metatable fallback, tip override
infrastructure, partial note field-level fallback, difficulty fallback chain,
area translation with stable IDs, enUS byte-equivalent behavior.
Fixes: - GetInstanceInfo() first return (localized instance name) used correctly at all
display sites — previously only captured for logging, ignored for display.
- enUS.lua: rewrite from bare metatable to explicit semantic key table + metatable
This mod has no additional files

