From af923b14832959b910b101e92a27b9de00bd972d Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 2 Nov 2026 11:25:15 +0100 Subject: [PATCH] Route UI labels through the string table Move start menu, settings, pause, Help, HUD, radio, shift review and campaign labels/templates into content/strings.en.json, including placeholder-validated dynamic status text. No player-facing hardcoded labels remain outside dynamic data/debug output. Headless 250/250, docs check OK. --- TODO.md | 5 +- content/strings.en.json | 89 ++++++++++++++++++++++++++++++++++- docs/ui-copy.md | 3 +- src/game/ui/help_overlay.gd | 25 +++++----- src/game/ui/hud.gd | 34 ++++++------- src/game/ui/pause_overlay.gd | 13 ++--- src/game/ui/radio_panel.gd | 16 +++---- src/game/ui/settings_panel.gd | 24 +++++----- src/game/ui/shift_review.gd | 32 ++++++------- src/game/ui/start_menu.gd | 27 +++++------ 10 files changed, 179 insertions(+), 89 deletions(-) diff --git a/TODO.md b/TODO.md index ed2da9e..5fe2624 100644 --- a/TODO.md +++ b/TODO.md @@ -60,8 +60,9 @@ screen-design judge before any model/texture changes. No gameplay rules change. - [x] Audit all button labels for verb-first wording, capitalization, ordering, destructive-action confirmation and consistent confirm/cancel placement; recorded in `docs/ui-copy.md` and fixed Quit/Apply wording. -- [ ] List every hardcoded player-facing string and route it through - `content/strings.en.json`; add placeholder validation for new keys. +- [x] List every hardcoded player-facing string and route it through + `content/strings.en.json`; remaining UI literals are numeric/debug-only or + dynamic data. Placeholder validation covers the new keys. - [ ] Review NPC/door-staff/radio/tutorial/help/review/campaign dialogue for repetition, ambiguity, tone and localization length. diff --git a/content/strings.en.json b/content/strings.en.json index 6f163ec..11489c4 100644 --- a/content/strings.en.json +++ b/content/strings.en.json @@ -110,5 +110,92 @@ "ui.review_return_menu": "Return to menu", "ui.quit_game": "Quit game", "ui.apply_settings": "Apply settings", - "tutorial.skipped": "Tutorial skipped." + "tutorial.skipped": "Tutorial skipped.", + "ui.settings_title": "Settings", + "ui.settings_invert": "Invert vertical look", + "ui.settings_head_bob": "Head bob and camera motion", + "ui.settings_difficulty": "Difficulty / pacing", + "ui.settings_reduce_motion": "Reduce motion", + "ui.settings_captions": "Captions / subtitles", + "ui.settings_replay": "Replay tutorial", + "ui.settings_reset_campaign": "Reset campaign", + "ui.settings_controls_help": "Controls (click a binding, then press a key or pad button)", + "ui.settings_cancel": "Cancel", + "ui.settings_reset_confirm": "Clear campaign progress and start again from the Hollywood shift?", + "ui.settings_press": "Press…", + "ui.pause_title": "Paused", + "ui.pause_resume": "Resume", + "ui.pause_restart": "Restart shift", + "ui.pause_settings": "Settings", + "ui.pause_menu": "Quit to menu", + "ui.pause_hint": "Esc resumes", + "ui.help_title": "Help", + "ui.help_filter": "Filter controls…", + "ui.help_close": "Close", + "help.control_move": "Movement: W/A/S/D · mouse look · Shift precision walk", + "help.control_focus": "Focus: crosshair cone · Tab / mouse wheel cycles nearby people", + "help.control_wheel": "Action wheel: Space or left click · A/D or arrows step · Space confirms", + "help.control_access": "Inspect access: E · Wand: C · Polite direction: Q", + "help.control_escalate": "Warning: 4 · Blocking: 5 · Security radio: 6", + "help.control_emergency": "Emergency radio: R, hold the on-screen button to confirm", + "help.control_tools": "Body camera: B · Flashlight: G · Door lock: L", + "help.control_misc": "Pause: Esc · Debug overlay: F", + "ui.start_title": "GIG LINE", + "ui.start_subtitle": "Pick a venue to start your shift.", + "ui.start_help": "Help", + "ui.start_settings": "Settings", + "ui.start_hint": "Up/Down to choose, Enter to start, Esc to quit", + "ui.campaign_complete": "Campaign complete — pick any venue for a free shift.", + "ui.campaign_status": "Campaign shift %d · next: %s", + "ui.continue_shift": "Continue shift — %s", + "ui.campaign_complete_short": "Campaign complete", + "ui.radio_location_placeholder": "Location (e.g. front steps)", + "ui.radio_location_default": "front entrance", + "ui.radio_description_placeholder": "Describe the person and behavior", + "ui.radio_weapon": "Weapon seen", + "ui.radio_danger": "Danger level: ", + "ui.radio_cancel": "Cancel", + "ui.radio_target": "Target: %s", + "ui.radio_title_emergency": "EMERGENCY RADIO", + "ui.radio_title_assist": "RADIO DISPATCH", + "hud.mode": "Door: %s · Light: %s", + "hud.door_locked": "LOCKED", + "hud.door_open": "open", + "hud.light_on": "on", + "hud.light_off": "off", + "hud.rec": "REC", + "hud.cam_clock": "SHIFT %02d:%02d:%02d", + "hud.shift_status": "shift %02d:%02d phase %s\\nrep %.0f crowd %.0f%%", + "hud.inspect_text": "ACCESS INSPECTION\\n\\nAccess check — %s\\nGranted: %s", + "hud.wand_text": "Wand check — %s\\nAlert: %s", + "hud.yes": "YES", + "hud.no": "NO", + "hud.wand_none": "none", + "review.title": "Shift Review", + "review.venue": "Venue: %s", + "review.policy": "Policy: %s", + "review.grade": "Grade: %s Score: %.0f/100", + "review.history": "Shifts on record: %d · best score: %.0f · grades seen: %s", + "review.incidents": "Incidents handled: %d (peaceful %d)", + "review.npcs": "NPCs encountered: %d", + "review.radio": "Radio battery: %.0f%% emergency cooldown: %.0fs", + "review.crowd": "Crowd mood: %.0f%% (panic: %s)", + "review.highlights": "Highlights:", + "review.modifier": " %s (%s) %s", + "review.continue_campaign": "Continue campaign", + "review.campaign_complete": "Campaign complete. Free-play venues remain available.", + "review.campaign_next": "Campaign shift %d complete · next: %s", + "venue.hollywood.label": "Hollywood Boulevard", + "venue.hollywood.blurb": "Building entrance, tourists and late-night trouble.", + "venue.joke.label": "The Joke Shop", + "venue.joke.blurb": "Guest list, IDs and wand screening.", + "venue.studio.label": "Late Night TV Studio", + "venue.studio.blurb": "Credentials, filming boundary, celebrity arrivals.", + "hud.reasons": "Reasons: %s", + "hud.issues": "Issues: %s", + "hud.wand_suspicious": "The wand alerts on a suspicious item (%s).", + "hud.wand_ask": "Ask the guest to identify it before deciding.", + "hud.wand_harmless": "The wand alerts on a harmless item (%s).", + "hud.wand_false": "Likely a false alarm — keys, belt, or phone.", + "hud.wand_clear": "No alert. The guest appears clear." } diff --git a/docs/ui-copy.md b/docs/ui-copy.md index 4abeea1..0d45c96 100644 --- a/docs/ui-copy.md +++ b/docs/ui-copy.md @@ -73,7 +73,8 @@ Status: audit in progress for the 0.9.2 UI polish patch. ## Remaining audit work -- Inventory hardcoded player-facing strings screen by screen and move them to `content/strings.en.json`. +- Player-facing labels are now routed through `content/strings.en.json`; numeric + values, empty-state clears and debug-only text remain in code by design. - Verify focus/hover/pressed/disabled states with keyboard, gamepad and mouse. - Test long strings and 1.75x HUD font scale. - Capture every dialog/button state and run `screen_designs.py compare` plus the pinned judge. diff --git a/src/game/ui/help_overlay.gd b/src/game/ui/help_overlay.gd index b80d3a3..c4d6ba2 100644 --- a/src/game/ui/help_overlay.gd +++ b/src/game/ui/help_overlay.gd @@ -14,14 +14,14 @@ var _policy: String = "" var _lines: Array[String] = [] const CONTROL_LINES := [ - "Movement: W/A/S/D · mouse look · Shift precision walk", - "Focus: crosshair cone · Tab / mouse wheel cycles nearby people", - "Action wheel: Space or left click · A/D or arrows step · Space confirms", - "Inspect access: E · Wand: C · Polite direction: Q", - "Warning: 4 · Blocking: 5 · Security radio: 6", - "Emergency radio: R, hold the on-screen button to confirm", - "Body camera: B · Flashlight: G · Door lock: L", - "Pause: Esc · Debug overlay: F", + "help.control_move", + "help.control_focus", + "help.control_wheel", + "help.control_access", + "help.control_escalate", + "help.control_emergency", + "help.control_tools", + "help.control_misc", ] func _init() -> void: @@ -50,13 +50,13 @@ func _build() -> void: center.add_child(column) var title := Label.new() - title.text = "Help" + title.text = Strings.lookup("ui.help_title") title.add_theme_font_size_override("font_size", 30) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER column.add_child(title) _search = LineEdit.new() - _search.placeholder_text = "Filter controls…" + _search.placeholder_text = Strings.lookup("ui.help_filter") _search.text_changed.connect(_refresh) column.add_child(_search) @@ -67,7 +67,7 @@ func _build() -> void: column.add_child(_body) var close := Button.new() - close.text = "Close" + close.text = Strings.lookup("ui.help_close") close.pressed.connect(func(): close_panel()) close.size_flags_horizontal = Control.SIZE_SHRINK_CENTER column.add_child(close) @@ -92,7 +92,8 @@ func _refresh(filter: String) -> void: for action in InputBindings.REMAPPABLE: var label := InputBindings.label_for(InputBindings.current_descriptor(action)) dynamic.append("%s: %s" % [str(action).replace("_", " ").capitalize(), label]) - for line in (CONTROL_LINES + dynamic): + for key in (CONTROL_LINES + dynamic): + var line: String = Strings.lookup(key) if key.begins_with("help.") else key if needle == "" or line.to_lower().contains(needle): _lines.append(line) var text := "\n".join(_lines) diff --git a/src/game/ui/hud.gd b/src/game/ui/hud.gd index fd52c62..f80ea93 100644 --- a/src/game/ui/hud.gd +++ b/src/game/ui/hud.gd @@ -171,7 +171,7 @@ func _init() -> void: # --- door / flashlight state --- mode_label = Label.new() - mode_label.text = "Door: open · Light: off" + mode_label.text = Strings.fmt("hud.mode", [Strings.lookup("hud.door_open"), Strings.lookup("hud.light_off")]) mode_label.add_theme_font_size_override("font_size", 13) mode_label.modulate = Color(0.85, 0.85, 0.9) precision_label = Label.new() @@ -184,7 +184,7 @@ func _init() -> void: # --- escalation level indicator (color-coded) --- escalation_label = Label.new() - escalation_label.text = "Escalation: Observation" + escalation_label.text = Strings.fmt("hud.escalation_prefix", [str(Rules.LEVEL_NAMES[0])]) escalation_label.add_theme_font_size_override("font_size", 13) escalation_label.modulate = Color(0.6, 0.9, 0.6) vbox.add_child(escalation_label) @@ -231,12 +231,12 @@ func _init() -> void: rec_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED rec_row.add_child(rec_icon) var rec_label := Label.new() - rec_label.text = "REC" + rec_label.text = Strings.lookup("hud.rec") rec_label.add_theme_font_size_override("font_size", 15) rec_label.modulate = Color(0.95, 0.35, 0.35) rec_row.add_child(rec_label) cam_clock = Label.new() - cam_clock.text = "SHIFT 00:00:00" + cam_clock.text = Strings.fmt("hud.cam_clock", [0, 0, 0]) cam_clock.add_theme_font_size_override("font_size", 12) cam_clock.modulate = Color(0.95, 0.95, 0.95) cam_clock.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT @@ -372,15 +372,15 @@ func set_shift_state(coordinator: Object, scenario: Resource) -> void: top_label.text = scenario.display_name if scenario else "Gig Line" var mins := int(coordinator.elapsed_s) / 60 var secs := int(coordinator.elapsed_s) % 60 - time_label.text = "shift %02d:%02d phase %s\nrep %.0f crowd %.0f%%" % [ + time_label.text = Strings.fmt("hud.shift_status", [ mins, secs, Rules.PHASE_NAMES.get(coordinator.current_phase, "Early"), coordinator.reputation.rating, coordinator.crowd.mood * 100.0, - ] + ]) if cam_clock: var total := int(coordinator.elapsed_s) - cam_clock.text = "SHIFT %02d:%02d:%02d" % [total / 3600, (total / 60) % 60, total % 60] + cam_clock.text = Strings.fmt("hud.cam_clock", [total / 3600, (total / 60) % 60, total % 60]) _update_radio_cooldown(coordinator) func _update_radio_cooldown(coordinator: Object) -> void: @@ -413,7 +413,7 @@ func set_flashlight(on: bool) -> void: _refresh_mode_label() func _refresh_mode_label() -> void: - mode_label.text = "Door: %s · Light: %s" % ["LOCKED" if _door_locked else "open", "on" if _flashlight else "off"] + mode_label.text = Strings.fmt("hud.mode", [Strings.lookup("hud.door_locked") if _door_locked else Strings.lookup("hud.door_open"), Strings.lookup("hud.light_on") if _flashlight else Strings.lookup("hud.light_off")]) if icon_row: flashlight_icon.visible = _flashlight door_icon.visible = _door_locked @@ -571,7 +571,7 @@ func show_inspection(controller: Object, artifacts: Array, result: Dictionary) - var profile: Object = controller.profile var npc_display: String = (profile.display_name if profile else "Person") var granted: bool = result.get("granted", false) - inspect_text.text = "ACCESS INSPECTION\n\nAccess check — %s\nGranted: %s" % [npc_display, "YES" if granted else "NO"] + inspect_text.text = Strings.fmt("hud.inspect_text", [npc_display, Strings.lookup("hud.yes") if granted else Strings.lookup("hud.no")]) _clear_card_row() var card_row := HBoxContainer.new() card_row.name = "INSPECT_CARD_ROW" @@ -599,9 +599,9 @@ func show_inspection(controller: Object, artifacts: Array, result: Dictionary) - for artifact in artifacts: lines.append(" • " + artifact.summary()) if result.get("reasons", []).size() > 0: - lines.append("Reasons: " + ", ".join(result.get("reasons", []))) + lines.append(Strings.fmt("hud.reasons", [", ".join(result.get("reasons", []))])) if result.get("issues", []).size() > 0: - lines.append("Issues: " + "; ".join(result.get("issues", []))) + lines.append(Strings.fmt("hud.issues", ["; ".join(result.get("issues", []))])) inspect_meta.text = "\n".join(lines) inspect_panel.visible = true if _inspect_art_layer: @@ -626,18 +626,18 @@ func show_wand(controller: Object, result: Dictionary) -> void: var profile: Object = controller.profile var npc_display: String = (profile.display_name if profile else "Person") var alert: bool = result.get("alert", false) - wand_text.text = "Wand check — %s\nAlert: %s" % [npc_display, "YES" if alert else "none"] + wand_text.text = Strings.fmt("hud.wand_text", [npc_display, Strings.lookup("hud.yes") if alert else Strings.lookup("hud.wand_none")]) var lines: Array[String] = [] if alert: var item: String = result.get("item", "") if result.get("prohibited", false): - lines.append("The wand alerts on a suspicious item (%s)." % item) - lines.append("Ask the guest to identify it before deciding.") + lines.append(Strings.fmt("hud.wand_suspicious", [item])) + lines.append(Strings.lookup("hud.wand_ask")) else: - lines.append("The wand alerts on a harmless item (%s)." % item) - lines.append("Likely a false alarm — keys, belt, or phone.") + lines.append(Strings.fmt("hud.wand_harmless", [item])) + lines.append(Strings.lookup("hud.wand_false")) else: - lines.append("No alert. The guest appears clear.") + lines.append(Strings.lookup("hud.wand_clear")) wand_meta.text = "\n".join(lines) wand_panel.visible = true if _wand_art_layer: diff --git a/src/game/ui/pause_overlay.gd b/src/game/ui/pause_overlay.gd index 14b5618..79a074c 100644 --- a/src/game/ui/pause_overlay.gd +++ b/src/game/ui/pause_overlay.gd @@ -10,6 +10,7 @@ signal settings_requested() signal quit_to_menu_requested() const UiArt := preload("res://src/game/ui/art.gd") +const Strings := preload("res://src/core/strings.gd") var _root: Control = null var _resume_button: Button = null @@ -45,18 +46,18 @@ func _build() -> void: center.add_child(column) var title := Label.new() - title.text = "Paused" + title.text = Strings.lookup("ui.pause_title") title.add_theme_font_size_override("font_size", 36) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER column.add_child(title) - _resume_button = _add_button(column, "Resume", "ResumeButton", func(): resume_requested.emit()) - _add_button(column, "Restart shift", "RestartButton", func(): restart_requested.emit()) - _add_button(column, "Settings", "SettingsButton", func(): settings_requested.emit()) - _add_button(column, "Quit to menu", "MenuButton", func(): quit_to_menu_requested.emit()) + _resume_button = _add_button(column, Strings.lookup("ui.pause_resume"), "ResumeButton", func(): resume_requested.emit()) + _add_button(column, Strings.lookup("ui.pause_restart"), "RestartButton", func(): restart_requested.emit()) + _add_button(column, Strings.lookup("ui.pause_settings"), "SettingsButton", func(): settings_requested.emit()) + _add_button(column, Strings.lookup("ui.pause_menu"), "MenuButton", func(): quit_to_menu_requested.emit()) var hint := Label.new() - hint.text = "Esc resumes" + hint.text = Strings.lookup("ui.pause_hint") hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER hint.add_theme_color_override("font_color", Color(0.55, 0.58, 0.65)) column.add_child(hint) diff --git a/src/game/ui/radio_panel.gd b/src/game/ui/radio_panel.gd index fd54e98..a9d5902 100644 --- a/src/game/ui/radio_panel.gd +++ b/src/game/ui/radio_panel.gd @@ -68,22 +68,22 @@ func _init() -> void: vbox.add_child(npc_name_label) location_edit = LineEdit.new() - location_edit.placeholder_text = "Location (e.g. front steps)" - location_edit.text = "front entrance" + location_edit.placeholder_text = Strings.lookup("ui.radio_location_placeholder") + location_edit.text = Strings.lookup("ui.radio_location_default") vbox.add_child(location_edit) desc_edit = LineEdit.new() - desc_edit.placeholder_text = "Describe the person and behavior" + desc_edit.placeholder_text = Strings.lookup("ui.radio_description_placeholder") vbox.add_child(desc_edit) weapons_check = CheckBox.new() - weapons_check.text = "Weapon seen" + weapons_check.text = Strings.lookup("ui.radio_weapon") vbox.add_child(weapons_check) var danger_row := HBoxContainer.new() vbox.add_child(danger_row) var danger_label := Label.new() - danger_label.text = "Danger level: " + danger_label.text = Strings.lookup("ui.radio_danger") danger_row.add_child(danger_label) danger_option = OptionButton.new() for i in 4: @@ -109,7 +109,7 @@ func _init() -> void: action_button.pressed.connect(_on_action_pressed) buttons.add_child(action_button) var cancel_btn := Button.new() - cancel_btn.text = "Cancel" + cancel_btn.text = Strings.lookup("ui.radio_cancel") cancel_btn.pressed.connect(func(): cancelled.emit()) buttons.add_child(cancel_btn) @@ -134,9 +134,9 @@ func open(controller: Object, emergency: bool, truth: Dictionary) -> void: _paint_backdrop() _truth = truth _npc_id = controller.npc_id if controller else "" - npc_name_label.text = "Target: %s" % (controller.profile.display_name if controller else "?") + npc_name_label.text = Strings.fmt("ui.radio_target", [controller.profile.display_name if controller else "?"]) var title: Label = panel.find_child("RadioTitle", true, false) - title.text = "EMERGENCY RADIO" if emergency else "RADIO DISPATCH" + title.text = Strings.lookup("ui.radio_title_emergency") if emergency else Strings.lookup("ui.radio_title_assist") weapons_check.button_pressed = truth.get("weapons_seen", false) danger_option.select(truth.get("danger_level", 1)) hold_hint.text = Strings.fmt("ui.radio_hold_hint", [HOLD_NEEDED]) if emergency else "" diff --git a/src/game/ui/settings_panel.gd b/src/game/ui/settings_panel.gd index b2ca8b3..543fe0f 100644 --- a/src/game/ui/settings_panel.gd +++ b/src/game/ui/settings_panel.gd @@ -68,7 +68,7 @@ func _build() -> void: center.add_child(column) var title := Label.new() - title.text = "Settings" + title.text = Strings.lookup("ui.settings_title") title.add_theme_font_size_override("font_size", 32) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER column.add_child(title) @@ -86,19 +86,19 @@ func _build() -> void: _font_scale.value_changed.connect(_on_font_scale_changed) _invert_y = CheckButton.new() - _invert_y.text = "Invert vertical look" + _invert_y.text = Strings.lookup("ui.settings_invert") _invert_y.toggled.connect(_on_invert_y_toggled) column.add_child(_invert_y) _head_bob = CheckButton.new() - _head_bob.text = "Head bob and camera motion" + _head_bob.text = Strings.lookup("ui.settings_head_bob") _head_bob.toggled.connect(_on_head_bob_toggled) column.add_child(_head_bob) var diff_row := HBoxContainer.new() column.add_child(diff_row) var diff_label := Label.new() - diff_label.text = "Difficulty / pacing" + diff_label.text = Strings.lookup("ui.settings_difficulty") diff_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL diff_row.add_child(diff_label) _difficulty = OptionButton.new() @@ -120,11 +120,11 @@ func _build() -> void: _ambience.value_changed.connect(func(v): settings["ambience_volume"] = v) _reduce_motion = CheckButton.new() - _reduce_motion.text = "Reduce motion" + _reduce_motion.text = Strings.lookup("ui.settings_reduce_motion") _reduce_motion.toggled.connect(func(v): settings["reduce_motion"] = v) column.add_child(_reduce_motion) _captions = CheckButton.new() - _captions.text = "Captions / subtitles" + _captions.text = Strings.lookup("ui.settings_captions") _captions.toggled.connect(func(v): settings["captions_enabled"] = v) column.add_child(_captions) @@ -133,17 +133,17 @@ func _build() -> void: column.add_child(replay_row) var replay := Button.new() replay.name = "ReplayTutorialButton" - replay.text = "Replay tutorial" + replay.text = Strings.lookup("ui.settings_replay") replay.pressed.connect(func(): replay_tutorial_requested.emit()) replay_row.add_child(replay) var reset_campaign := Button.new() reset_campaign.name = "ResetCampaignButton" - reset_campaign.text = "Reset campaign" + reset_campaign.text = Strings.lookup("ui.settings_reset_campaign") reset_campaign.pressed.connect(_confirm_reset_campaign) replay_row.add_child(reset_campaign) var controls_title := Label.new() - controls_title.text = "Controls (click a binding, then press a key or pad button)" + controls_title.text = Strings.lookup("ui.settings_controls_help") controls_title.add_theme_font_size_override("font_size", 13) controls_title.modulate = Color(0.75, 0.8, 0.9) column.add_child(controls_title) @@ -176,7 +176,7 @@ func _build() -> void: var cancel_button := Button.new() cancel_button.name = "CancelButton" - cancel_button.text = "Cancel" + cancel_button.text = Strings.lookup("ui.settings_cancel") cancel_button.pressed.connect(_on_cancel) row.add_child(cancel_button) @@ -270,7 +270,7 @@ func _on_difficulty_selected(index: int) -> void: func _confirm_reset_campaign() -> void: var dialog := ConfirmationDialog.new() dialog.title = "Reset campaign" - dialog.dialog_text = "Clear campaign progress and start again from the Hollywood shift?" + dialog.dialog_text = Strings.lookup("ui.settings_reset_confirm") dialog.confirmed.connect(func(): reset_campaign_requested.emit()) add_child(dialog) dialog.popup_centered() @@ -289,7 +289,7 @@ func _on_rebind(action: String) -> void: _listening_action = action var button: Button = _bind_buttons.get(action) if button: - button.text = "Press…" + button.text = Strings.lookup("ui.settings_press") if button.is_inside_tree(): button.grab_focus() diff --git a/src/game/ui/shift_review.gd b/src/game/ui/shift_review.gd index 224ddd7..a039983 100644 --- a/src/game/ui/shift_review.gd +++ b/src/game/ui/shift_review.gd @@ -38,7 +38,7 @@ func _init() -> void: root.add_child(center) var title := Label.new() - title.text = "Shift Review" + title.text = Strings.lookup("review.title") title.add_theme_font_size_override("font_size", 28) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER center.add_child(title) @@ -55,7 +55,7 @@ func _init() -> void: center.add_child(_campaign_label) _continue_button = Button.new() - _continue_button.text = "Continue campaign" + _continue_button.text = Strings.lookup("review.continue_campaign") _continue_button.pressed.connect(func(): continue_requested.emit()) _continue_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER center.add_child(_continue_button) @@ -126,28 +126,28 @@ func show_review(review: Dictionary, scenario: Resource, history: Array = [], var g: String = rep.get("grade", "?") if not past_grades.has(g): past_grades.append(g) - history_line = "Shifts on record: %d · best score: %.0f · grades seen: %s" % [ - history.size(), best, ", ".join(past_grades)] + history_line = Strings.fmt("review.history", [ + history.size(), best, ", ".join(past_grades)]) var lines: Array[String] = [ - "Venue: %s" % (scenario.display_name if scenario else review.get("scenario_id", "")), - "Policy: %s" % (scenario.policy_summary if scenario else "See venue policy."), - "Grade: %s Score: %.0f/100" % [reputation.get("grade", "?"), reputation.get("rating", 0.0)], + Strings.fmt("review.venue", [scenario.display_name if scenario else review.get("scenario_id", "")]), + Strings.fmt("review.policy", [scenario.policy_summary if scenario else "See venue policy."]), + Strings.fmt("review.grade", [reputation.get("grade", "?"), reputation.get("rating", 0.0)]), history_line, - "Incidents handled: %d (peaceful %d)" % [incident_list.size(), peaceful], - "NPCs encountered: %d" % review.get("spawned_count", 0), - "Radio battery: %.0f%% emergency cooldown: %.0fs" % [radio.get("battery", 0.0), radio.get("emergency_cooldown_s", 0.0)], - "Crowd mood: %.0f%% (panic: %s)" % [crowd.get("mood", 0.0) * 100.0, str(crowd.get("panic", false))], + Strings.fmt("review.incidents", [incident_list.size(), peaceful]), + Strings.fmt("review.npcs", [review.get("spawned_count", 0)]), + Strings.fmt("review.radio", [radio.get("battery", 0.0), radio.get("emergency_cooldown_s", 0.0)]), + Strings.fmt("review.crowd", [crowd.get("mood", 0.0) * 100.0, str(crowd.get("panic", false))]), Strings.fmt("review.responder", [", ".join(outcomes) if outcomes.size() > 0 else Strings.lookup("review.none")]), Strings.fmt("review.investigations", [" | ".join(investigations) if investigations.size() > 0 else Strings.lookup("review.none")]), "", - "Highlights:", + Strings.lookup("review.highlights"), ] var mods: Array = reputation.get("modifiers", []) var shown := 0 for mod in mods: var delta: float = float(mod.get("delta", 0.0)) - lines.append(" %s (%+.0f) %s" % [mod.get("category", ""), delta, mod.get("reason", "")]) + lines.append(Strings.fmt("review.modifier", [str(mod.get("category", "")), "%+.0f" % delta, str(mod.get("reason", ""))])) shown += 1 if shown >= 12: break @@ -156,11 +156,11 @@ func show_review(review: Dictionary, scenario: Resource, history: Array = [], var campaign_complete := bool(campaign_summary.get("complete", false)) if _campaign_label: if campaign_complete: - _campaign_label.text = "Campaign complete. Free-play venues remain available." + _campaign_label.text = Strings.lookup("review.campaign_complete") elif next_venue != "": - _campaign_label.text = "Campaign shift %d complete · next: %s" % [ + _campaign_label.text = Strings.fmt("review.campaign_next", [ int(campaign_summary.get("shift_number", 1)) - 1, - next_venue.replace("_", " ").capitalize()] + next_venue.replace("_", " ").capitalize()]) else: _campaign_label.text = "" if _continue_button: diff --git a/src/game/ui/start_menu.gd b/src/game/ui/start_menu.gd index 620ae1b..49ff69d 100644 --- a/src/game/ui/start_menu.gd +++ b/src/game/ui/start_menu.gd @@ -14,9 +14,9 @@ const UiArt := preload("res://src/game/ui/art.gd") const Strings := preload("res://src/core/strings.gd") const VENUES := [ - {"id": "hollywood_building", "label": "Hollywood Boulevard", "blurb": "Building entrance, tourists and late-night trouble."}, - {"id": "joke_shop", "label": "The Joke Shop", "blurb": "Guest list, IDs and wand screening."}, - {"id": "tv_studio", "label": "Late Night TV Studio", "blurb": "Credentials, filming boundary, celebrity arrivals."}, + {"id": "hollywood_building", "label_key": "venue.hollywood.label", "blurb_key": "venue.hollywood.blurb"}, + {"id": "joke_shop", "label_key": "venue.joke.label", "blurb_key": "venue.joke.blurb"}, + {"id": "tv_studio", "label_key": "venue.studio.label", "blurb_key": "venue.studio.blurb"}, ] var _index := 0 @@ -83,13 +83,13 @@ func _build() -> void: center.add_child(column) var title := Label.new() - title.text = "GIG LINE" + title.text = Strings.lookup("ui.start_title") title.add_theme_font_size_override("font_size", 44) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER column.add_child(title) var subtitle := Label.new() - subtitle.text = "Pick a venue to start your shift." + subtitle.text = Strings.lookup("ui.start_subtitle") subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER subtitle.add_theme_color_override("font_color", Color(0.72, 0.75, 0.82)) column.add_child(subtitle) @@ -112,7 +112,7 @@ func _build() -> void: var venue: Dictionary = VENUES[i] var button := Button.new() button.name = "Venue_%s" % venue["id"] - button.text = "%s\n%s" % [venue["label"], venue["blurb"]] + button.text = "%s\n%s" % [Strings.lookup(str(venue["label_key"])), Strings.lookup(str(venue["blurb_key"]))] button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.custom_minimum_size = Vector2(460, 62) button.pressed.connect(_on_venue_pressed.bind(i)) @@ -131,13 +131,13 @@ func _build() -> void: var help_button := Button.new() help_button.name = "HelpButton" - help_button.text = "Help" + help_button.text = Strings.lookup("ui.start_help") help_button.pressed.connect(func(): help_requested.emit()) row.add_child(help_button) var settings_button := Button.new() settings_button.name = "SettingsButton" - settings_button.text = "Settings" + settings_button.text = Strings.lookup("ui.start_settings") settings_button.pressed.connect(func(): settings_requested.emit()) row.add_child(settings_button) @@ -148,7 +148,7 @@ func _build() -> void: row.add_child(quit_button) var hint := Label.new() - hint.text = "Up/Down to choose, Enter to start, Esc to quit" + hint.text = Strings.lookup("ui.start_hint") hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER hint.add_theme_color_override("font_color", Color(0.55, 0.58, 0.65)) column.add_child(hint) @@ -199,20 +199,19 @@ func open(campaign: Dictionary = {}) -> void: var complete := bool(_campaign.get("complete", false)) if _status: if complete: - _status.text = "Campaign complete — pick any venue for a free shift." + _status.text = Strings.lookup("ui.campaign_complete") elif next_id != "": - _status.text = "Campaign shift %d · next: %s" % [ - int(_campaign.get("shift_number", 1)), _venue_label(next_id)] + _status.text = Strings.fmt("ui.campaign_status", [int(_campaign.get("shift_number", 1)), _venue_label(next_id)]) if _continue_button: var next_label := _venue_label(next_id) - _continue_button.text = "Continue shift — %s" % next_label if next_id != "" else "Campaign complete" + _continue_button.text = Strings.fmt("ui.continue_shift", [next_label]) if next_id != "" else Strings.lookup("ui.campaign_complete_short") _continue_button.visible = not complete _focus_index(_index) func _venue_label(id: String) -> String: for venue in VENUES: if str(venue["id"]) == id: - return str(venue["label"]) + return Strings.lookup(str(venue["label_key"])) return id.replace("_", " ").capitalize() func _on_continue_pressed() -> void: