i18n(@projects/@magic-civilization): 🌐 route 25 hardcoded .gd UI strings through ThemeVocabulary

Replace hardcoded user-visible strings in 12 scene scripts (great_person_modal,
merge_panel, specialists_drag_panel, throne_room_great_works,
intelligence_log_panel, knowledge_tree tier badge, credits, game_setup,
past_games, replay_viewer, lens_switcher/ransom_offers tooltips) with
ThemeVocabulary.lookup() + add the corresponding keys (incl. fmt_* for format
strings). Part of clearing validate-i18n with no bypass. (48 → 23 remaining.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-19 17:34:35 -05:00
parent 5b2b88b2a9
commit 07a10054f4
13 changed files with 64 additions and 25 deletions

View file

@ -335,6 +335,42 @@
"replay_title": "Replay Viewer",
"replay_placeholder": "World map renderer (wired when GdReplayPlayer bridge lands)",
"replay_step": "Step",
"replay_play": "Play",
"replay_pause": "Pause",
"fmt_replay_turn": "Turn %d / %d",
"fmt_great_person_title": "%s (%s) — Tier %d",
"fmt_great_person_action": "Action: %s",
"great_person_choose_target": "Choose a target.",
"great_person_ok": "OK",
"fmt_great_person_ok_gold": "OK — +%d gold",
"fmt_great_person_ok_production": "OK — +%d production",
"fmt_great_person_cannot": "Cannot apply: %s",
"merge_no_extension": "GdGameState extension not loaded.",
"fmt_merge_not_found": "Hybrid building '%s' not found in registry.",
"merge_buildings": "Merge Buildings",
"merge_confirm": "Merge",
"merge_cancel": "Cancel",
"specialists_idle_citizens": "Idle citizens:",
"great_works_empty": " (empty)",
"fmt_great_works_entry": " • %s — %s (era %d)",
"intel_no_envelopes": "No intercepted envelopes yet.",
"fmt_tier_badge": "T%d",
"fmt_credits_link": "%s [color=#9bbfe0][url=%s]%s[/url][/color]",
"fmt_ai_slot": "AI Slot %d",
"fmt_past_game_entry": "%s — Turn %d — %s",
"watch_replay": "Watch Replay",
"past_game_delete": "Delete",
"lens_none_tooltip": "No lens (default biome view)",
"fmt_ransom_offered": "Offered turn %d",
"endgame_game_over": "Game Over",
"endgame_winner": "Winner",
"endgame_your_clan": "Your Clan",
"endgame_final_standings": "Final Standings",
"endgame_score_over_time": "Score Over Time",
"endgame_awards": "Awards",
"endgame_timeline": "Timeline",
"endgame_view_map": "View Map",
"endgame_save_archive": "Save to Archive",
"tutorial_skip": "Skip Tutorial",
"tutorial_step_1_title": "The Hex Map",
"tutorial_step_1_body": "The world is carved into hex tiles. Click a tile to inspect it. Right-click (or click a destination) to move your selected unit. Drag with the middle mouse button to pan; scroll to zoom.",

View file

@ -109,7 +109,7 @@ func rebuild() -> void:
add_child(v)
var title: Label = Label.new()
title.text = "%s (%s) — Tier %d" % [_gp_name, _gp_class, _gp_tier]
title.text = ThemeVocabulary.lookup("fmt_great_person_title") % [_gp_name, _gp_class, _gp_tier]
title.add_theme_font_size_override("font_size", 16)
v.add_child(title)
@ -117,21 +117,21 @@ func rebuild() -> void:
if _gp_action != null and _gp_action.has_method("action_type"):
at = String(_gp_action.action_type())
var act_label: Label = Label.new()
act_label.text = "Action: %s" % (at if at != "" else "(none)")
act_label.text = ThemeVocabulary.lookup("fmt_great_person_action") % (at if at != "" else "(none)")
v.add_child(act_label)
var status: Label = Label.new()
if _validation.is_empty():
status.text = "Choose a target."
status.text = ThemeVocabulary.lookup("great_person_choose_target")
elif bool(_validation.get("ok", false)):
status.text = "OK"
status.text = ThemeVocabulary.lookup("great_person_ok")
if _validation.has("gold"):
status.text = "OK — +%d gold" % int(_validation["gold"])
status.text = ThemeVocabulary.lookup("fmt_great_person_ok_gold") % int(_validation["gold"])
if _validation.has("production"):
status.text = "OK — +%d production" % int(_validation["production"])
status.text = ThemeVocabulary.lookup("fmt_great_person_ok_production") % int(_validation["production"])
else:
var err: String = String(_validation.get("error", "invalid"))
status.text = "Cannot apply: %s" % err
status.text = ThemeVocabulary.lookup("fmt_great_person_cannot") % err
v.add_child(status)
_last_snapshot = {

View file

@ -56,7 +56,7 @@ func populate(
_clear_rows()
if not _city.is_extension_available():
_error_label.text = "GdGameState extension not loaded."
_error_label.text = ThemeVocabulary.lookup("merge_no_extension")
_error_label.show()
return
@ -121,7 +121,7 @@ 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 = "Hybrid building '%s' not found in registry." % into
_error_label.text = ThemeVocabulary.lookup("fmt_merge_not_found") % into
_error_label.show()
return
var hybrid_json: String = JSON.stringify(hybrid_def)

View file

@ -179,7 +179,7 @@ func _make_slot_row(row: Dictionary) -> Control:
func _make_unassigned_row() -> Control:
var row: HBoxContainer = HBoxContainer.new()
var label: Label = Label.new()
label.text = "Idle citizens:"
label.text = ThemeVocabulary.lookup("specialists_idle_citizens")
label.custom_minimum_size = Vector2(220, 0)
row.add_child(label)
if _unassigned.is_empty():

View file

@ -118,13 +118,13 @@ func _make_layer_section(layer: String, rows: Array) -> Control:
if rows.is_empty():
var empty: Label = Label.new()
empty.text = " (empty)"
empty.text = ThemeVocabulary.lookup("great_works_empty")
box.add_child(empty)
return box
for r: Dictionary in rows:
var line: Label = Label.new()
line.text = "%s%s (era %d)" % [
line.text = ThemeVocabulary.lookup("fmt_great_works_entry") % [
String(r["name"]),
String(r["building"]),
int(r["era"]),

View file

@ -76,7 +76,7 @@ func _build_ui() -> void:
_scroll.add_child(_vbox)
_empty_label = Label.new()
_empty_label.text = "No intercepted envelopes yet."
_empty_label.text = ThemeVocabulary.lookup("intel_no_envelopes")
_empty_label.add_theme_font_size_override("font_size", 13)
_empty_label.theme_type_variation = "LabelMuted"
_body.add_child(_empty_label)

View file

@ -451,7 +451,7 @@ func _create_node_card(
if tier > 0:
var tier_label: Label = Label.new()
tier_label.text = "T%d" % tier
tier_label.text = ThemeVocabulary.lookup("fmt_tier_badge") % tier
tier_label.add_theme_font_size_override("font_size", 10)
top_row.add_child(tier_label)

View file

@ -208,7 +208,7 @@ 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 = "%s [color=#9bbfe0][url=%s]%s[/url][/color]" % [line, url, url]
rich.text = ThemeVocabulary.lookup("fmt_credits_link") % [line, url, url]
rich.meta_clicked.connect(func(meta: Variant) -> void: OS.shell_open(str(meta)))
return rich

View file

@ -274,7 +274,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 = "AI Slot %d" % slot_idx
label.text = ThemeVocabulary.lookup("fmt_ai_slot") % slot_idx
label.add_theme_font_size_override("font_size", 11)
label.theme_type_variation = "LabelSecondary"
label.custom_minimum_size = Vector2(80, 0)

View file

@ -102,16 +102,16 @@ 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 = "%s — Turn %d%s" % [title, turn, outcome]
info_label.text = ThemeVocabulary.lookup("fmt_past_game_entry") % [title, turn, outcome]
hbox.add_child(info_label)
var watch_btn: Button = Button.new()
watch_btn.text = "Watch Replay"
watch_btn.text = ThemeVocabulary.lookup("watch_replay")
watch_btn.pressed.connect(_on_watch_replay_pressed.bind(entry))
hbox.add_child(watch_btn)
var delete_btn: Button = Button.new()
delete_btn.text = "Delete"
delete_btn.text = ThemeVocabulary.lookup("past_game_delete")
delete_btn.pressed.connect(_on_delete_pressed.bind(entry))
hbox.add_child(delete_btn)

View file

@ -94,7 +94,7 @@ func _process(delta: float) -> void:
var next: int = _current_turn + 1
if next > _final_turn:
_playing = false
_play_pause_button.text = "Play"
_play_pause_button.text = ThemeVocabulary.lookup("replay_play")
return
_goto_turn(next)
@ -143,7 +143,7 @@ func _resolve_archive_root() -> String:
func _update_turn_display() -> void:
_turn_label.text = "Turn %d / %d" % [_current_turn, _final_turn]
_turn_label.text = ThemeVocabulary.lookup("fmt_replay_turn") % [_current_turn, _final_turn]
_speed_label.text = "%.1f×" % _speed
@ -153,7 +153,10 @@ func _on_scrubber_changed(value: float) -> void:
func _on_play_pause_pressed() -> void:
_playing = not _playing
_play_pause_button.text = "Pause" if _playing else "Play"
_play_pause_button.text = (
ThemeVocabulary.lookup("replay_pause") if _playing
else ThemeVocabulary.lookup("replay_play")
)
_playback_elapsed = 0.0
@ -165,7 +168,7 @@ func _on_speed_set(speed: float) -> void:
func _on_step_pressed() -> void:
if _playing:
_playing = false
_play_pause_button.text = "Play"
_play_pause_button.text = ThemeVocabulary.lookup("replay_play")
_goto_turn(_current_turn + 1)

View file

@ -215,7 +215,7 @@ func _build_buttons() -> void:
var btn_none: Button = Button.new()
btn_none.name = "BtnNone"
btn_none.text = ""
btn_none.tooltip_text = "No lens (default biome view)"
btn_none.tooltip_text = ThemeVocabulary.lookup("lens_none_tooltip")
btn_none.pressed.connect(_on_lens_pressed.bind(""))
row.add_child(btn_none)
_buttons_by_id[""] = btn_none

View file

@ -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 = "Offered turn %d" % created_turn
panel.tooltip_text = ThemeVocabulary.lookup("fmt_ransom_offered") % created_turn
return panel