fix(ui): adapt client to new ui-strings.yaml structure

The copy team rewrote ui-strings.yaml with multi-level nesting,
renamed sections, and inline comments. Update the YAML parser to
use a stack-based approach for arbitrary nesting depth, update HUD
and interaction prompt to reference the new key names, and align
tests with the new structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 18:07:15 +01:00
co-authored by Claude Opus 4.6
parent 15ea6f1375
commit 470ec59b63
4 changed files with 92 additions and 18 deletions
+29 -9
View File
@@ -47,11 +47,11 @@ func reload() -> void:
_loaded = false
_load_strings()
## Parse subset of YAML: one-level-nested key-value pairs.
## Returns flat Dictionary with dotted keys: { "section.key": "value" }.
## Parse YAML with arbitrary nesting depth.
## Returns flat Dictionary with dotted keys: { "section.sub.key": "value" }.
static func _parse_yaml(text: String) -> Dictionary:
var strings := {}
var current_section := ""
var stack: Array = [] # [[indent, key], ...]
for line in text.split("\n"):
var stripped := line.strip_edges(false, true)
if stripped.is_empty() or stripped.begins_with("#"):
@@ -63,10 +63,30 @@ static func _parse_yaml(text: String) -> Dictionary:
continue
var key := content.substr(0, colon_pos).strip_edges()
var val := content.substr(colon_pos + 1).strip_edges()
if val.length() >= 2 and val.begins_with("\"") and val.ends_with("\""):
val = val.substr(1, val.length() - 2)
if indent == 0:
current_section = key
elif indent >= 2 and not current_section.is_empty():
strings[current_section + "." + key] = val
# Trailing comment without a value — treat as section header
if val.begins_with("#"):
val = ""
# Pop sections at same or deeper indent
while stack.size() > 0 and stack.back()[0] >= indent:
stack.pop_back()
if val.is_empty():
# Section header — push onto stack
stack.push_back([indent, key])
else:
# Leaf value — extract from quotes or strip inline comment
if val.begins_with("\""):
var end_quote := val.find("\"", 1)
if end_quote > 0:
val = val.substr(1, end_quote - 1)
else:
val = val.substr(1)
else:
var comment_pos := val.find(" #")
if comment_pos >= 0:
val = val.substr(0, comment_pos).strip_edges()
var dotted_key := ""
for entry in stack:
dotted_key += entry[1] + "."
dotted_key += key
strings[dotted_key] = val
return strings
+51 -5
View File
@@ -54,19 +54,59 @@ func test_parse_multiple_keys_per_section() -> void:
assert_that(result["hud.health"]).is_equal("Health")
# -- YAML parser: nested sections --
func test_parse_nested_sections() -> void:
var yaml := "states:\n unknown:\n label: \"Unknown\"\n description: \"No prior contact.\""
var result := UIStrings._parse_yaml(yaml)
assert_that(result.has("states.unknown.label")).is_true()
assert_that(result["states.unknown.label"]).is_equal("Unknown")
assert_that(result["states.unknown.description"]).is_equal("No prior contact.")
func test_parse_nested_multiple_subsections() -> void:
var yaml := "states:\n a:\n x: \"1\"\n b:\n x: \"2\""
var result := UIStrings._parse_yaml(yaml)
assert_that(result["states.a.x"]).is_equal("1")
assert_that(result["states.b.x"]).is_equal("2")
func test_parse_mixed_flat_and_nested() -> void:
var yaml := "hud:\n health: \"Condition\"\n health_values:\n full: \"Normal\"\n critical: \"Critical\""
var result := UIStrings._parse_yaml(yaml)
assert_that(result["hud.health"]).is_equal("Condition")
assert_that(result["hud.health_values.full"]).is_equal("Normal")
assert_that(result["hud.health_values.critical"]).is_equal("Critical")
func test_parse_inline_comment_quoted() -> void:
var yaml := "hud:\n prefix: \"\" # No prefix"
var result := UIStrings._parse_yaml(yaml)
assert_that(result.has("hud.prefix")).is_true()
assert_that(result["hud.prefix"]).is_equal("")
func test_parse_inline_comment_unquoted() -> void:
var yaml := "hud:\n mode: Standard # default mode"
var result := UIStrings._parse_yaml(yaml)
assert_that(result["hud.mode"]).is_equal("Standard")
func test_parse_quoted_empty_string() -> void:
var yaml := "section:\n key: \"\""
var result := UIStrings._parse_yaml(yaml)
assert_that(result.has("section.key")).is_true()
assert_that(result["section.key"]).is_equal("")
# -- Autoload: registration and file loading --
func test_autoload_registered() -> void:
assert_that(UIStrings).is_not_null()
func test_autoload_loaded_strings() -> void:
assert_that(UIStrings.has_key("hud.mode_label")).is_true()
assert_that(UIStrings.has_key("hud.health")).is_true()
func test_get_text_returns_value() -> void:
assert_that(UIStrings.get_text("hud.mode_label")).is_equal("Mode")
assert_that(UIStrings.get_text("hud.health")).is_equal("Condition")
func test_get_text_interaction_prefix() -> void:
assert_that(UIStrings.get_text("interaction.prompt_prefix")).is_equal("E")
func test_get_text_interaction_verb() -> void:
assert_that(UIStrings.get_text("interaction_verbs.talk")).is_equal("Talk")
func test_get_text_missing_key_returns_key() -> void:
var result := UIStrings.get_text("nonexistent.key")
@@ -76,7 +116,13 @@ func test_get_all_keys_not_empty() -> void:
assert_that(UIStrings.get_all_keys().size()).is_greater(0)
func test_has_key_true_for_existing() -> void:
assert_that(UIStrings.has_key("interaction.talk")).is_true()
assert_that(UIStrings.has_key("interaction_verbs.talk")).is_true()
func test_has_key_false_for_missing() -> void:
assert_that(UIStrings.has_key("nonexistent.key")).is_false()
func test_nested_key_exists() -> void:
assert_that(UIStrings.has_key("relationship_states.unknown.label")).is_true()
func test_nested_key_value() -> void:
assert_that(UIStrings.get_text("relationship_states.unknown.label")).is_equal("Unknown")
+11 -3
View File
@@ -13,12 +13,20 @@ func _ready() -> void:
func update_from_hud_data(data: Dictionary) -> void:
# Update perception mode display
if data.has("perception_mode"):
perception_label.text = UIStrings.get_text("hud.mode_label") + ": " + data.perception_mode.capitalize()
var prefix := UIStrings.get_text("hud.perception_mode_prefix")
if prefix.is_empty():
perception_label.text = data.perception_mode.capitalize()
else:
perception_label.text = prefix + ": " + data.perception_mode.capitalize()
# Update time display (per D-031)
if data.has("time"):
time_label.text = UIStrings.get_text("hud.time_label") + ": " + data.time
var prefix := UIStrings.get_text("hud.time_prefix")
if prefix.is_empty():
time_label.text = data.time
else:
time_label.text = prefix + ": " + data.time
# Update player health (from player data, not hud_data)
func update_health(health: int) -> void:
health_label.text = UIStrings.get_text("hud.health_label") + ": " + str(health)
health_label.text = UIStrings.get_text("hud.health") + ": " + str(health)
+1 -1
View File
@@ -40,7 +40,7 @@ func _show_prompt(interaction: Dictionary) -> void:
# v0.1: pick first verb (sorted by priority from server)
var verb_label: String = verbs[0].get("label", "")
var display_text: String = "%s - %s" % [UIStrings.get_text("interaction.prompt_prefix"), verb_label]
var display_text: String = "E - %s" % verb_label
if _current_target_id != target_id or prompt_label.text != display_text:
prompt_label.text = display_text