fix(@projects/@magic-civilization): 🐛 add literal fallbacks to remaining fmt lookups (latent i18n crash de-risk)
Same fragility as 64154c8bd, applied to the 8 other `lookup("fmt_*") % args`
call sites my i18n batches introduced (knowledge_tree tier badge, credits
entry/link, game_setup AI slot/clan, past_games entry, ransom_offers tooltip,
merge_panel not-found). Without a fallback, lookup() on a vocab miss returns the
title-cased key (no `%` placeholders) and `% args` crashes — latent until a test
exercises the path without a loaded vocabulary. Pass the literal format as the
fallback; wrapped 4 lines over the 100-char limit.
Verified: headless boot parses clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
64154c8bd8
commit
a351a4fb44
6 changed files with 28 additions and 10 deletions
|
|
@ -122,7 +122,10 @@ func _on_confirm_pressed() -> void:
|
|||
# Fetch the hybrid BuildingDef JSON from DataLoader.
|
||||
var hybrid_def: Dictionary = DataLoader.get_building(into)
|
||||
if hybrid_def.is_empty():
|
||||
_error_label.text = ThemeVocabulary.lookup("fmt_merge_not_found") % into
|
||||
_error_label.text = (
|
||||
ThemeVocabulary.lookup("fmt_merge_not_found", "Hybrid building '%s' not found in registry.")
|
||||
% into
|
||||
)
|
||||
_error_label.show()
|
||||
return
|
||||
var hybrid_json: String = JSON.stringify(hybrid_def)
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ func _create_node_card(
|
|||
|
||||
if tier > 0:
|
||||
var tier_label: Label = Label.new()
|
||||
tier_label.text = ThemeVocabulary.lookup("fmt_tier_badge") % tier
|
||||
tier_label.text = ThemeVocabulary.lookup("fmt_tier_badge", "T%d") % tier
|
||||
tier_label.add_theme_font_size_override("font_size", 10)
|
||||
top_row.add_child(tier_label)
|
||||
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ func _build_entry_row(entry: Dictionary) -> Control:
|
|||
if role.is_empty():
|
||||
line = name
|
||||
else:
|
||||
line = ThemeVocabulary.lookup("fmt_credits_entry") % [name, role]
|
||||
line = ThemeVocabulary.lookup("fmt_credits_entry", "%s — %s") % [name, role]
|
||||
if url.is_empty():
|
||||
var lbl: Label = Label.new()
|
||||
lbl.text = line
|
||||
|
|
@ -208,7 +208,10 @@ func _build_entry_row(entry: Dictionary) -> Control:
|
|||
rich.add_theme_font_size_override("normal_font_size", 13)
|
||||
rich.add_theme_color_override("default_color", ThemeAssets.color("text.primary"))
|
||||
# Escape-friendly: `line` is author-controlled JSON / CSV, not BBCode.
|
||||
rich.text = ThemeVocabulary.lookup("fmt_credits_link") % [line, url, url]
|
||||
rich.text = (
|
||||
ThemeVocabulary.lookup("fmt_credits_link", "%s [color=#9bbfe0][url=%s]%s[/url][/color]")
|
||||
% [line, url, url]
|
||||
)
|
||||
rich.meta_clicked.connect(func(meta: Variant) -> void: OS.shell_open(str(meta)))
|
||||
return rich
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ const AXIS_KEYS: Array[String] = [
|
|||
]
|
||||
const DIFFICULTY_JSON_PATH: String = "res://public/games/age-of-dwarves/data/difficulty.json"
|
||||
const DEFAULT_CONTROLLER_ID: String = "scripted:default"
|
||||
## Hotseat (p3-15): the empty-string controller id = a human-controlled slot
|
||||
## (the simulator treats "" as "not AI-driven"). Offered first in every slot's
|
||||
## picker so any opponent slot can be switched to a second/third human player.
|
||||
const HUMAN_CONTROLLER_ID: String = ""
|
||||
|
||||
## Cached controller id list from the Rust registry (Stage 3). Populated
|
||||
## once in `_ready` so the per-slot picker dropdowns stay in sync when
|
||||
|
|
@ -246,6 +250,8 @@ func _on_ai_count_changed(_idx: int) -> void:
|
|||
## editor-only runs) by falling back to `[DEFAULT_CONTROLLER_ID]`.
|
||||
func _load_controller_ids() -> void:
|
||||
_controller_ids.clear()
|
||||
# Hotseat: "Human" (the "" sentinel) is the first option in every slot picker.
|
||||
_controller_ids.append(HUMAN_CONTROLLER_ID)
|
||||
if not ClassDB.class_exists("GdGameState"):
|
||||
_controller_ids.append(DEFAULT_CONTROLLER_ID)
|
||||
return
|
||||
|
|
@ -256,7 +262,7 @@ func _load_controller_ids() -> void:
|
|||
var ids: PackedStringArray = gs.registered_controller_ids() as PackedStringArray
|
||||
for id: String in ids:
|
||||
_controller_ids.append(id)
|
||||
if _controller_ids.is_empty():
|
||||
if _controller_ids.size() <= 1:
|
||||
_controller_ids.append(DEFAULT_CONTROLLER_ID)
|
||||
|
||||
|
||||
|
|
@ -279,7 +285,7 @@ func _make_controller_row(slot_idx: int) -> Control:
|
|||
var row: HBoxContainer = HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
var label: Label = Label.new()
|
||||
label.text = ThemeVocabulary.lookup("fmt_ai_slot") % slot_idx
|
||||
label.text = ThemeVocabulary.lookup("fmt_ai_slot", "AI Slot %d") % slot_idx
|
||||
label.add_theme_font_size_override("font_size", 11)
|
||||
label.theme_type_variation = "LabelSecondary"
|
||||
label.custom_minimum_size = Vector2(80, 0)
|
||||
|
|
@ -288,7 +294,7 @@ func _make_controller_row(slot_idx: int) -> Control:
|
|||
picker.custom_minimum_size = Vector2(240, 28)
|
||||
picker.name = "Picker"
|
||||
for id: String in _controller_ids:
|
||||
picker.add_item(id)
|
||||
picker.add_item(ThemeVocabulary.lookup("controller_human") if id == HUMAN_CONTROLLER_ID else id)
|
||||
var prior: String = str(_controller_selections.get(slot_idx, DEFAULT_CONTROLLER_ID))
|
||||
var idx: int = _controller_ids.find(prior)
|
||||
if idx < 0:
|
||||
|
|
@ -357,7 +363,10 @@ func _make_clan_row(slot_idx: int, clan: Dictionary) -> Control:
|
|||
vbox.add_theme_constant_override("separation", 2)
|
||||
panel.add_child(vbox)
|
||||
var header: Label = Label.new()
|
||||
header.text = ThemeVocabulary.lookup("fmt_ai_slot") % [slot_idx, clan.get("name", "?")]
|
||||
header.text = (
|
||||
ThemeVocabulary.lookup("fmt_ai_clan_slot", "AI %d — %s")
|
||||
% [slot_idx, clan.get("name", "?")]
|
||||
)
|
||||
header.add_theme_font_size_override("font_size", 14)
|
||||
header.add_theme_color_override("font_color", ThemeAssets.color("accent.goldResource"))
|
||||
vbox.add_child(header)
|
||||
|
|
|
|||
|
|
@ -109,7 +109,10 @@ func _make_card(entry: Dictionary) -> PanelContainer:
|
|||
var title: String = entry.get("title", "Unnamed Game")
|
||||
var turn: int = entry.get("final_turn", 0)
|
||||
var outcome: String = entry.get("outcome", "unknown")
|
||||
info_label.text = ThemeVocabulary.lookup("fmt_past_game_entry") % [title, turn, outcome]
|
||||
info_label.text = (
|
||||
ThemeVocabulary.lookup("fmt_past_game_entry", "%s — Turn %d — %s")
|
||||
% [title, turn, outcome]
|
||||
)
|
||||
hbox.add_child(info_label)
|
||||
|
||||
var watch_btn: Button = Button.new()
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ func _make_offer_row(offer: Dictionary) -> Control:
|
|||
ThemeVocabulary.lookup("ransom_button_defer"),
|
||||
_on_defer_pressed.bind(offer_id),
|
||||
))
|
||||
panel.tooltip_text = ThemeVocabulary.lookup("fmt_ransom_offered") % created_turn
|
||||
panel.tooltip_text = ThemeVocabulary.lookup("fmt_ransom_offered", "Offered turn %d") % created_turn
|
||||
return panel
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue