Skip to content

Commit

Permalink
Roman namelist fixes (#181)
Browse files Browse the repository at this point in the history
* Add basic support for localization tags and variants

* Add support for roman numeral formatting in Stellaris localization

* Bump version to 6.5.3
  • Loading branch information
MichaelMakesGames authored Dec 22, 2024
1 parent b29d3e4 commit 91aa02e
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 10 deletions.
2 changes: 1 addition & 1 deletion mod/stellaris_dashboard/descriptor.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name="Stellaris Dashboard"
version="v6.5.2"
version="v6.5.3"
tags={
"Utilities"
"Gameplay"
Expand Down
2 changes: 1 addition & 1 deletion mod/stellaris_dashboard/interface/main_bottom.gui
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ guiTypes = {
pdx_tooltip = "STELLARIS_DASHBOARD_TOOLTIP"
pdx_tooltip_anchor_offset = { x= 0 y = @tt_offset_y }
pdx_tooltip_anchor_orientation = lower_left
web_link="http://127.0.0.1:28053/checkversion/v6.5.2"
web_link="http://127.0.0.1:28053/checkversion/v6.5.3"
}

iconType = {
Expand Down
2 changes: 1 addition & 1 deletion stellarisdashboard/dashboard_app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

logger = logging.getLogger(__name__)

VERSION = "v6.5.2"
VERSION = "v6.5.3"


def parse_version(version: str):
Expand Down
59 changes: 52 additions & 7 deletions stellarisdashboard/game_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def render_from_json(self, name_json: str):
logger.warning(f"Failed name: {name_json!r}")
return rendered

def render_from_dict(self, name_dict: dict) -> str:
def render_from_dict(self, name_dict: dict, tags: set[str] | None = None) -> str:
if not isinstance(name_dict, dict):
logger.warning(f"Expected name template dictionary, received {name_dict}")
return str(name_dict)
Expand All @@ -92,23 +92,31 @@ def render_from_dict(self, name_dict: dict) -> str:
render_template = self.name_mapping.get(key, key)
render_template = self._preprocess_template(render_template, name_dict)

# this handles tags and variants as documented in the Stellaris file localisation/99_README_GRAMMAR.txt
# note: this does not fully implement tag forwarding (section 6) or tag-sensitive text (section 7)
# but this seems to be "good enough" for our purposes of rendering names
if tags is None:
tags = set()
render_template, added_tags = self._process_tags_and_variants(render_template, tags)
tags.update(added_tags)

if "value" in name_dict:
return self.render_from_dict(name_dict["value"])
return self.render_from_dict(name_dict["value"], tags)

substitution_values = self._collect_substitution_values(name_dict)
substitution_values = self._collect_substitution_values(name_dict, tags)
render_template = self._substitute_variables(
render_template, substitution_values
)
render_template = self._handle_unresolved_variables(render_template)
return render_template

def _collect_substitution_values(self, name_dict):
def _collect_substitution_values(self, name_dict, tags: set[str]):
substitution_values = []
for var in name_dict.get("variables", []):
if "key" in var and "value" in var:
var_key = var.get("key")
substitution_values.append(
(var_key, self.render_from_dict(var["value"]))
(var_key, self.render_from_dict(var["value"], tags))
)
return substitution_values

Expand All @@ -132,6 +140,28 @@ def _preprocess_template(self, render_template, name_dict):
render_template = "$fmt$"

return render_template

def _process_tags_and_variants(self, render_template: str, tags: set[str]):
raw_variants = render_template.split("|||")
raw_variants.reverse() # variants are checked right-to-left
for variant in raw_variants:
# tags added
if "&!" in variant:
added_tags = set(variant[variant.index("&!") + 2:].split(","))
variant = variant[0:variant.index("&!")]
else:
added_tags = set()

# variants (other than first) have a comma-separated list of required tags, followed by a colon
if ":" in variant and variant != raw_variants[-1]:
required_tags = set(variant[0:variant.index(":")].split(","))
variant = variant[variant.index(":") + 1:]
else:
required_tags = set()

# all required tags must be present to use a variant
if tags.issuperset(required_tags):
return variant, added_tags

def _substitute_variables(self, render_template, substitution_values):
if render_template == "%ACRONYM%":
Expand All @@ -152,7 +182,9 @@ def _substitute_variables(self, render_template, substitution_values):
if subst_key == "num":
try:
render_template = render_template.replace(
f"$ORD$", self._fmt_ord_number(int(subst_value))
"$ORD$", self._fmt_ord_number(int(subst_value))
).replace(
"$R$", self._fmt_roman_number(int(subst_value))
)
except ValueError:
...
Expand Down Expand Up @@ -188,7 +220,7 @@ def _handle_unresolved_variables(self, render_template):

# Find variables that were not resolved so far:
for match in var_re.findall(render_template):
if match == "ORD":
if match == "ORD" or match == "R":
continue
resolved = lookup_key(match)
render_template = re.sub(rf"\${match}\$", resolved, render_template)
Expand All @@ -204,6 +236,19 @@ def _fmt_ord_number(self, num: int):
return f"{num}rd"
return f"{num}th"

def _fmt_roman_number(self, num: int):
ONES_SYMBOL = ("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX")
TENS_SYMBOL = ("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC")
HUNDREDS_SYMBOL = ("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM")
thousands = int(num / 1000)
num -= thousands * 1000
hundreds = int(num / 100)
num -= hundreds * 100
tens = int(num / 10)
num -= tens * 10
ones = num
return "M" * thousands + HUNDREDS_SYMBOL[hundreds] + TENS_SYMBOL[tens] + ONES_SYMBOL[ones]

def _fmt_adjective(self, noun: str) -> str:
# {'i': '*ian $1$', 'r': '*ran $1$', 'a': '*an $1$', 'e': '*an $1$', 'us': '*an $1$',
# 'is': '*an $1$', 'es': '*an $1$', 'ss': '*an $1$', 'id': '*an $1$', 'ed': '*an $1$',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
l_english:
HUMAN3_CHR_Aulus:1 "Aulus&!masc"
HUMAN3_CHR_Aula:0 "Aula&!fem"
HUMAN3_CHR_Aufidius:1 "$1$ Aufidia|||masc:$1$ Aufidius"

22 changes: 22 additions & 0 deletions test/names_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,28 @@ def test_name_rendering_with_game_files(test_case: NameTestcase):
expected="Quetzan Consolidated Worlds",
description="Nested %ADJECTIVE% and %ADJ% test",
),
NameTestcase(
dict(
key="%LEADER_2%",
variables=[
{"key": "1", "value": {"key": "HUMAN3_CHR_Aula"}},
{"key": "2", "value": {"key": "HUMAN3_CHR_Aufidius"}},
]
),
expected="Aula Aufidia",
description="Female tagged name"
),
NameTestcase(
dict(
key="%LEADER_2%",
variables=[
{"key": "1", "value": {"key": "HUMAN3_CHR_Aulus"}},
{"key": "2", "value": {"key": "HUMAN3_CHR_Aufidius"}},
]
),
expected="Aulus Aufidius",
description="Male tagged name"
)
],
ids=lambda tc: tc.description,
)
Expand Down

0 comments on commit 91aa02e

Please sign in to comment.