Files
draugnorak/scripts/generate_i18n_catalog.py

1202 lines
62 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple
ROOT = Path(__file__).resolve().parents[1]
TARGET_FUNCTION_ARGS = {
"speak_with_history": [0],
"screen_reader_speak": [0],
"notify": [0],
"learn_sounds_speak": [0],
"learn_sounds_add_description": [1],
"notifications_speak": [0],
"ui_info_box": [0, 1, 2],
"ui_question": [0, 1],
"ui_input_box": [0, 1],
"virtual_info_box": [0, 1, 2],
"virtual_question": [0, 1],
"virtual_input_box": [0, 1],
"text_reader": [1],
"text_reader_lines": [1],
"text_reader_file": [1],
}
INSERT_LAST_CONTEXT_HINTS = (
"option",
"label",
"line",
"prompt",
"instruction",
"intro",
"reward",
"message",
"title",
"menu",
)
PASS_THROUGH_TEXT_FUNCTIONS = {
"i18n_text",
"i18n_translate_speech_message",
}
SKIP_DIR_NAMES = {".git", "bloodshed", "docs", "skills", "nvgt-git"}
SKIP_FILE_NAMES = {"crash.log"}
def iter_nvgt_files() -> List[Path]:
files: List[Path] = []
entrypoints = [ROOT / "draugnorak.nvgt", ROOT / "src" / "sound_settings.nvgt"]
for entry in entrypoints:
if entry.exists():
files.append(entry)
source_roots = [ROOT / "src", ROOT / "libstorm-nvgt"]
for source_root in source_roots:
if not source_root.exists():
continue
for path in source_root.rglob("*.nvgt"):
rel = path.relative_to(ROOT)
if any(part in SKIP_DIR_NAMES for part in rel.parts):
continue
if path.name in SKIP_FILE_NAMES:
continue
files.append(path)
return sorted(set(files))
def is_identifier_char(ch: str) -> bool:
return ch.isalnum() or ch == "_"
def read_identifier_backward(text: str, before_index: int) -> str:
i = before_index
while i >= 0 and text[i].isspace():
i -= 1
end = i
while i >= 0 and is_identifier_char(text[i]):
i -= 1
start = i + 1
if end < start:
return ""
return text[start : end + 1]
def find_matching_paren(text: str, open_index: int) -> int:
depth = 0
in_string = False
escape = False
for i in range(open_index, len(text)):
ch = text[i]
if in_string:
if escape:
escape = False
elif ch == "\\":
escape = True
elif ch == '"':
in_string = False
continue
if ch == '"':
in_string = True
continue
if ch == "(":
depth += 1
continue
if ch == ")":
depth -= 1
if depth == 0:
return i
continue
return -1
def split_top_level(expr: str, delimiter: str) -> List[str]:
parts: List[str] = []
depth_paren = 0
depth_bracket = 0
depth_brace = 0
in_string = False
escape = False
start = 0
for i, ch in enumerate(expr):
if in_string:
if escape:
escape = False
elif ch == "\\":
escape = True
elif ch == '"':
in_string = False
continue
if ch == '"':
in_string = True
continue
if ch == "(":
depth_paren += 1
continue
if ch == ")":
depth_paren = max(0, depth_paren - 1)
continue
if ch == "[":
depth_bracket += 1
continue
if ch == "]":
depth_bracket = max(0, depth_bracket - 1)
continue
if ch == "{":
depth_brace += 1
continue
if ch == "}":
depth_brace = max(0, depth_brace - 1)
continue
if ch == delimiter and depth_paren == 0 and depth_bracket == 0 and depth_brace == 0:
parts.append(expr[start:i])
start = i + 1
parts.append(expr[start:])
return parts
def unescape_string_literal(literal_body: str) -> str:
result: List[str] = []
i = 0
while i < len(literal_body):
ch = literal_body[i]
if ch != "\\":
result.append(ch)
i += 1
continue
if i + 1 >= len(literal_body):
result.append("\\")
break
nxt = literal_body[i + 1]
if nxt == "n":
result.append("\n")
elif nxt == "r":
result.append("\r")
elif nxt == "t":
result.append("\t")
else:
result.append(nxt)
i += 2
return "".join(result)
def parse_string_literal_sequence(expr: str) -> Optional[str]:
expr = expr.strip()
if not expr or expr[0] != '"':
return None
parts: List[str] = []
i = 0
length = len(expr)
while i < length:
while i < length and expr[i].isspace():
i += 1
if i >= length:
break
if expr[i] != '"':
return None
i += 1
literal_chars: List[str] = []
escape = False
while i < length:
ch = expr[i]
if escape:
literal_chars.append("\\" + ch)
escape = False
i += 1
continue
if ch == "\\":
escape = True
i += 1
continue
if ch == '"':
i += 1
break
literal_chars.append(ch)
i += 1
else:
return None
parts.append(unescape_string_literal("".join(literal_chars)))
if not parts:
return None
return "".join(parts)
def expression_to_template(expr: str) -> Optional[str]:
expr = expr.strip()
if not expr:
return None
parts = split_top_level(expr, "+")
template_parts: List[str] = []
placeholder_count = 0
for raw_part in parts:
part = raw_part.strip()
if not part:
continue
passthrough_expr = unwrap_passthrough_expression(part)
if passthrough_expr is not None:
passthrough_template = expression_to_template(passthrough_expr)
if passthrough_template is not None:
template_parts.append(passthrough_template)
continue
literal_sequence = parse_string_literal_sequence(part)
if literal_sequence is not None:
template_parts.append(literal_sequence)
else:
placeholder_count += 1
template_parts.append(f"{{arg{placeholder_count}}}")
if not template_parts:
return None
template = "".join(template_parts)
if template.strip() == "":
return None
if template_literal_length(template) == 0:
return None
return template
def unwrap_passthrough_expression(expr: str) -> Optional[str]:
expr = expr.strip()
match = re.match(r"^([A-Za-z_][A-Za-z0-9_]*)\((.*)\)$", expr, re.DOTALL)
if not match:
return None
function_name = match.group(1)
if function_name not in PASS_THROUGH_TEXT_FUNCTIONS:
return None
arg_text = match.group(2)
args = split_top_level(arg_text, ",")
if not args:
return None
return args[0].strip()
def template_literal_length(template_text: str) -> int:
literal_count = 0
in_placeholder = False
for ch in template_text:
if not in_placeholder and ch == "{":
in_placeholder = True
continue
if in_placeholder and ch == "}":
in_placeholder = False
continue
if not in_placeholder:
literal_count += 1
return literal_count
def line_number_for_index(text: str, index: int) -> int:
return text.count("\n", 0, index) + 1
def add_entry(entries: Dict[str, Dict[str, object]], template: str, source_ref: str) -> None:
base_key = f"msg.{hashlib.sha1(template.encode('utf-8')).hexdigest()[:12]}"
key = base_key
suffix = 1
while key in entries and entries[key]["value"] != template:
key = f"{base_key}_{suffix}"
suffix += 1
if key not in entries:
entries[key] = {"value": template, "refs": [source_ref]}
else:
refs: List[str] = entries[key]["refs"] # type: ignore[assignment]
if source_ref not in refs:
refs.append(source_ref)
def extract_from_call(
entries: Dict[str, Dict[str, object]],
receiver: str,
function_name: str,
args: List[str],
source_ref_base: str,
) -> None:
target_arg_indexes: List[int] = []
if function_name in TARGET_FUNCTION_ARGS:
target_arg_indexes = TARGET_FUNCTION_ARGS[function_name]
elif function_name == "insert_last":
receiver_lower = receiver.lower()
if not any(hint in receiver_lower for hint in INSERT_LAST_CONTEXT_HINTS):
return
target_arg_indexes = [0]
else:
return
for arg_index in target_arg_indexes:
if arg_index >= len(args):
continue
template = expression_to_template(args[arg_index])
if not template:
continue
source_ref = f"{source_ref_base}:{function_name}[{arg_index}]"
add_entry(entries, template, source_ref)
def scan_file(path: Path, entries: Dict[str, Dict[str, object]]) -> None:
text = path.read_text(encoding="utf-8", errors="replace")
i = 0
while i < len(text):
ch = text[i]
if not is_identifier_char(ch):
i += 1
continue
start = i
while i < len(text) and is_identifier_char(text[i]):
i += 1
name = text[start:i]
j = i
while j < len(text) and text[j].isspace():
j += 1
if j >= len(text) or text[j] != "(":
continue
receiver = ""
k = start - 1
while k >= 0 and text[k].isspace():
k -= 1
if k >= 0 and text[k] == ".":
receiver = read_identifier_backward(text, k - 1)
close = find_matching_paren(text, j)
if close < 0:
break
arg_text = text[j + 1 : close]
args = split_top_level(arg_text, ",")
line = line_number_for_index(text, start)
rel_path = path.relative_to(ROOT).as_posix()
source_ref_base = f"{rel_path}:{line}"
extract_from_call(entries, receiver, name, args, source_ref_base)
i = close + 1
def slugify_fragment(value: str) -> str:
slug = re.sub(r"[^a-z0-9]+", "_", value.lower())
slug = slug.strip("_")
return slug or "value"
def add_item_registry_fragments(entries: Dict[str, Dict[str, object]]) -> None:
item_registry_path = ROOT / "src" / "item_registry.nvgt"
if not item_registry_path.exists():
return
text = item_registry_path.read_text(encoding="utf-8", errors="replace")
pattern = re.compile(
r"ItemDefinition\([^,]+,\s*\"([^\"]+)\",\s*\"([^\"]+)\",\s*\"([^\"]+)\"",
re.MULTILINE,
)
for match in pattern.finditer(text):
plural = match.group(1)
singular = match.group(2)
display = match.group(3)
plural_slug = slugify_fragment(plural)
singular_slug = slugify_fragment(singular)
display_slug = slugify_fragment(display)
add_entry(entries, plural, f"src/item_registry.nvgt:item_plural:{plural_slug}")
add_entry(entries, singular, f"src/item_registry.nvgt:item_singular:{singular_slug}")
add_entry(entries, display, f"src/item_registry.nvgt:item_display:{display_slug}")
def add_seed_messages(entries: Dict[str, Dict[str, object]]) -> None:
seeds = [
"Load Game (no saves found)",
"No saves found.",
"Unable to load save.",
"Unhandled exception",
"Really exit?",
"Found a stone.",
"Found a reed.",
"Found clay.",
]
for value in seeds:
add_entry(entries, value, "seed:manual")
def normalize_learn_sounds_label(sound_path: Path) -> str:
name = sound_path.stem.lower()
name = name.replace("_", " ").replace("-", " ")
name = re.sub(r"\s+", " ", name).strip()
return name or "sound"
def add_learn_sounds_label_seeds(entries: Dict[str, Dict[str, object]]) -> None:
sounds_dir = ROOT / "sounds"
if not sounds_dir.exists():
return
for path in sorted(sounds_dir.rglob("*")):
if not path.is_file():
continue
if path.suffix.lower() not in {".ogg", ".wav"}:
continue
label = normalize_learn_sounds_label(path)
rel = path.relative_to(ROOT).as_posix()
add_entry(entries, label, f"seed:learn_sounds_label:{rel}")
def escape_for_ini(value: str) -> str:
escaped = value.replace("\\", "\\\\")
escaped = escaped.replace("\n", "\\n")
escaped = escaped.replace("\r", "\\r")
escaped = escaped.replace("\t", "\\t")
return escaped
def write_catalog(entries: Dict[str, Dict[str, object]], output_path: Path) -> None:
output_path.parent.mkdir(parents=True, exist_ok=True)
manual_entries: List[Tuple[str, str]] = [
("meta.code", "en"),
("meta.name", "English"),
("meta.native_name", "English"),
("system.language.select_prompt", "Select your language."),
("system.language.selected", "Language set to {language}."),
("system.language.english_label", "English (en)"),
("system.ui.window_title", "Draugnorak"),
("system.main_menu.prompt", "Draugnorak. Main menu."),
("system.game.new_game_started", "New game started."),
("system.game.loaded", "Game loaded."),
("system.game.paused_prompt", "Paused. Press backspace to resume."),
("system.game.resumed", "Resumed."),
("system.exit.confirm", "Really exit?"),
("system.direction.west", "west"),
("system.direction.east", "east"),
("system.sex.male", "Male"),
("system.sex.female", "Female"),
("system.new_character.choose_sex", "Choose your sex."),
("system.new_character.enter_name", "Enter your name or press Enter for random."),
("system.new_character.save_exists_overwrite", "Save found for {name}. Overwrite?"),
("system.load_game.option_with_metadata", "{name}, {sex}, day {day}"),
("system.load_game.delete_confirm_base", "Are you sure you want to delete the character {name}?"),
("system.load_game.delete_confirm_with_metadata",
"Are you sure you want to delete the character {name} gender {sex} days {day}?"),
("system.load_game.delete_save_heading", "Delete Save"),
("system.load_game.delete_save_failed", "Unable to delete save."),
("system.load_game.save_deleted", "Save deleted."),
("system.load_game.no_saves_found", "No saves found."),
("system.load_game.unable_to_load", "Unable to load save."),
("system.load_game.select_prompt", "Load game. Select character. Press delete to remove a save."),
("system.quick_slot.no_item_bound", "No item bound to slot {slot}."),
("system.menu.closed", "Closed."),
("system.menu.canceled", "Canceled."),
("system.menu.no_options", "No options."),
("system.menu.no_matches", "No matches for {arg1}."),
("system.menu.main.story", "The story"),
("system.menu.main.language", "Language"),
("system.option.no", "No"),
("system.option.yes", "Yes"),
("system.inventory.cant_carry_any_more_item", "You can't carry any more {item}."),
("system.inventory.menu.prompt", "Inventory menu. {option}"),
("system.inventory.menu.no_options", "Inventory menu. No options."),
("system.inventory.option.personal", "Personal inventory"),
("system.inventory.option.base_storage", "Base storage"),
("system.inventory.count_set_to_slot", "{name} count set to slot {slot}."),
("system.inventory.need_quiver_for_arrows", "You need a quiver to carry arrows."),
("system.inventory.max_arrows_with_quivers", "You can only carry {max} arrows with your current quivers."),
("system.inventory.item_not_available", "Item not available."),
("system.search.found_item", "Found {item}."),
("system.search.found_nothing", "Found nothing."),
("system.pickup.item", "Picked up {item}."),
("system.time.period.am", "am"),
("system.time.period.pm", "pm"),
("system.time.report", "{hour} oclock {period} day {day}"),
("system.time.report_with_conditions", "{time}. {conditions}"),
("system.time.condition.sunny", "Sunny"),
("system.time.condition.daylight", "Daylight"),
("system.time.condition.dark", "Dark"),
("system.time.condition.windy", "Windy"),
("system.time.condition.raining", "Raining"),
("system.time.condition.storming", "Storming"),
("system.time.condition.moon_and_stars", "Moon and stars"),
("system.time.event.area_expanded", "The area has expanded! New territory discovered to the east."),
("system.time.event.mountain_discovered", "A mountain range has been discovered to the east!"),
("system.time.event.invasion_source_mountains", "the mountains"),
("system.time.event.invasion_source_new_area", "the new area"),
("system.time.event.invasion_start", "{enemy_plural} are invading from {source}!"),
("system.time.event.zombie_swarm_spotted", "A swarm of zombies has been spotted."),
("system.time.event.survivor_joins_one", "A survivor joins your base."),
("system.time.event.survivor_joins_many", "{count} survivors join your base."),
("system.time.event.base_max_capacity", "Your base is at maximum capacity."),
("system.time.event.invasion_ended", "The {enemy} invasion has ended."),
("system.time.event.blessing_speed_fades", "The speed blessing fades."),
("system.time.event.blessing_resident_fades", "The residents' purpose fades."),
("system.time.event.blessing_search_fades", "The eagle's sight fades."),
("system.time.event.sun_setting", "The sun is setting."),
("system.time.event.sky_brightening", "The sky begins to brighten."),
("system.time.event.residents_repaired_barricade", "Residents repaired the barricade. +{health} health."),
("system.time.event.incense_burned_out", "The incense has burned out."),
("system.combat.burning", "Burning! {health} health remaining."),
("system.combat.health_status", "{health} health."),
("system.combat.you_died", "You have died."),
("system.enemies.residents_killed_attacker", "Residents killed an attacking {enemy}."),
("system.enemies.resident_taken", "A resident has been taken."),
("system.action.fire.intensity.giant_blaze", "Giant blaze"),
("system.action.fire.intensity.small_fire", "Small fire"),
("system.action.fire.intensity.glowing_coals", "A few glowing coals"),
("system.action.fire.estimate.less_than_one_hour", "less than 1 hour"),
("system.action.fire.estimate.approximately_one_hour", "approximately 1 hour"),
("system.action.fire.estimate.approximately_hours", "approximately {hours} hours"),
("system.action.fire.status", "{label}. {estimate} remaining."),
("system.action.no_fire_nearby", "No fire nearby."),
("system.action.no_snare_nearby", "No snare nearby."),
("system.action.snare_holds", "Snare holds a {catch_type}."),
("system.action.snare_set_inactive", "Snare is set but not active yet."),
("system.action.snare_set_empty", "Snare is set and empty."),
("system.action.fish_on_line", "Fish on the line."),
("system.action.no_fish_on_line", "No fish on the line."),
("system.action.cannot_place_snares_in_base", "Cannot place snares in the base area."),
("system.action.snare_already_here", "There is already a snare here."),
("system.action.snare_set", "Snare set."),
("system.action.no_snares_to_place", "No snares to place."),
("system.action.feed_fire.stick", "You dump an arm load of sticks into the fire."),
("system.action.feed_fire.vine", "You toss a few vines and leaves into the fire."),
("system.action.feed_fire.log", "You heave a log into the fire."),
("system.action.no_altar_built", "No altar built."),
("system.action.need_clay_pot_for_incense", "You need a clay pot to burn incense."),
("system.action.no_incense_to_burn", "No incense to burn."),
("system.action.incense_burning", "Incense burning. {hours} hours remaining."),
("system.action.no_sticks_to_feed_fire", "No sticks to feed fire."),
("system.action.dumped_sticks", "Dumped {amount} sticks into the fire."),
("system.action.no_vines_to_feed_fire", "No vines to feed fire."),
("system.action.dumped_vines", "Dumped {amount} vines into the fire."),
("system.action.no_logs_to_feed_fire", "No logs to feed fire."),
("system.action.dumped_logs", "Dumped {amount} logs into the fire."),
("system.action.burned_incense", "Burned {amount} incense. +{hours} hours."),
("system.action.option.place_snare", "Place Snare"),
("system.action.option.feed_fire_stick", "Feed fire with stick"),
("system.action.option.feed_fire_vine", "Feed fire with vine"),
("system.action.option.feed_fire_log", "Feed fire with log"),
("system.action.option.burn_incense", "Burn incense"),
("system.action.option.check_fire", "Check fire"),
("system.action.option.check_snare", "Check snare"),
("system.action.option.check_fishing_pole", "Check fishing pole"),
("system.action.menu.prompt", "Action menu. {option}"),
("system.action.cant_do_that", "Can't do that."),
("system.base_info.not_in_base", "You are not in the base."),
("system.base_info.menu.no_options", "Base info. No options."),
("system.base_info.menu.prompt", "Base info. {option}"),
("system.base.barricade_fallen", "The barricade has fallen!"),
("system.base.no_food_residents_hungry", "No food, residents are hungry."),
("system.base.resident_fishing_pole_broke_one", "A resident's fishing pole broke."),
("system.base.resident_fishing_pole_broke_many", "{count} fishing poles broke."),
("system.base.resident_caught_fish_one", "Resident caught a fish and added it to storage."),
("system.base.resident_caught_fish_many", "Residents caught {count} fish and added them to storage."),
("system.base.resident_smoked_fish", "Resident smoked a fish into {yield} smoked fish."),
("system.base.livestock_produced",
"Livestock produced {meat} meat, {skins} skins, and {feathers} feathers and added to storage."),
("system.base.resident_spear_broke_one", "A resident's spear broke from wear."),
("system.base.resident_spear_broke_many", "{count} spears broke from wear."),
("system.base.resident_sling_broke_one", "A resident's sling broke from wear."),
("system.base.resident_sling_broke_many", "{count} slings broke from wear."),
("system.base.resident_bow_broke_one", "A resident's bow broke from wear."),
("system.base.resident_bow_broke_many", "{count} bows broke from wear."),
("system.base.resident_clothing_repair_one",
"A resident is mending clothing. Used {vines} vines, {skins} skins, and {down} down."),
("system.base.resident_clothing_repair_many",
"Residents are mending clothing. Used {vines} vines, {skins} skins, and {down} down."),
("system.base.snare_escape_during_check",
"A {catch_type} escaped while a resident checked the snare at x {position}."),
("system.base.resident_retrieved_from_snare",
"Resident retrieved {game_type} from snare at x {position} y 0 and reset it."),
("system.base.resident_knife_broke_butchering", "A resident's knife broke while butchering."),
("system.base.resident_butchered_result",
"Resident butchered {game_type}. Added {meat} meat, {skins} skins, {feathers} feathers, {down} down, {sinew} sinew to storage."),
("system.base.resident_added_to_storage", "Resident added {item} to storage."),
("system.base.resident_basket_broke_one", "A resident's basket broke."),
("system.base.resident_basket_broke_many", "{count} baskets broke."),
("system.base.resident_basket_broke_foraging_one", "A resident's basket broke while foraging."),
("system.base.resident_basket_broke_foraging_many", "{count} baskets broke while foraging."),
("system.base.resident_gathered_basket_food_one", "Resident gathered a basket of fruits and nuts."),
("system.base.resident_gathered_basket_food_many",
"Residents gathered {count} baskets of fruits and nuts."),
("system.fishing.size.small", "small"),
("system.fishing.size.medium", "medium sized"),
("system.fishing.size.large", "large"),
("system.fishing.size.monster", "monster"),
("system.fishing.pole_broke.default", "Your fishing pole broke."),
("system.fishing.pole_broke.switched_weapons", "You switched weapons and your fishing pole broke."),
("system.fishing.pole_broke.moved_fish_got_away", "You moved and the fish got away. Your fishing pole broke."),
("system.fishing.pole_broke.moved", "You moved and your fishing pole broke."),
("system.fishing.require_pole_equipped", "You need a fishing pole equipped."),
("system.fishing.require_near_stream", "You need to be within 2 tiles of a stream."),
("system.fishing.fish_on_line", "A fish is on the line!"),
("system.fishing.caught", "Caught a {size} {fish}."),
("system.fishing.fish_slipped_off_water", "The fish slipped off in the water."),
("system.fishing.fish_got_away", "The fish got away."),
("system.fishing.fish_still_on_line", "The fish is still on the line."),
("system.fishing.stopped", "You stop fishing."),
("system.altar.must_be_in_base", "Must be in base to use altar."),
("system.altar.no_altar_built", "No altar built."),
("system.altar.runed_cannot_sacrifice", "Runed items cannot be sacrificed."),
("system.altar.nothing_to_sacrifice", "Nothing to sacrifice."),
("system.altar.sacrificed_one", "Sacrificed 1 {item}. Favor +{gain}. Total {total}."),
("system.altar.sacrificed_many", "Sacrificed {count} {items}. Favor +{gain}. Total {total}."),
("system.altar.menu.prompt", "Altar. Favor {favor}."),
("system.storage.window_title", "Inventory"),
("system.storage.transfer_prompt", "{prompt} (max {max})"),
("system.storage.deposit_how_many", "Deposit how many?"),
("system.storage.withdraw_how_many", "Withdraw how many?"),
("system.storage.nothing_to_deposit", "Nothing to deposit."),
("system.storage.nothing_to_withdraw", "Nothing to withdraw."),
("system.storage.runed_cannot_deposit", "Runed items cannot be deposited into storage."),
("system.storage.item_full", "Storage for that item is full."),
("system.storage.no_storage_built", "No storage built."),
("system.storage.menu_title", "Base storage."),
("system.storage.menu.prompt", "Base storage. {option}"),
("system.storage.menu.no_options", "Base storage. No options."),
("system.storage.runed_item_name", "Runed {equipment} of {rune}"),
("system.storage.deposited_one", "Deposited {name}."),
("system.storage.deposited_many", "Deposited {amount} {item}."),
("system.storage.withdrew_one", "Withdrew {name}."),
("system.storage.withdrew_many", "Withdrew {amount} {item}."),
("system.crafting.require.fire_within_three_clay_pot",
"You need a fire within 3 tiles to craft a clay pot."),
("system.crafting.require.fire_within_three_clay_pots",
"You need a fire within 3 tiles to craft clay pots."),
("system.crafting.require.fire_within_three_bowstring",
"You need a fire within 3 tiles to make bowstring."),
("system.crafting.require.altar_incense", "You need an altar to craft incense."),
("system.crafting.require.fire_within_three_smoke_fish",
"You need a fire within 3 tiles to smoke fish."),
("system.crafting.require.fire_within_three_butcher", "You need a fire within 3 tiles to butcher."),
("system.character.slot.head", "head"),
("system.character.slot.torso", "torso"),
("system.character.slot.arms", "arms"),
("system.character.slot.hands", "hands"),
("system.character.slot.legs", "legs"),
("system.character.slot.feet", "feet"),
("system.character.menu.prompt", "Character info. {option}"),
("system.character.menu.no_options", "Character info. No options."),
("system.character.pet.no_pet", "No pet."),
("system.character.pet.abandon_confirm", "Really abandon your pet? {option}"),
("system.character.pet.unconscious_cannot_abandon",
"Your {pet} is unconscious. You can't abandon it right now."),
("system.character.info.name_sex", "Name {name}, {sex}"),
("system.character.info.name_unknown", "Name unknown"),
("system.character.info.health", "Health {health} of {max}"),
("system.character.info.weapon", "Weapon {weapon}"),
("system.character.info.clothing_equipped", "Clothing equipped: {items}"),
("system.character.info.no_clothing", "No clothing equipped."),
("system.character.info.missing", "Missing: {slots}"),
("system.character.info.favor", "Favor {favor}"),
("system.character.info.speed", "Speed {status}"),
("system.character.info.pet", "Pet {pet}, health {health} of {max_health}, loyalty {loyalty} of {max_loyalty}"),
("system.character.info.pet_knocked_out", "Knocked out for {hours} {hour_label}."),
("system.character.info.pet_none", "No pet."),
("system.character.word.hour", "hour"),
("system.character.word.hours", "hours"),
("system.pet.default_name", "pet"),
("system.pet.returned", "A {pet} returns to you."),
("system.pet.knocked_out", "Your {pet} has been knocked out."),
("system.pet.knocked_out_with_remaining", "Your {pet} has been knocked out. {hours} {hour_label} remaining."),
("system.pet.leaves", "{pet} leaves."),
("system.pet.leaving", "Your {pet} is leaving."),
("system.pet.on_the_way", "Your pet is on its way."),
("system.pet.hungry_unresponsive", "A {pet} is hungry and unresponsive."),
("system.pet.joins_you", "A {pet} joins you."),
("system.pet.offer.prompt", "A friendly looking {pet} begs for food. Accept?"),
("system.pet.offer.declined", "Declined."),
("system.pet.retrieve.cant_carry_chew_on",
"You can't carry a {item}, so you give it to a {pet} to chew on."),
("system.pet.retrieve.retrieved_item", "Your {pet} retrieved {item}."),
("system.pet.retrieve.random_find", "Your {pet} retrieved {count} {item}."),
("system.pet.recovered_from_injuries", "Your {pet} has recovered from its injuries."),
("system.pet.getting_hungry", "A {pet} is getting hungry."),
("system.fylgja.already_used_today", "You have already used your Fylgja today."),
("system.fylgja.menu.prompt", "Fylgja menu."),
("system.fylgja.need_open_ground_to_charge", "You need open ground in front of you to charge."),
("system.fylgja.option", "{name} Fylgja"),
("system.fylgja.connection_with_target", "You have a {stage} connection with the {target}."),
("system.fylgja.unlocked", "You have unlocked the {name} Fylgja!"),
("system.fylgja.already_unlocked", "You have already unlocked the {name} Fylgja."),
("system.fylgja.name.unicorn", "Unicorn"),
("system.fylgja.target.unicorn", "unicorn"),
("system.fylgja.stage.tenuous", "tenuous"),
("system.fylgja.stage.faint", "faint"),
("system.fylgja.stage.stirring", "stirring"),
("system.fylgja.stage.budding", "budding"),
("system.fylgja.stage.kindled", "kindled"),
("system.fylgja.stage.bound", "bound"),
("system.fylgja.stage.sworn", "sworn"),
("system.fylgja.stage.ascendant", "ascendant"),
("system.fylgja.stage.ultimate", "ultimate"),
("system.quest.name.bat_invasion", "Bat Invasion"),
("system.quest.name.catch_boomerang", "Catch the Boomerang"),
("system.quest.name.enchanted_melody", "Enchanted Melody"),
("system.quest.name.escape_from_hel", "Escape from Hel"),
("system.quest.name.skeletal_bard", "Skeletal Bard"),
("system.quest.name.unknown", "Unknown Quest"),
("system.quest.description.bat_invasion",
"Bat Invasion. Giant killer bats are attacking. Press space to throw when the bat is centered."),
("system.quest.description.catch_boomerang",
"Catch the Boomerang. Throw and catch the boomerang as it returns for up to 4 points."),
("system.quest.description.enchanted_melody",
"Enchanted Melody. Repeat the pattern using E R D F or U I J K. Lowest to highest pitch."),
("system.quest.description.escape_from_hel",
"Escape from Hel. Press space to jump over open graves. The pace quickens."),
("system.quest.description.skeletal_bard",
"Skeletal Bard. A skeleton named Billy Bones is practicing to become a bard. Count the notes."),
("system.quest.description.unknown", "Unknown quest."),
("system.quest.new_available", "A new quest is available: {quest}."),
("system.quest.none_available", "No quests available."),
("system.quest.favor_phrase.displeased", "The gods are displeased with your performance."),
("system.quest.favor_phrase.pleased", "The gods are pleased with your performance."),
("system.quest.favor_phrase.very_pleased", "The gods are very pleased with your performance."),
("system.quest.favor_phrase.extremely_pleased", "The gods are extremely pleased with your performance."),
("system.quest.favor_phrase.ultimately_pleased", "The gods are ultimately pleased with your performance."),
("system.quest.rewards.title", "Quest Rewards"),
("system.quest.rewards.complete_heading", "Quest Complete!"),
("system.quest.rewards.heading", "Rewards:"),
("system.quest.rewards.favor_line", "Favor: +{favor}"),
("system.quest.rewards.item_line", "{item}: +{amount}"),
("system.quest.rewards.inventory_full_line", "Inventory full, could not receive {item}."),
("system.quest.rewards.no_items_awarded", "No items awarded."),
("system.quest.rewards.score_line", "Score: {score}"),
("system.quest.menu.prompt", "Quest menu."),
("system.quest.menu.count", "{index} of {total}"),
("system.quest.minigame.starting", "Starting."),
("system.quest.bat_invasion.complete_score", "Bat invasion complete. Score {score}."),
("system.quest.boomerang.press_space_to_throw", "Press Space to throw."),
("system.quest.boomerang.skill.almost_dropped", "almost dropped"),
("system.quest.boomerang.skill.managed_to_catch", "managed to catch"),
("system.quest.boomerang.skill.caught", "caught"),
("system.quest.boomerang.skill.expertly_caught", "expertly caught"),
("system.quest.boomerang.caught_result", "You {skill} the boomerang. {points} points.{suffix}"),
("system.quest.boomerang.missed", "You missed the boomerang.{suffix}"),
("system.quest.boomerang.hit_you", "The boomerang hit you.{suffix}"),
("system.quest.boomerang.summary", "You caught {count} boomerangs for a total of {score} points."),
("system.quest.enchanted_melody.practice_prompt", "Practice mode. Press Enter to begin, Escape to cancel."),
("system.quest.enchanted_melody.starting_prompt", "Starting. Repeat the pattern."),
("system.quest.enchanted_melody.score", "You matched {rounds} notes. Score {score}."),
("system.quest.skeletal_bard.select_notes", "Select number of notes. {selection}."),
("system.quest.skeletal_bard.round_result",
"You entered {guess} notes and the actual number was {actual}. {points} points. Press Enter to continue."),
("system.quest.skeletal_bard.complete_score", "Skeletal Bard complete. Your score for all five tunes is {score}."),
("system.quest.escape_from_hel.fell_in_score", "You fell in. Score {score}."),
("system.adventure.none_in_base", "No adventures available in the base."),
("system.adventure.already_attempted_today", "You have already attempted an adventure today."),
("system.adventure.option.unicorn_hunt", "Unicorn Hunt (Mountain Boss)"),
("system.adventure.option.bandit_hideout", "Bandit's Hideout"),
("system.adventure.none_in_area", "No adventures found in this area."),
("system.adventure.menu.prompt", "Adventure menu."),
("system.adventure.victory", "Victory!"),
("system.adventure.rewards.title", "=== Victory Rewards ==="),
("system.adventure.unicorn.flee", "You fled from the unicorn."),
("system.adventure.unicorn.position_report",
"X {x}, {terrain}, facing {direction}"),
("system.adventure.unicorn.player_trampled", "The unicorn trampled you!"),
("system.adventure.unicorn.victory_title", "Unicorn Victory"),
("system.adventure.unicorn.reward.favor_awarded",
"The gods are pleased with your victory! {favor} favor awarded."),
("system.adventure.unicorn.reward.heal_scrolls", "Heal Scrolls: +{count}."),
("system.adventure.unicorn.reward.learned_rune_swiftness", "Learned Rune of Swiftness!"),
("system.adventure.unicorn.reward.engrave_unlocked",
"You can now engrave equipment with this rune at the crafting menu."),
("system.adventure.unicorn.reward.mastered_rune_swiftness",
"You have already mastered the Rune of Swiftness."),
("system.adventure.unicorn.horses.no_stable_or_storage",
"Stable or storage not built. No horses were captured."),
("system.adventure.unicorn.horses.stable_full", "Stable is full. No horses were captured."),
("system.adventure.unicorn.horses.joined_stable", "A horse joins your stable."),
("system.adventure.unicorn.horses.none_captured", "No horses were captured."),
("system.adventure.bandit_hideout.flee", "You fled from the bandit hideout."),
("system.adventure.bandit_hideout.position_report",
"X {x}, {terrain}. Distance to base {distance}. Facing {direction}. Base health {health} of {max}."),
("system.adventure.bandit_hideout.player_defeated", "You were defeated in the bandit hideout."),
("system.character.report.position", "{direction}, x {x}, y {y}, terrain {terrain}"),
("system.character.report.terrain_mountains", "{terrain}. Mountains"),
("system.character.report.player", "{health} of {max} health"),
("system.character.report.pet",
"{pet}: {health} of {max} health, loyalty {loyalty} of {max_loyalty}"),
("system.item.label.items", "items"),
("system.item.label.item", "item"),
("system.item.label.unknown", "Unknown"),
("system.equipment.menu.prompt", "Equipment menu. {option}"),
("system.equipment.menu.nothing_to_equip", "Nothing to equip."),
("system.equipment.menu.equipped_suffix", " (equipped)"),
("system.equipment.set_to_slot", "{name} set to slot {slot}."),
("system.equipment.equipped", "{name} equipped."),
("system.equipment.unequipped", "{name} unequipped."),
("system.equipment.name.none", "None"),
("system.equipment.name.unknown", "Unknown"),
("system.equipment.name.spear", "Spear"),
("system.equipment.name.stone_axe", "Stone Axe"),
("system.equipment.name.sling", "Sling"),
("system.equipment.name.bow", "Bow"),
("system.equipment.name.skin_hat", "Skin Hat"),
("system.equipment.name.skin_gloves", "Skin Gloves"),
("system.equipment.name.skin_pants", "Skin Pants"),
("system.equipment.name.skin_tunic", "Skin Tunic"),
("system.equipment.name.moccasins", "Moccasins"),
("system.equipment.name.skin_pouch", "Skin Pouch"),
("system.equipment.name.backpack", "Backpack"),
("system.equipment.name.fishing_pole", "Fishing Pole"),
("system.equipment.name_plural.spears", "Spears"),
("system.equipment.name_plural.stone_axes", "Stone Axes"),
("system.equipment.name_plural.slings", "Slings"),
("system.equipment.name_plural.bows", "Bows"),
("system.equipment.name_plural.skin_hats", "Skin Hats"),
("system.equipment.name_plural.skin_gloves", "Skin Gloves"),
("system.equipment.name_plural.skin_pants", "Skin Pants"),
("system.equipment.name_plural.skin_tunics", "Skin Tunics"),
("system.equipment.name_plural.moccasins", "Moccasins"),
("system.equipment.name_plural.skin_pouches", "Skin Pouches"),
("system.equipment.name_plural.backpacks", "Backpacks"),
("system.equipment.name_plural.fishing_poles", "Fishing Poles"),
("system.equipment.name_plural.items", "Items"),
("system.environment.tree.fell", "Tree fell!"),
("system.environment.tree.fell_with_loot",
"Tree fell! Got {sticks} {sticks_label}, {vines} {vines_label}, and {logs} {logs_label}."),
("system.environment.tree.inventory_full", "Inventory full."),
("system.environment.fall.damage_report",
"Fell {height} feet! Took {damage} damage. {health} health remaining."),
("system.environment.snare.collected_with_catch", "Collected {catch_type} and snare."),
("system.environment.snare.collected_empty", "Collected snare."),
("system.environment.tree.cut_down", "This tree has been cut down."),
("system.environment.tree.empty", "This tree is empty."),
("system.environment.area.nothing_left", "This area has nothing left."),
("system.environment.tree.climb_started", "Started climbing tree. Height is {height} feet."),
("system.environment.tree.climb_reached_top", "Reached the top at {height} feet."),
("system.environment.tree.climb_reached_ground", "Safely reached the ground."),
("system.environment.tree.climb_down", "Climbing down."),
("system.environment.movement.need_rope", "You'll need a rope to climb there."),
("system.environment.movement.press_up_climb", "Press up to climb up."),
("system.environment.movement.press_down_climb", "Press down to climb down."),
("system.environment.movement.need_canoe", "You need a canoe to cross deep water."),
("system.environment.rope_climb.direction.up", "up"),
("system.environment.rope_climb.direction.down", "down"),
("system.environment.rope_climb.progress", "Climbing {direction}. {distance} feet."),
("system.environment.rope_climb.reached_elevation", "Reached elevation {elevation}."),
("system.snare.escape_at", "A {catch_type} escaped from your snare at x {position} y 0!"),
("system.snare.caught_at", "{catch_type} caught in snare at x {position} y 0!"),
("system.snare.stepped_on_with_escape", "You stepped on your snare! The {catch_type} escaped."),
("system.snare.stepped_on_broke", "You stepped on your snare and broke it!"),
("system.fire.low_fuel_at", "Fire at x {position} y {y} is getting low!"),
("system.fire.went_out_at", "Fire at x {position} y {y} has gone out."),
("system.crafting.menu.prompt", "Crafting menu. {option}"),
("system.crafting.category.weapons", "Weapons"),
("system.crafting.category.tools", "Tools"),
("system.crafting.category.materials", "Materials"),
("system.crafting.category.clothing", "Clothing"),
("system.crafting.category.buildings", "Buildings"),
("system.crafting.category.barricade", "Barricade"),
("system.crafting.category.runes", "Runes"),
("system.crafting.in_progress", "Crafting..."),
("system.crafting.weapons.prompt", "Weapons. {option}"),
("system.crafting.weapons.option.spear", "Spear (1 Stick, 1 Vine, 1 Stone) [Requires Knife]"),
("system.crafting.weapons.option.sling", "Sling (1 Skin, 2 Vines)"),
("system.crafting.weapons.option.bow", "Bow (1 Stick, 1 Bowstring)"),
("system.crafting.weapons.crafted.spear", "Crafted a Spear."),
("system.crafting.weapons.crafted.sling", "Crafted a Sling."),
("system.crafting.weapons.crafted.bow", "Crafted a Bow."),
("system.crafting.weapons.crafted.stone_axe", "Crafted a Stone Axe."),
("system.crafting.weapons.crafted_max.spears", "Crafted {count} Spears."),
("system.crafting.weapons.crafted_max.slings", "Crafted {count} Slings."),
("system.crafting.weapons.crafted_max.bows", "Crafted {count} Bows."),
("system.crafting.weapons.crafted_max.stone_axes", "Crafted {count} Stone Axes."),
("system.crafting.tools.prompt", "Tools. {option}"),
("system.crafting.tools.option.stone_knife", "Stone Knife (2 Stones)"),
("system.crafting.tools.option.snare", "Snare (1 Stick, 2 Vines)"),
("system.crafting.tools.option.stone_axe", "Stone Axe (1 Stick, 1 Vine, 2 Stones) [Requires Knife]"),
("system.crafting.tools.option.fishing_pole", "Fishing Pole (1 Stick, 2 Vines)"),
("system.crafting.tools.option.rope", "Rope (3 Vines)"),
("system.crafting.tools.option.quiver", "Quiver (2 Skins, 2 Vines)"),
("system.crafting.tools.option.canoe", "Canoe (4 Logs, 11 Sticks, 11 Vines, 6 Skins, 2 Rope, 6 Reeds)"),
("system.crafting.tools.option.reed_basket", "Reed Basket (3 Reeds)"),
("system.crafting.tools.option.clay_pot", "Clay Pot (3 Clay)"),
("system.crafting.tools.crafted.stone_knife", "Crafted a Stone Knife."),
("system.crafting.tools.crafted.snare", "Crafted a Snare."),
("system.crafting.tools.crafted.fishing_pole", "Crafted a Fishing Pole."),
("system.crafting.tools.crafted.rope", "Crafted rope."),
("system.crafting.tools.crafted.quiver", "Crafted a Quiver."),
("system.crafting.tools.crafted.canoe", "Crafted a Canoe."),
("system.crafting.tools.crafted.reed_basket", "Crafted a reed basket."),
("system.crafting.tools.crafted.clay_pot", "Crafted a clay pot."),
("system.crafting.tools.crafted_max.stone_knives", "Crafted {count} Stone Knives."),
("system.crafting.tools.crafted_max.snares", "Crafted {count} Snares."),
("system.crafting.tools.crafted_max.fishing_poles", "Crafted {count} Fishing Poles."),
("system.crafting.tools.crafted_max.ropes", "Crafted {count} Rope."),
("system.crafting.tools.crafted_max.quivers", "Crafted {count} Quivers."),
("system.crafting.tools.crafted_max.canoes", "Crafted {count} Canoes."),
("system.crafting.tools.crafted_max.reed_baskets", "Crafted {count} Reed Baskets."),
("system.crafting.tools.crafted_max.clay_pots", "Crafted {count} Clay Pots."),
("system.crafting.materials.prompt", "Materials. {option}"),
("system.crafting.materials.option.butcher_game", "Butcher Game [Requires Game, Knife, Fire nearby]"),
("system.crafting.materials.option.smoke_fish", "Smoke Fish (1 Fish, 1 Stick) [Requires Fire nearby]"),
("system.crafting.materials.option.arrows", "Arrows (2 Sticks, 4 Feathers, 2 Stones) [Requires Quiver]"),
("system.crafting.materials.option.bowstring", "Bowstring (3 Sinew) [Requires Fire nearby]"),
("system.crafting.materials.option.incense", "Incense (6 Sticks, 2 Vines, 1 Reed) [Requires Altar]"),
("system.crafting.materials.not_enough_quiver_capacity_for_arrows", "Not enough quiver capacity for arrows."),
("system.crafting.materials.crafted.arrows", "Crafted {count} arrows."),
("system.crafting.materials.crafted.bowstring", "Crafted a bowstring."),
("system.crafting.materials.crafted_max.bowstrings", "Crafted {count} Bowstrings."),
("system.crafting.materials.crafted.incense", "Crafted incense."),
("system.crafting.materials.crafted_max.incense", "Crafted {count} Incense."),
("system.crafting.materials.smoke_fish.crafted", "Smoked a fish into {yield} smoked fish."),
("system.crafting.materials.smoke_fish.crafted_max",
"Smoked {count} fish into {yield} smoked fish."),
("system.crafting.materials.butcher.goose", "Butchered goose. Got 1 meat, feathers, and down."),
("system.crafting.materials.butcher.turkey", "Butchered turkey. Got 1 meat and feathers."),
("system.crafting.materials.butcher.boar", "Butchered boar. Got meat, 3 skins, and 2 sinew."),
("system.crafting.materials.butcher.default", "Butchered {game}. Got 1 meat and 1 skin."),
("system.crafting.materials.butcher.no_space_outputs", "No space for outputs."),
("system.crafting.materials.butcher.max_result",
"Butchered {count} game. Got {meat} meat, {skins} skins, {feathers} feathers, {down} down, {sinew} sinew."),
("system.crafting.clothing.prompt", "Clothing. {option}"),
("system.crafting.clothing.option.skin_hat", "Skin Hat (1 Skin, 1 Vine)"),
("system.crafting.clothing.option.skin_gloves", "Skin Gloves (1 Skin, 1 Vine)"),
("system.crafting.clothing.option.skin_pants", "Skin Pants (6 Skins, 3 Vines)"),
("system.crafting.clothing.option.skin_tunic", "Skin Tunic (4 Skins, 2 Vines)"),
("system.crafting.clothing.option.moccasins", "Moccasins (2 Skins, 1 Vine)"),
("system.crafting.clothing.option.skin_pouch", "Skin Pouch (2 Skins, 1 Vine)"),
("system.crafting.clothing.option.backpack", "Backpack (11 Skins, 5 Vines, 4 Skin Pouches)"),
("system.crafting.clothing.crafted.skin_hat", "Crafted a Skin Hat."),
("system.crafting.clothing.crafted.skin_gloves", "Crafted Skin Gloves."),
("system.crafting.clothing.crafted.skin_pants", "Crafted Skin Pants."),
("system.crafting.clothing.crafted.skin_tunic", "Crafted a Skin Tunic."),
("system.crafting.clothing.crafted.moccasins", "Crafted moccasins."),
("system.crafting.clothing.crafted.skin_pouch", "Crafted a Skin Pouch."),
("system.crafting.clothing.crafted.backpack", "Crafted a Backpack."),
("system.crafting.clothing.crafted_max.skin_hats", "Crafted {count} Skin Hats."),
("system.crafting.clothing.crafted_max.skin_gloves", "Crafted {count} Skin Gloves."),
("system.crafting.clothing.crafted_max.skin_pants", "Crafted {count} Skin Pants."),
("system.crafting.clothing.crafted_max.skin_tunics", "Crafted {count} Skin Tunics."),
("system.crafting.clothing.crafted_max.moccasins", "Crafted {count} Moccasins."),
("system.crafting.clothing.crafted_max.skin_pouches", "Crafted {count} Skin Pouches."),
("system.crafting.clothing.crafted_max.backpacks", "Crafted {count} Backpacks."),
("system.crafting.buildings.prompt", "Buildings. {option}"),
("system.crafting.buildings.option.firepit", "Firepit (9 Stones)"),
("system.crafting.buildings.option.fire", "Fire (2 Sticks, 1 Log) [Requires Firepit]"),
("system.crafting.buildings.option.herb_garden", "Herb Garden (9 Stones, 3 Vines, 2 Logs) [Base Only]"),
("system.crafting.buildings.option.storage_upgrade_1",
"Upgrade Storage (6 Logs, 9 Stones, 8 Vines) [Base Only, 50 each]"),
("system.crafting.buildings.option.storage_upgrade_2",
"Upgrade Storage (12 Logs, 18 Stones, 16 Vines) [Base Only, 100 each]"),
("system.crafting.buildings.option.pasture", "Pasture (8 Logs, 18 Ropes) [Base Only, Requires Storage Upgrade]"),
("system.crafting.buildings.option.stable",
"Stable (10 Logs, 15 Stones, 10 Vines) [Base Only, Requires Storage Upgrade]"),
("system.crafting.buildings.option.altar", "Altar (9 Stones, 3 Sticks) [Base Only]"),
("system.crafting.buildings.none_available", "No buildings available."),
("system.crafting.buildings.firepit.already_in_base", "There is already a firepit in the base."),
("system.crafting.buildings.firepit.already_here", "There is already a firepit here."),
("system.crafting.buildings.firepit.built", "Firepit built here."),
("system.crafting.buildings.fire.requires_firepit_nearby", "You need a firepit within 2 tiles to build a fire."),
("system.crafting.buildings.fire.already_in_base", "There is already a fire in the base."),
("system.crafting.buildings.fire.built", "Fire built at firepit."),
("system.crafting.buildings.herb_garden.base_only", "Herb garden can only be built in the base area."),
("system.crafting.buildings.herb_garden.already_in_base", "There is already an herb garden in the base."),
("system.crafting.buildings.herb_garden.built", "Herb garden built. The base now heals faster."),
("system.crafting.buildings.storage.base_only", "Storage must be built in the base."),
("system.crafting.buildings.storage.fully_upgraded", "Storage is fully upgraded."),
("system.crafting.buildings.storage.upgraded", "Storage upgraded. Capacity is now {capacity} per item."),
("system.crafting.buildings.pasture.base_only", "Pasture must be built in the base."),
("system.crafting.buildings.pasture.requires_storage_upgrade", "Storage must be upgraded before a pasture."),
("system.crafting.buildings.pasture.already_built", "Pasture already built."),
("system.crafting.buildings.pasture.built", "Pasture built."),
("system.crafting.buildings.stable.base_only", "Stable must be built in the base."),
("system.crafting.buildings.stable.requires_storage_upgrade", "Storage must be upgraded before a stable."),
("system.crafting.buildings.stable.already_built", "Stable already built."),
("system.crafting.buildings.stable.built", "Stable built."),
("system.crafting.buildings.altar.base_only", "Altar must be built in the base."),
("system.crafting.buildings.altar.already_built", "Altar already built."),
("system.crafting.buildings.altar.built", "Altar built."),
("system.crafting.runes.base_only", "Rune engraving can only be done in the base area."),
("system.crafting.runes.option", "{rune} (1 Clay, 1 Favor) [Requires Knife]"),
("system.crafting.runes.none_unlocked", "No runes unlocked yet."),
("system.crafting.runes.prompt", "Runes. {option}"),
("system.crafting.runes.equipment_option", "{name} ({count} available)"),
("system.crafting.runes.no_equipment_available", "No equipment available to engrave."),
("system.crafting.runes.select_equipment_prompt",
"Select equipment to engrave with {rune}. {option}"),
("system.crafting.runes.no_item_available", "No {item} available."),
("system.crafting.runes.runed_name", "Runed {equipment} of {rune}"),
("system.crafting.runes.engraved", "Engraved {item}."),
("system.crafting.runes.engraved_max", "Engraved {count} {item} with {rune}."),
("system.crafting.barricade.prompt", "Barricade. {option}"),
("system.crafting.barricade.option.reinforce_sticks",
"Reinforce with sticks ({cost} sticks, +{health} health)"),
("system.crafting.barricade.option.reinforce_vines", "Reinforce with vines ({cost} vines, +{health} health)"),
("system.crafting.barricade.option.reinforce_log", "Reinforce with log ({cost} log, +{health} health)"),
("system.crafting.barricade.option.reinforce_stones",
"Reinforce with stones ({cost} stones, +{health} health)"),
("system.crafting.barricade.full_health", "Barricade is already at full health."),
("system.crafting.barricade.no_materials", "No materials to reinforce the barricade."),
("system.crafting.barricade.reinforced.sticks",
"Reinforced barricade with sticks. +{gained} health. Now {health} of {max}."),
("system.crafting.barricade.reinforced.vines",
"Reinforced barricade with vines. +{gained} health. Now {health} of {max}."),
("system.crafting.barricade.reinforced.log",
"Reinforced barricade with log. +{gained} health. Now {health} of {max}."),
("system.crafting.barricade.reinforced.stones",
"Reinforced barricade with stones. +{gained} health. Now {health} of {max}."),
("system.crafting.barricade.reinforced_max.sticks",
"Reinforced barricade {count} times with sticks. Health now {health}."),
("system.crafting.barricade.reinforced_max.vines",
"Reinforced barricade {count} times with vines. Health now {health}."),
("system.crafting.barricade.reinforced_max.log",
"Reinforced barricade {count} times with log. Health now {health}."),
("system.crafting.barricade.reinforced_max.stones",
"Reinforced barricade {count} times with stones. Health now {health}."),
("system.crafting.barricade.not_enough.sticks", "Not enough sticks."),
("system.crafting.barricade.not_enough.vines", "Not enough vines."),
("system.crafting.barricade.not_enough.logs", "Not enough logs."),
("system.crafting.barricade.not_enough.stones", "Not enough stones."),
("system.crafting.missing", "Missing: {requirements}"),
("system.crafting.requirement.stone_knife", "Stone Knife"),
("system.crafting.requirement.game", "Game"),
("system.crafting.requirement.favor", "favor"),
("system.crafting.requirement.resources", "resources"),
("system.story.voice.format", "{speaker}: {line}"),
("system.story.god.fallback", "A god"),
("system.story.god.odin", "Odin"),
("system.story.god.thor", "Thor"),
("system.story.god.freyja", "Freyja"),
("system.story.god.loki", "Loki"),
("system.story.god.tyr", "Tyr"),
("system.story.god.baldur", "Baldur"),
("system.story.god.frigg", "Frigg"),
("system.story.god.heimdall", "Heimdall"),
("system.story.god.hel", "Hel"),
("system.story.god.fenrir", "Fenrir"),
("system.story.god.freyr", "Freyr"),
("system.story.line01", "The world has fallen."),
("system.story.line02", "Once-great cities lie in ruin."),
("system.story.line03", "In most places, not two stones remain as mankind set them."),
("system.story.line04", "The gods, long thought sleeping, achieved the impossible."),
("system.story.line05", "Ragnarok was averted."),
("system.story.line06", "Great Fenrir was freed and did not strike down his captors."),
("system.story.line07", "Surtr did not lay waste to our world with his flaming greatsword."),
("system.story.line08", "For a fleeting age, the gods wrestled fate into silence."),
("system.story.line09", "But the end of the world was delayed, never denied."),
("system.story.line10", "One force lay beyond even divine command: mankind."),
("system.story.line11", "Hatred swelled."),
("system.story.line12", "Greed answered it."),
("system.story.line13", "Mercy withered."),
("system.story.line14", "Differences became crimes."),
("system.story.line15", "Folk no longer argued; they tore at one another like beasts."),
("system.story.line16", "Neighbors became enemies for trifles: a color, a song, a single word."),
("system.story.line17", "War followed."),
("system.story.line18", "Cities burned, roads broke, and monsters wearing human faces roamed the streets."),
("system.story.line19", "Then ruin deepened into terror."),
("system.story.line20", "The dead, enraged by wanton slaughter, rose from their graves."),
("system.story.line21", "Corpses woke and hunted the living."),
("system.story.line22", "Night-stalkers fed on blood."),
("system.story.line23", "Even ash and dust remembered form and rose again."),
("system.story.line24", "Soon all that remained was rubble and bone."),
("system.story.line25", "Your last memory is flight from a vast horde of nightmare creatures."),
("system.story.line26", "When you wake, you are somewhere strange."),
("system.story.line27",
"A crude wooden shelter stands against the wind, open to the elements, braced with sticks and logs."),
("system.story.line28", "As your senses return, faint voices stir in the dark."),
("system.story.line29", "After the death of this world, another shall rise."),
("system.story.line30", "You have been judged worthy to try and survive."),
("system.story.line31", "This world belongs to the dead."),
("system.story.line32", "In time, they will slay even us."),
("system.story.line33", "Prove yourself, warrior, and you may walk into the next world."),
("system.story.line34", "May your death, when it comes, be honorable."),
("system.story.line35", "We will aid you while we still have strength, but time is against us."),
("system.story.line36",
"Beyond your shelter lies grassland, and farther off, broken stone that may once have been a city."),
("system.story.line37", "In the total devastation, even memory has lost its shape."),
]
lines: List[str] = []
lines.append("; Draugnorak localization catalog")
lines.append("; Copy this file to lang/<code>.ini and translate only the right-hand values.")
lines.append("; Keep keys unchanged. Use placeholders like {arg1}, {count}, {language} exactly as written.")
lines.append("")
lines.append("[meta]")
for key, value in manual_entries[:3]:
lines.append(f"{key.split('.', 1)[1]}={escape_for_ini(value)}")
lines.append("")
lines.append("[system]")
for key, value in manual_entries[3:]:
lines.append(f"{key.split('.', 1)[1]}={escape_for_ini(value)}")
lines.append("")
lines.append("[messages]")
for key in sorted(entries.keys()):
value = entries[key]["value"]
refs: List[str] = entries[key]["refs"] # type: ignore[assignment]
for ref in refs[:3]:
lines.append(f"; {ref}")
lines.append(f"{key}={escape_for_ini(str(value))}")
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> None:
entries: Dict[str, Dict[str, object]] = {}
for file_path in iter_nvgt_files():
scan_file(file_path, entries)
add_item_registry_fragments(entries)
add_seed_messages(entries)
add_learn_sounds_label_seeds(entries)
en_path = ROOT / "lang" / "en.ini"
template_path = ROOT / "lang" / "en.template.ini"
write_catalog(entries, en_path)
write_catalog(entries, template_path)
print(f"Wrote {en_path.relative_to(ROOT)} with {len(entries)} generated entries")
print(f"Wrote {template_path.relative_to(ROOT)}")
if __name__ == "__main__":
main()