diff --git a/main.py b/main.py index 331a548..9d41834 100644 --- a/main.py +++ b/main.py @@ -21,6 +21,7 @@ from yacs.config import CfgNode as CN from rf4s import config, utils +from rf4s.i18n import setup as setup_i18n, t from rf4s.app import ( BotApp, CalculateApp, @@ -43,48 +44,44 @@ # https://patorjk.com/software/taag/#p=testall&f=3D-ASCII&t=RF4S%0A, ANSI Shadow FEATURES = ( - {"name": "Fishing Bot", "command": "bot"}, - {"name": "Craft Items", "command": "craft"}, - {"name": "Move Forward", "command": "move"}, - {"name": "Harvest Baits", "command": "harvest"}, - {"name": "Auto Friction Brake", "command": "frictionbrake"}, - {"name": "Calculate Tackle's Stats", "command": "calculate"}, + {"name_key": "feature.bot", "command": "bot"}, + {"name_key": "feature.craft", "command": "craft"}, + {"name_key": "feature.move", "command": "move"}, + {"name_key": "feature.harvest", "command": "harvest"}, + {"name_key": "feature.frictionbrake", "command": "frictionbrake"}, + {"name_key": "feature.calculate", "command": "calculate"}, ) BOT_BOOLEAN_ARGUMENTS = ( - ("t", "tag", "keep only tagged fishes"), - ("c", "coffee", "drink coffee if stamina is low during fish fight"), - ("a", "alcohol", "drink alcohol before keeping the fish"), - ("r", "refill", "consume tea and carrot if hunger or comfort is low"), - ("H", "harvest", "harvest baits before casting the rod"), - ("L", "lure", "change current lure with a random favorite one, mode: spin"), - ("m", "mouse", "move mouse randomly before casting the rod"), - ("P", "pause", "pause the script before casting the rod occasionally"), - ("RC", "random-cast", "do a redundant rod cast randomly"), - ("SC", "skip-cast", "skip the first rod cast"), - ("l", "lift", "lift the tackle constantly during a fish fight"), - ("e", "electro", "enable electric mode for Electro Raptor series reel"), - ("FB", "friction-brake", "adjust friction brake automatically"), - ("GR", "gear-ratio", "switch the gear ratio or mode after the retrieval timed out"), - ("b", "bite", "save a screenshot in screenshots/ when a fish bite"), - ("s", "screenshot", "save a screenshot in screenshots/ after you caught a fish"), - ("d", "data", "save fishing data in /logs"), - ("E", "email", "send email noticication after the script stop"), - ("M", "miaotixing", "send miaotixing notification after the script stop"), - ("D", "discord", "send Discord notification after the script stop"), - ("TG", "telegram", "send Telegram notification after the script stop"), - ("S", "shutdown", "shutdown computer after the script stop"), - ("SO", "signout", "sign out instead of closing the game"), - ("BL", "broken-lure", "replace broken lures with favorite ones"), - ("SR", "spod-rod", "recast spod rod"), - ("DM", "dry-mix", "enable dry mix refill, mode: bottom"), - ("GB", "groundbait", "enable groundbait refill, mode: bottom"), - ("PVA", "pva", "enable pva refill, mode: bottom"), - ( - "NA", - "no-animation", - "disable waiting for trophy and gift animations, gift\nchange 'Catch screen style' to 'Simple' in game settings to use this flag", - ), + ("t", "tag", "help.tag"), + ("c", "coffee", "help.coffee"), + ("a", "alcohol", "help.alcohol"), + ("r", "refill", "help.refill"), + ("H", "harvest", "help.harvest_arg"), + ("L", "lure", "help.lure"), + ("m", "mouse", "help.mouse"), + ("P", "pause", "help.pause"), + ("RC", "random-cast", "help.random_cast"), + ("SC", "skip-cast", "help.skip_cast"), + ("l", "lift", "help.lift"), + ("e", "electro", "help.electro"), + ("FB", "friction-brake", "help.friction_brake"), + ("GR", "gear-ratio", "help.gear_ratio"), + ("b", "bite", "help.bite"), + ("s", "screenshot", "help.screenshot"), + ("d", "data", "help.data"), + ("E", "email", "help.email"), + ("M", "miaotixing", "help.miaotixing"), + ("D", "discord", "help.discord"), + ("TG", "telegram", "help.telegram"), + ("S", "shutdown", "help.shutdown"), + ("SO", "signout", "help.signout"), + ("BL", "broken-lure", "help.broken_lure"), + ("SR", "spod-rod", "help.spod_rod"), + ("DM", "dry-mix", "help.dry_mix"), + ("GB", "groundbait", "help.groundbait"), + ("PVA", "pva", "help.pva"), + ("NA", "no-animation", "help.no_animation"), ) EPILOG = """ @@ -158,18 +155,20 @@ def setup_parser(cfg: CN) -> tuple[argparse.ArgumentParser, tuple]: :rtype: ArgumentParser """ parent_parser = argparse.ArgumentParser(add_help=False) - parent_parser.add_argument("opts", nargs="*", help="overwrite configuration") + parent_parser.add_argument("opts", nargs="*", help=t("help.opts")) main_parser = argparse.ArgumentParser(epilog=EPILOG, formatter_class=Formatter) main_parser.add_argument( "-V", "--version", action="version", version=f"RF4S {VERSION}" ) - feature_parsers = main_parser.add_subparsers(title="features", dest="feature") + feature_parsers = main_parser.add_subparsers( + title=t("main.features"), dest="feature" + ) bot_parser = feature_parsers.add_parser( "bot", - help="start fishing bot", + help=t("help.bot"), parents=[parent_parser], formatter_class=Formatter, ) @@ -180,7 +179,7 @@ def setup_parser(cfg: CN) -> tuple[argparse.ArgumentParser, tuple]: for argument in BOT_BOOLEAN_ARGUMENTS: flag1 = f"-{argument[0]}" flag2 = f"--{argument[1]}" - help_message = argument[2] + help_message = t(argument[2]) bot_parser.add_argument(flag1, flag2, action="store_true", help=help_message) profile_strategy = bot_parser.add_mutually_exclusive_group() @@ -193,7 +192,7 @@ def pid(_pid: str) -> int: "--pid", type=pid, choices=range(len(cfg.PROFILE)), - help="specify the id of the profile to use", + help=t("help.pid"), metavar=f"{{0-{len(cfg.PROFILE) - 1}}}", ) @@ -206,7 +205,7 @@ def pname(_pname: str) -> str: "-N", "--pname", type=pname, - help="specify the name of the profile to use", + help=t("help.pname"), metavar="{profile name}", ) @@ -220,7 +219,7 @@ def num_fish(_num_fish: str) -> int: # const=0, # Flag is used but no argument given type=num_fish, choices=range(cfg.BOT.KEEPNET.CAPACITY), - help="specify the number of fishes in your keepnet, (default: %(default)s)", + help=t("help.fishes_in_keepnet"), metavar=f"{{0-{cfg.BOT.KEEPNET.CAPACITY - 1}}}", ) bot_parser.add_argument( @@ -231,10 +230,7 @@ def num_fish(_num_fish: str) -> int: default=None, type=str, choices=["forward", "left", "right"], - help=( - "enable trolling mode and specify the direction\n" - "(default: %(default)s, no argument: %(const)s)" - ), + help=t("help.trolling"), ) bot_parser.add_argument( "-R", @@ -244,10 +240,7 @@ def num_fish(_num_fish: str) -> int: default=None, type=int, choices=[0, 5], - help=( - "enable rainbow line mode and specify the meter to lift the rod\n" - "(default: %(default)s, no argument: %(const)s)" - ), + help=t("help.rainbow"), ) bot_parser.add_argument( @@ -258,14 +251,14 @@ def num_fish(_num_fish: str) -> int: default=0, type=int, choices=[0, 1, 2, 3, 5], - help=( - "enable boat ticket renewal and specify the duration\n" - "(default: %(default)s, no argument: %(const)s)" - ), + help=t("help.boat_ticket"), ) craft_parser = feature_parsers.add_parser( - "craft", help="craft items", parents=[parent_parser], formatter_class=Formatter + "craft", + help=t("help.craft"), + parents=[parent_parser], + formatter_class=Formatter, ) craft_parser.add_argument( "-V", "--version", action="version", version=f"RF4S-craft {VERSION}" @@ -274,26 +267,26 @@ def num_fish(_num_fish: str) -> int: "-d", "--discard", action="store_true", - help="discard all the crafted items (for groundbaits)", + help=t("help.discard"), ) craft_parser.add_argument( "-i", "--ignore", action="store_true", - help="ignore unselected material slots", + help=t("help.ignore"), ) craft_parser.add_argument( "-n", "--craft-limit", type=int, default=-1, - help="specify the number of items to craft, (default: %(default)s)", + help=t("help.craft_limit"), metavar="{number of items}", ) move_parser = feature_parsers.add_parser( "move", - help="toggle moving forward", + help=t("help.move"), parents=[parent_parser], formatter_class=Formatter, ) @@ -304,12 +297,12 @@ def num_fish(_num_fish: str) -> int: "-s", "--shift", action="store_true", - help="Hold down the Shift key while moving", + help=t("help.shift"), ) harvest_parser = feature_parsers.add_parser( "harvest", - help="harvest baits", + help=t("help.harvest"), parents=[parent_parser], formatter_class=Formatter, ) @@ -320,12 +313,12 @@ def num_fish(_num_fish: str) -> int: "-r", "--refill", action="store_true", - help="refill hunger and comfort by consuming tea and carrot", + help=t("help.refill_harvest"), ) friction_brake_parser = feature_parsers.add_parser( "frictionbrake", - help="automate friction brake", + help=t("help.frictionbrake"), aliases=["fb"], parents=[parent_parser], formatter_class=Formatter, @@ -336,7 +329,7 @@ def num_fish(_num_fish: str) -> int: calculate_paser = feature_parsers.add_parser( "calculate", - help="calculate tackle's stats", + help=t("help.calculate"), aliases=["cal"], parents=[parent_parser], formatter_class=Formatter, @@ -361,15 +354,15 @@ def display_features() -> None: Shows a formatted table with feature IDs and names. """ table = Table( - "Features", - title="Select a feature to start 🚀", + t("main.features"), + title=t("main.select_feature"), show_header=False, box=box.HEAVY, min_width=36, ) for i, feature in enumerate(FEATURES): - table.add_row(f"{i:>2}. {feature['name']}") + table.add_row(f"{i:>2}. {t(feature['name_key'])}") print(table) @@ -379,30 +372,28 @@ def get_fid(parser: argparse.ArgumentParser) -> int: Continuously prompts until a valid feature ID is entered or the user chooses to quit. """ - utils.print_usage_box("Enter feature id to use, h to see help message, q to quit.") + utils.print_usage_box(t("main.enter_fid")) while True: user_input = input(">>> ") if user_input.isdigit() and 0 <= int(user_input) < len(FEATURES): break if user_input == "q": - print("Bye.") + print(t("common.bye")) sys.exit() if user_input == "h": parser.print_help() continue - utils.print_error("Invalid input, please try again.") + utils.print_error(t("common.invalid_input")) return int(user_input) def get_launch_options(parser: argparse.ArgumentParser) -> str: - utils.print_usage_box( - "Enter launch options, Enter to skip, h to see help message, q to quit." - ) + utils.print_usage_box(t("main.enter_launch_options")) while True: user_input = input(">>> ") if user_input == "q": - print("Bye.") + print(t("common.bye")) sys.exit() if user_input == "h": parser.print_help() @@ -412,30 +403,28 @@ def get_launch_options(parser: argparse.ArgumentParser) -> str: def get_language(): - utils.print_usage_box("What's your game language? [(1) en (2) ru (3) q (quit)]") + utils.print_usage_box(t("main.game_language_prompt")) while True: user_input = input(">>> ") if user_input.isdigit() and user_input in ("1", "2"): break if user_input == "q": - print("Bye.") + print(t("common.bye")) sys.exit() - utils.print_error("Invalid input, please try again.") + utils.print_error(t("common.invalid_input")) return '"en"' if user_input == "1" else '"ru"' def get_click_lock(): - utils.print_usage_box( - "Is Windows Mouse ClickLock enabled? [(1) yes (2) no (3) q (quit)]" - ) + utils.print_usage_box(t("main.click_lock_prompt")) while True: user_input = input(">>> ") if user_input.isdigit() and user_input in ("1", "2"): break if user_input == "q": - print("Bye.") + print(t("common.bye")) sys.exit() - utils.print_error("Invalid input, please try again.") + utils.print_error(t("common.invalid_input")) return "true" if user_input == "1" else "false" @@ -468,7 +457,9 @@ def setup_cfg(): def main() -> None: + setup_i18n("en") cfg = setup_cfg() + setup_i18n(cfg.LANGUAGE) parser, subparsers = setup_parser(cfg) args = parser.parse_args() # First parse to get {command} {flags} utils.print_logo_box(LOGO) # Print logo here so the help message will not show it diff --git a/pyproject.toml b/pyproject.toml index 8133143..8b2f9b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "pynput==1.7.6", "pyscreeze==0.1.29", "python-dotenv==1.0.1", + "python-i18n[YAML]==0.3.9", "pywin32==306", "pyyaml==6.0.2", "rich==13.9.4", diff --git a/requirements.txt b/requirements.txt index d23ab7b..e5568a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,6 +45,7 @@ pyscreenshot==3.1 pyscreeze==0.1.29 python-dateutil==2.9.0.post0 python-dotenv==1.1.1 +python-i18n[YAML]==0.3.9 pytweening==1.2.0 pywin32==306 pyyaml==6.0.2 diff --git a/rf4s/app/app.py b/rf4s/app/app.py index 4f51c65..2b1aa3d 100644 --- a/rf4s/app/app.py +++ b/rf4s/app/app.py @@ -34,6 +34,7 @@ from rf4s import config, exceptions, utils from rf4s.app.core import logger +from rf4s.i18n import t from rf4s.component.friction_brake import FrictionBrake from rf4s.config import load_cfg from rf4s.controller.detection import Detection @@ -82,7 +83,7 @@ def display_result(self) -> None: if not result_dict: return - result = Table(title="Running Result", box=box.HEAVY, show_header=False) + result = Table(title=t("ui.running_result"), box=box.HEAVY, show_header=False) for name, value in self.result.as_dict().items(): result.add_row(name, str(value)) print(result) @@ -136,9 +137,9 @@ def validate_cfg(self): def display_info(self): settings = Table( - title="Settings", show_header=False, box=box.HEAVY, min_width=36 + title=t("ui.settings"), show_header=False, box=box.HEAVY, min_width=36 ) - settings.add_row("LAUNCH OPTIONS (FINAL)", " ".join(sys.argv[1:])) + settings.add_row(t("ui.launch_options_final"), " ".join(sys.argv[1:])) for k, v in self.cfg.PROFILE.items(): if k != "DESCRIPTION": settings.add_row(k, str(v)) @@ -146,7 +147,7 @@ def display_info(self): if self.cfg.PROFILE.DESCRIPTION: utils.print_description_box(self.cfg.PROFILE.DESCRIPTION) utils.print_usage_box( - f"Press {self.cfg.KEY.PAUSE} to pause, {self.cfg.KEY.QUIT} to quit." + t("ui.press_pause_quit", pause=self.cfg.KEY.PAUSE, quit=self.cfg.KEY.QUIT) ) def validate_smtp(self) -> None: @@ -253,7 +254,7 @@ def display_profiles(self) -> None: Shows a formatted table with profile IDs and names. """ profiles = Table( - title="Select a profile to start ⚙️", + title=t("ui.select_profile"), box=box.HEAVY, show_header=False, min_width=36, @@ -268,16 +269,16 @@ def get_pid(self) -> None: Continuously prompts until a valid profile ID is entered or the user chooses to quit. """ - utils.print_usage_box("Enter profile id to use, q to quit.") + utils.print_usage_box(t("ui.enter_pid")) while True: user_input = input(">>> ") if user_input.isdigit() and 0 <= int(user_input) < len(self.cfg.PROFILE): break if user_input == "q": - print("Bye.") + print(t("common.bye")) sys.exit() - utils.print_error("Invalid profile id, please try again.") + utils.print_error(t("ui.invalid_pid")) self.args.pid = int(user_input) @@ -447,12 +448,8 @@ def start(self) -> None: if not self.paused: break - utils.print_usage_box( - f"Press {self.cfg.KEY.PAUSE} to reload config and restart." - ) - utils.print_hint_box( - "Any modifications made to LAUNCH_OPTIONS will be ignored." - ) + utils.print_usage_box(t("ui.press_restart", pause=self.cfg.KEY.PAUSE)) + utils.print_hint_box(t("ui.hint_launch_ignored")) with self.player.hold_keys(mouse=False, shift=False, reset=True): pause_listener = keyboard.Listener(on_release=self._pause_wait) pause_listener.start() @@ -470,7 +467,7 @@ def start(self) -> None: ) self.paused = False - self.player.handle_termination("Terminated by user", shutdown=False, send=False) + self.player.handle_termination("stop.terminated", shutdown=False, send=False) class CraftApp(App): @@ -489,9 +486,9 @@ def __init__(self, cfg, args, parser): self.cfg.freeze() settings = Table( - title="Settings", show_header=False, box=box.HEAVY, min_width=36 + title=t("ui.settings"), show_header=False, box=box.HEAVY, min_width=36 ) - settings.add_row("LAUNCH OPTIONS (FINAL)", " ".join(sys.argv[1:])) + settings.add_row(t("ui.launch_options_final"), " ".join(sys.argv[1:])) print(settings) self.result = CraftResult() @@ -551,7 +548,7 @@ def start(self) -> None: listener.start() try: - utils.print_usage_box(f"Press {self.cfg.KEY.QUIT} to quit.") + utils.print_usage_box(t("ui.press_quit", quit=self.cfg.KEY.QUIT)) logger.warning("This might get you banned, use at your own risk") logger.warning("Use Razor or Logitech macros instead") random.seed(datetime.now().timestamp()) @@ -606,8 +603,11 @@ def __init__(self, cfg, args, parser): self.cfg.freeze() utils.print_usage_box( - f"Press {self.cfg.KEY.MOVE_PAUSE} to pause, " - f"{self.cfg.KEY.MOVE_QUIT} to quit.", + t( + "ui.press_move", + pause=self.cfg.KEY.MOVE_PAUSE, + quit=self.cfg.KEY.MOVE_QUIT, + ) ) self.result = Result() @@ -666,16 +666,22 @@ def __init__(self, cfg, args, parser): self.cfg.freeze() settings = Table( - title="Settings", show_header=False, box=box.HEAVY, min_width=36 + title=t("ui.settings"), show_header=False, box=box.HEAVY, min_width=36 + ) + settings.add_row(t("ui.launch_options_final"), " ".join(sys.argv[1:])) + settings.add_row(t("harvest.power_saving"), str(self.cfg.HARVEST.POWER_SAVING)) + settings.add_row(t("harvest.check_delay"), str(self.cfg.HARVEST.CHECK_DELAY)) + settings.add_row( + t("harvest.energy_threshold"), str(self.cfg.STAT.ENERGY_THRESHOLD) + ) + settings.add_row( + t("harvest.hunger_threshold"), str(self.cfg.STAT.HUNGER_THRESHOLD) + ) + settings.add_row( + t("harvest.comfort_threshold"), str(self.cfg.STAT.COMFORT_THRESHOLD) ) - settings.add_row("LAUNCH OPTIONS (FINAL)", " ".join(sys.argv[1:])) - settings.add_row("Power saving", str(self.cfg.HARVEST.POWER_SAVING)) - settings.add_row("Check delay", str(self.cfg.HARVEST.CHECK_DELAY)) - settings.add_row("Energy threshold", str(self.cfg.STAT.ENERGY_THRESHOLD)) - settings.add_row("Hunger threshold", str(self.cfg.STAT.HUNGER_THRESHOLD)) - settings.add_row("Comfort threshold", str(self.cfg.STAT.COMFORT_THRESHOLD)) print(settings) - utils.print_usage_box(f"Press {self.cfg.KEY.QUIT} to quit.") + utils.print_usage_box(t("ui.press_quit", quit=self.cfg.KEY.QUIT)) self.result = HarvestResult() self.timer = Timer(self.cfg) @@ -772,8 +778,9 @@ def start(self) -> None: @dataclass class Part: - name: str - prompt: str + key: str + name_key: str + prompt_key: str color: str base: float = 0.0 load_capacity: Optional[float] = None @@ -781,6 +788,14 @@ class Part: real_load_capacity: Optional[float] = None pre_real_load_capacity: Optional[float] = None + @property + def name(self) -> str: + return t(self.name_key) + + @property + def prompt(self) -> str: + return t(self.prompt_key) + def calculate_real_load_capacity(self) -> None: self.real_load_capacity = ( self.load_capacity * (1 - self.base) * (1 - self.wear / 100) @@ -802,15 +817,47 @@ def __init__(self, cfg, args, parser): _ = cfg, args, parser self.result = None self.parts = [ - Part(name="Rod", prompt="Load capacity (kg)", color="orange1", base=0.3), - Part(name="Reel mechanism", prompt="Mech (kg)", color="plum1", base=0.3), - Part(name="Reel friction brake", prompt="Drag (kg)", color="gold1"), - Part(name="Fishing line", prompt="Load capacity (kg)", color="salmon1"), - Part(name="Leader", prompt="Load capacity (kg)", color="pale_green1"), - Part(name="Hook", prompt="Load capacity (kg)", color="sky_blue1"), + Part( + key="rod", + name_key="part.rod", + prompt_key="prompt.load_capacity", + color="orange1", + base=0.3, + ), + Part( + key="reel_mechanism", + name_key="part.reel_mechanism", + prompt_key="prompt.mech", + color="plum1", + base=0.3, + ), + Part( + key="reel_friction_brake", + name_key="part.reel_fb", + prompt_key="prompt.drag", + color="gold1", + ), + Part( + key="fishing_line", + name_key="part.fishing_line", + prompt_key="prompt.load_capacity", + color="salmon1", + ), + Part( + key="leader", + name_key="part.leader", + prompt_key="prompt.load_capacity", + color="pale_green1", + ), + Part( + key="hook", + name_key="part.hook", + prompt_key="prompt.load_capacity", + color="sky_blue1", + ), ] self.friction_brake = next( - part for part in self.parts if part.name == "Reel friction brake" + part for part in self.parts if part.key == "reel_friction_brake" ) def calculate_tackle_stats(self): @@ -821,9 +868,13 @@ def calculate_tackle_stats(self): if part.pre_real_load_capacity is not None: raise exceptions.PreviousError else: - utils.print_error(f"{part.name}'s value not found.") + utils.print_error( + t("calculate.value_not_found", name=part.name) + ) part.load_capacity = self.get_validated_input(part, part.prompt) - part.wear = self.get_validated_input(part, "Wear (%)") + part.wear = self.get_validated_input( + part, t("prompt.wear"), is_wear=True + ) except exceptions.SkipError: if part.real_load_capacity is not None: part.real_load_capacity = None @@ -837,7 +888,9 @@ def calculate_tackle_stats(self): part.calculate_real_load_capacity() self.result.add_row(part.name, f"{part.real_load_capacity:.2f} kg") - def get_validated_input(self, part: Part, prompt: str) -> float: + def get_validated_input( + self, part: Part, prompt: str, is_wear: bool = False + ) -> float: while True: user_input = Prompt.ask( f"[{part.color}][{part.name}][/{part.color}] {prompt}" @@ -847,12 +900,16 @@ def get_validated_input(self, part: Part, prompt: str) -> float: raise exceptions.RestartError case CalculateCommand.PREVIOUS.value: if part.pre_real_load_capacity is None: - utils.print_error(f"{part.name}'s value not found.") + utils.print_error( + t("calculate.value_not_found", name=part.name) + ) continue raise exceptions.PreviousError case CalculateCommand.PREVIOUS_REMAINING.value: if part.pre_real_load_capacity is None: - utils.print_error(f"{part.name}'s value not found.") + utils.print_error( + t("calculate.value_not_found", name=part.name) + ) continue raise exceptions.PreviousRemainingError case CalculateCommand.SKIP.value: @@ -864,24 +921,24 @@ def get_validated_input(self, part: Part, prompt: str) -> float: try: number = float(user_input) - if prompt.startswith("Wear"): + if is_wear: if not (0 <= number <= 100): - utils.print_error("Wear must be between 0 and 100.") + utils.print_error(t("calculate.wear_range")) continue elif number < 0: - utils.print_error("Value must be non-negative.") + utils.print_error(t("calculate.non_negative")) continue return number except ValueError: - utils.print_error("Invalid input. Please try again.") + utils.print_error(t("calculate.invalid_input_retry")) def reset_stats(self) -> None: for part in self.parts: part.pre_real_load_capacity = part.real_load_capacity part.real_load_capacity = None self.result = Table( - "Results", - title="Tackle's Stats", + t("calculate.results"), + title=t("calculate.tackle_stats"), show_header=False, box=box.HEAVY, min_width=36, @@ -892,7 +949,7 @@ def update_result(self) -> None: if not valid_parts: return weakest_part = min(valid_parts, key=lambda x: x.real_load_capacity) - self.result.add_row("Weakest part", weakest_part.name) + self.result.add_row(t("calculate.weakest_part"), weakest_part.name) if self.friction_brake.real_load_capacity is None: return @@ -907,7 +964,7 @@ def update_result(self) -> None: ), ) self.result.add_row( - "Recommend friction brake", + t("calculate.recommend_fb"), f"{int(recommend_friction_brake):2d}", ) except ZeroDivisionError: @@ -918,16 +975,8 @@ def start(self): Prompts the user for input, calculates the result, and displays them in a table. """ - utils.print_usage_box( - "Commands:\n" - "r: Restart\n" - "s: Skip a part\n" - "S: Skip the remaining parts\n" - "p: Use previous value for a part\n" - "P: Use previous value for the remaing parts\n" - "q: Quit" - ) - utils.print_hint_box("Press V and click the gear icon to view the parts.") + utils.print_usage_box(t("calculate.commands_help")) + utils.print_hint_box(t("calculate.hint_view_parts")) while True: self.reset_stats() @@ -938,7 +987,7 @@ def start(self): except exceptions.RestartError: continue except exceptions.QuitError: - print("Bye.") + print(t("common.bye")) break if self.result.rows: self.update_result() @@ -971,19 +1020,31 @@ def __init__(self, cfg, args, parser): self.cfg.freeze() settings = Table( - title="Settings", show_header=False, box=box.HEAVY, min_width=36 + title=t("ui.settings"), show_header=False, box=box.HEAVY, min_width=36 + ) + settings.add_row(t("ui.launch_options_final"), " ".join(sys.argv[1:])) + settings.add_row( + t("friction_brake.initial_fb"), str(self.cfg.FRICTION_BRAKE.INITIAL) + ) + settings.add_row(t("friction_brake.max_fb"), str(self.cfg.FRICTION_BRAKE.MAX)) + settings.add_row( + t("friction_brake.start_delay"), str(self.cfg.FRICTION_BRAKE.START_DELAY) + ) + settings.add_row( + t("friction_brake.increase_delay"), + str(self.cfg.FRICTION_BRAKE.INCREASE_DELAY), + ) + settings.add_row( + t("friction_brake.sensitivity"), self.cfg.FRICTION_BRAKE.SENSITIVITY ) - settings.add_row("LAUNCH OPTIONS (FINAL)", " ".join(sys.argv[1:])) - settings.add_row("Initial friction brake", str(self.cfg.FRICTION_BRAKE.INITIAL)) - settings.add_row("Max friction brake", str(self.cfg.FRICTION_BRAKE.MAX)) - settings.add_row("Start delay", str(self.cfg.FRICTION_BRAKE.START_DELAY)) - settings.add_row("Increase delay", str(self.cfg.FRICTION_BRAKE.INCREASE_DELAY)) - settings.add_row("Sensitivity", self.cfg.FRICTION_BRAKE.SENSITIVITY) print(settings) utils.print_usage_box( - f"Press {self.cfg.KEY.FRICTION_BRAKE_RESET} to reset friction brake, " - f"{self.cfg.KEY.FRICTION_BRAKE_QUIT} to quit." + t( + "ui.press_fb", + reset=self.cfg.KEY.FRICTION_BRAKE_RESET, + quit=self.cfg.KEY.FRICTION_BRAKE_QUIT, + ) ) self.window = Window() diff --git a/rf4s/controller/player.py b/rf4s/controller/player.py index 118da98..9a52894 100644 --- a/rf4s/controller/player.py +++ b/rf4s/controller/player.py @@ -30,6 +30,7 @@ from rf4s.controller.detection import Detection, TagColor from rf4s.controller.notification import send_result, send_screenshot from rf4s.controller.timer import Timer +from rf4s.i18n import t from rf4s.result.result import BotResult from rf4s.utils import add_jitter @@ -202,13 +203,13 @@ def loop_restart_handler(self): if self.cfg.ARGS.FRICTION_BRAKE: with self.friction_brake.lock: self.friction_brake.change(increase=False) - self.general_quit("Fishing line is at its end") + self.general_quit("stop.line_at_end") except exceptions.LineSnaggedError: self._handle_snagged_line() except exceptions.DisconnectedError: self.disconnected_quit() except exceptions.TackleBrokenError: - self.general_quit("Tackle is broken") + self.general_quit("stop.tackle_broken") except exceptions.BaitNotChosenError: self.handle_bait_not_chosen() except exceptions.DryMixNotFoundError: @@ -419,7 +420,7 @@ def _drink_coffee(self) -> None: mouse = True if self.mouse_pressed and self.cfg.KEY["COFFEE"] != -1 else False with self.hold_keys(mouse=mouse, shift=False, reset=True): if self.cur_coffee > self.cfg.STAT.COFFEE_LIMIT: - self.general_quit("Coffee limit reached") + self.general_quit("stop.coffee_limit") logger.info("Drinking coffee") for _ in range(self.cfg.STAT.COFFEE_PER_DRINK): @@ -504,7 +505,7 @@ def error_handler(self): def handle_bait_not_chosen(self) -> None: if len(self.tackles) == 1: - self.general_quit("Run out of bait") + self.general_quit("stop.no_bait") self.tackle.available = False def cast_spod_rod(self) -> None: @@ -671,7 +672,7 @@ def _update_tackle(self) -> None: """Update the current tackle (rod) being used.""" candidates = self._get_available_rods() if not candidates: - self.general_quit("All rods are unavailable") + self.general_quit("stop.no_rods") if self.cfg.PROFILE.RANDOM_ROD_SELECTION: self.tackle_idx = random.choice(candidates) else: @@ -790,7 +791,7 @@ def _pause_script(self) -> None: def _handle_timeout(self) -> None: """Handle common timeout events.""" if self.detection.is_tackle_broken(): - self.general_quit("Tackle is broken") + self.general_quit("stop.tackle_broken") if self.detection.is_disconnected(): self.disconnected_quit() @@ -804,7 +805,7 @@ def _handle_broken_lure(self) -> None: with self.hold_keys(mouse=False, shift=False): self._replace_broken_lures() else: - self.general_quit("Lure is broken") + self.general_quit("stop.lure_broken") def handle_termination(self, msg: str, shutdown: bool, send: bool) -> None: """Handle script termination. @@ -839,7 +840,7 @@ def handle_termination(self, msg: str, shutdown: bool, send: bool) -> None: def _handle_snagged_line(self) -> None: """Handle a snagged line event.""" if len(self.tackles) == 1: - self.general_quit("Line is snagged") + self.general_quit("stop.line_snagged") self.tackle.available = False def handle_fish(self) -> None: @@ -859,7 +860,7 @@ def handle_fish(self) -> None: self.timer.add_cast_time() limit = self.cfg.BOT.KEEPNET.CAPACITY - self.cfg.ARGS.FISHES_IN_KEEPNET if self.result.kept == limit: - self.general_quit("Keepnet is full") + self.general_quit("stop.keepnet_full") def _handle_fish(self) -> None: """Keep or release the fish and record the fish count.""" @@ -908,7 +909,7 @@ def _handle_fish(self) -> None: pag.press("esc") pag.press("backspace") sleep(ANIMATION_DELAY) - self.general_quit("Keepnet is full") + self.general_quit("stop.keepnet_full") for tag_color in tag_colors: setattr(self.result, tag_color, getattr(self.result, tag_color) + 1) @@ -979,7 +980,7 @@ def disconnected_quit(self) -> None: pag.moveTo(self.detection.get_confirm_button_position()) pag.click() - self.handle_termination("Game disconnected", shutdown=True, send=True) + self.handle_termination("stop.disconnected", shutdown=True, send=True) def get_result_dict(self, msg: str): return self.result.as_dict(msg, self.timer) @@ -990,7 +991,7 @@ def get_result_table(self, result) -> Table: :return: formatted running result table :rtype: Table """ - table = Table(title="Running Result", box=box.HEAVY, show_header=False) + table = Table(title=t("ui.running_result"), box=box.HEAVY, show_header=False) for k, v in result.items(): table.add_row(k, str(v)) @@ -1002,14 +1003,14 @@ def _handle_expired_ticket(self) -> None: if self.cfg.ARGS.BOAT_TICKET == 0: pag.press("esc") sleep(TICKET_EXPIRE_DELAY) - self.general_quit("Boat ticket expired") + self.general_quit("stop.ticket_expired") logger.info("Renewing boat ticket") ticket_loc = self.detection.get_ticket_position(self.cfg.ARGS.BOAT_TICKET) if ticket_loc is None: pag.press("esc") # Close ticket menu sleep(TICKET_EXPIRE_DELAY) - self.general_quit("New boat ticket not found") + self.general_quit("stop.no_ticket") pag.moveTo(ticket_loc) pag.click(clicks=2, interval=0.1) # pag.doubleClick() not implemented self.result.ticket += 1 @@ -1069,7 +1070,7 @@ def _replace_item(self) -> None: pag.press("esc") sleep(ANIMATION_DELAY) pag.press("esc") - self.general_quit("Favorite item not found") + self.general_quit("stop.no_favorite") # Check if the lure for replacement is already broken x, y = utils.get_box_center_integers(favorite_item_position) diff --git a/rf4s/i18n/__init__.py b/rf4s/i18n/__init__.py new file mode 100644 index 0000000..c29e77a --- /dev/null +++ b/rf4s/i18n/__init__.py @@ -0,0 +1,23 @@ +import sys +from pathlib import Path + +import i18n + +i18n.set("filename_format", "{locale}.{format}") +i18n.set("file_format", "yml") +i18n.set("fallback", "en") +i18n.set("enable_memoization", True) + +if "__compiled__" in globals(): + _i18n_dir = Path(sys.executable).parent / "rf4s" / "i18n" +else: + _i18n_dir = Path(__file__).parent + +i18n.load_path.append(str(_i18n_dir)) + + +def setup(language: str = "en") -> None: + i18n.set("locale", language) + + +t = i18n.t diff --git a/rf4s/i18n/en.yml b/rf4s/i18n/en.yml new file mode 100644 index 0000000..a7b69f8 --- /dev/null +++ b/rf4s/i18n/en.yml @@ -0,0 +1,167 @@ +en: + common: + bye: "Bye." + invalid_input: "Invalid input, please try again." + press_any_key: "Press any key to quit." + using: "You're now using: %{msg}" + hint: "Hint: %{msg}" + + main: + select_feature: "Select a feature to start 🚀" + features: "Features" + enter_fid: "Enter feature id to use, h to see help message, q to quit." + enter_launch_options: "Enter launch options, Enter to skip, h to see help message, q to quit." + game_language_prompt: "What's your game language? [(1) en (2) ru (3) q (quit)]" + click_lock_prompt: "Is Windows Mouse ClickLock enabled? [(1) yes (2) no (3) q (quit)]" + + feature: + bot: "Fishing Bot" + craft: "Craft Items" + move: "Move Forward" + harvest: "Harvest Baits" + frictionbrake: "Auto Friction Brake" + calculate: "Calculate Tackle's Stats" + + help: + bot: "start fishing bot" + craft: "craft items" + move: "toggle moving forward" + harvest: "harvest baits" + frictionbrake: "automate friction brake" + calculate: "calculate tackle's stats" + tag: "keep only tagged fishes" + coffee: "drink coffee if stamina is low during fish fight" + alcohol: "drink alcohol before keeping the fish" + refill: "consume tea and carrot if hunger or comfort is low" + harvest_arg: "harvest baits before casting the rod" + lure: "change current lure with a random favorite one, mode: spin" + mouse: "move mouse randomly before casting the rod" + pause: "pause the script before casting the rod occasionally" + random_cast: "do a redundant rod cast randomly" + skip_cast: "skip the first rod cast" + lift: "lift the tackle constantly during a fish fight" + electro: "enable electric mode for Electro Raptor series reel" + friction_brake: "adjust friction brake automatically" + gear_ratio: "switch the gear ratio or mode after the retrieval timed out" + bite: "save a screenshot in screenshots/ when a fish bite" + screenshot: "save a screenshot in screenshots/ after you caught a fish" + data: "save fishing data in /logs" + email: "send email noticication after the script stop" + miaotixing: "send miaotixing notification after the script stop" + discord: "send Discord notification after the script stop" + telegram: "send Telegram notification after the script stop" + shutdown: "shutdown computer after the script stop" + signout: "sign out instead of closing the game" + broken_lure: "replace broken lures with favorite ones" + spod_rod: "recast spod rod" + dry_mix: "enable dry mix refill, mode: bottom" + groundbait: "enable groundbait refill, mode: bottom" + pva: "enable pva refill, mode: bottom" + no_animation: "disable waiting for trophy and gift animations, gift\nchange 'Catch screen style' to 'Simple' in game settings to use this flag" + opts: "overwrite configuration" + pid: "specify the id of the profile to use" + pname: "specify the name of the profile to use" + fishes_in_keepnet: "specify the number of fishes in your keepnet, (default: %(default)s)" + trolling: "enable trolling mode and specify the direction\n(default: %(default)s, no argument: %(const)s)" + rainbow: "enable rainbow line mode and specify the meter to lift the rod\n(default: %(default)s, no argument: %(const)s)" + boat_ticket: "enable boat ticket renewal and specify the duration\n(default: %(default)s, no argument: %(const)s)" + discard: "discard all the crafted items (for groundbaits)" + ignore: "ignore unselected material slots" + craft_limit: "specify the number of items to craft, (default: %(default)s)" + shift: "Hold down the Shift key while moving" + refill_harvest: "refill hunger and comfort by consuming tea and carrot" + + ui: + settings: "Settings" + launch_options_final: "LAUNCH OPTIONS (FINAL)" + running_result: "Running Result" + select_profile: "Select a profile to start ⚙️" + enter_pid: "Enter profile id to use, q to quit." + invalid_pid: "Invalid profile id, please try again." + press_pause_quit: "Press %{pause} to pause, %{quit} to quit." + press_quit: "Press %{quit} to quit." + press_move: "Press %{pause} to pause, %{quit} to quit." + press_restart: "Press %{pause} to reload config and restart." + hint_launch_ignored: "Any modifications made to LAUNCH_OPTIONS will be ignored." + press_fb: "Press %{reset} to reset friction brake, %{quit} to quit." + + harvest: + power_saving: "Power saving" + check_delay: "Check delay" + energy_threshold: "Energy threshold" + hunger_threshold: "Hunger threshold" + comfort_threshold: "Comfort threshold" + + friction_brake: + initial_fb: "Initial friction brake" + max_fb: "Max friction brake" + start_delay: "Start delay" + increase_delay: "Increase delay" + sensitivity: "Sensitivity" + + calculate: + tackle_stats: "Tackle's Stats" + results: "Results" + weakest_part: "Weakest part" + recommend_fb: "Recommend friction brake" + commands_help: "Commands:\nr: Restart\ns: Skip a part\nS: Skip the remaining parts\np: Use previous value for a part\nP: Use previous value for the remaining parts\nq: Quit" + hint_view_parts: "Press V and click the gear icon to view the parts." + value_not_found: "%{name}'s value not found." + wear_range: "Wear must be between 0 and 100." + non_negative: "Value must be non-negative." + invalid_input_retry: "Invalid input. Please try again." + + part: + rod: "Rod" + reel_mechanism: "Reel mechanism" + reel_fb: "Reel friction brake" + fishing_line: "Fishing line" + leader: "Leader" + hook: "Hook" + + prompt: + load_capacity: "Load capacity (kg)" + mech: "Mech (kg)" + drag: "Drag (kg)" + wear: "Wear (%)" + + result: + stop_reason: "Stop reason" + start_time: "Start time" + end_time: "End time" + running_time: "Running time" + bite_rate: "Bite rate" + total_fish: "Total fish" + kept_fish: "Kept fish" + kept_ratio: "Kept ratio" + green_tag: "Green tag fish" + yellow_tag: "Yellow tag fish" + blue_tag: "Blue tag fish" + purple_tag: "Purple tag fish" + pink_tag: "Pink tag fish" + card: "Card" + gift: "Gift" + tea: "Tea consumed" + carrot: "Carrot consumed" + alcohol: "Alcohol consumed" + coffee: "Coffee consumed" + bait: "Bait harvested" + ticket: "Ticket used" + success_crafts: "Successful crafts" + fail_crafts: "Failed crafts" + materials: "Materials used" + + stop: + keepnet_full: "Keepnet is full" + tackle_broken: "Tackle is broken" + line_at_end: "Fishing line is at its end" + coffee_limit: "Coffee limit reached" + no_bait: "Run out of bait" + no_rods: "All rods are unavailable" + lure_broken: "Lure is broken" + line_snagged: "Line is snagged" + ticket_expired: "Boat ticket expired" + no_ticket: "New boat ticket not found" + no_favorite: "Favorite item not found" + disconnected: "Game disconnected" + terminated: "Terminated by user" diff --git a/rf4s/i18n/ru.yml b/rf4s/i18n/ru.yml new file mode 100644 index 0000000..f87bf73 --- /dev/null +++ b/rf4s/i18n/ru.yml @@ -0,0 +1,167 @@ +ru: + common: + bye: "Пока." + invalid_input: "Неверный ввод, попробуйте ещё раз." + press_any_key: "Нажмите любую клавишу для выхода." + using: "Вы используете: %{msg}" + hint: "Подсказка: %{msg}" + + main: + select_feature: "Выберите функцию для запуска 🚀" + features: "Функции" + enter_fid: "Введите id функции, h — справка, q — выход." + enter_launch_options: "Введите параметры запуска, Enter — пропустить, h — справка, q — выход." + game_language_prompt: "Какой язык в игре? [(1) en (2) ru (3) q (выход)]" + click_lock_prompt: "Включена ли залипание кнопки мыши Windows? [(1) да (2) нет (3) q (выход)]" + + feature: + bot: "Рыболовный бот" + craft: "Крафт предметов" + move: "Движение вперёд" + harvest: "Сбор наживки" + frictionbrake: "Авто фрикцион" + calculate: "Расчёт характеристик снасти" + + help: + bot: "запустить рыболовного бота" + craft: "крафтить предметы" + move: "переключить движение вперёд" + harvest: "собирать наживку" + frictionbrake: "автоматизировать фрикцион" + calculate: "рассчитать характеристики снасти" + tag: "оставлять только помеченных рыб" + coffee: "пить кофе при низкой выносливости во время вываживания" + alcohol: "пить алкоголь перед сохранением рыбы" + refill: "употреблять чай и морковь при низком голоде или комфорте" + harvest_arg: "собирать наживку перед забросом" + lure: "менять приманку на случайную избранную, режим: спиннинг" + mouse: "случайно двигать мышь перед забросом" + pause: "иногда ставить скрипт на паузу перед забросом" + random_cast: "случайно делать лишний заброс" + skip_cast: "пропустить первый заброс" + lift: "постоянно поднимать снасть во время вываживания" + electro: "включить электрический режим для катушки Electro Raptor" + friction_brake: "автоматически регулировать фрикцион" + gear_ratio: "переключить передаточное число после таймаута подмотки" + bite: "сохранять скриншот в screenshots/ при поклёвке" + screenshot: "сохранять скриншот в screenshots/ после поимки рыбы" + data: "сохранять данные рыбалки в /logs" + email: "отправить email-уведомление после остановки скрипта" + miaotixing: "отправить уведомление miaotixing после остановки скрипта" + discord: "отправить уведомление Discord после остановки скрипта" + telegram: "отправить уведомление Telegram после остановки скрипта" + shutdown: "выключить компьютер после остановки скрипта" + signout: "выйти из аккаунта вместо закрытия игры" + broken_lure: "заменять сломанные приманки на избранные" + spod_rod: "перезабрасывать спод-род" + dry_mix: "включить пополнение сухой смеси, режим: донка" + groundbait: "включить пополнение прикормки, режим: донка" + pva: "включить пополнение PVA, режим: донка" + no_animation: "отключить ожидание анимации трофея и подарка\nизмените 'Стиль экрана поимки' на 'Простой' в настройках игры" + opts: "перезаписать конфигурацию" + pid: "указать id профиля" + pname: "указать имя профиля" + fishes_in_keepnet: "указать количество рыб в садке, (по умолчанию: %(default)s)" + trolling: "включить режим троллинга и указать направление\n(по умолчанию: %(default)s, без аргумента: %(const)s)" + rainbow: "включить режим радужной лески и указать метры подъёма\n(по умолчанию: %(default)s, без аргумента: %(const)s)" + boat_ticket: "включить продление билета на лодку и указать длительность\n(по умолчанию: %(default)s, без аргумента: %(const)s)" + discard: "выбрасывать все скрафченные предметы (для прикормки)" + ignore: "игнорировать невыбранные слоты материалов" + craft_limit: "указать количество предметов для крафта, (по умолчанию: %(default)s)" + shift: "удерживать Shift при движении" + refill_harvest: "восполнять голод и комфорт чаем и морковью" + + ui: + settings: "Настройки" + launch_options_final: "ПАРАМЕТРЫ ЗАПУСКА (ИТОГ)" + running_result: "Результат работы" + select_profile: "Выберите профиль для запуска ⚙️" + enter_pid: "Введите id профиля, q — выход." + invalid_pid: "Неверный id профиля, попробуйте ещё раз." + press_pause_quit: "Нажмите %{pause} для паузы, %{quit} для выхода." + press_quit: "Нажмите %{quit} для выхода." + press_move: "Нажмите %{pause} для паузы, %{quit} для выхода." + press_restart: "Нажмите %{pause} для перезагрузки конфига и перезапуска." + hint_launch_ignored: "Любые изменения LAUNCH_OPTIONS будут проигнорированы." + press_fb: "Нажмите %{reset} для сброса фрикциона, %{quit} для выхода." + + harvest: + power_saving: "Энергосбережение" + check_delay: "Задержка проверки" + energy_threshold: "Порог энергии" + hunger_threshold: "Порог голода" + comfort_threshold: "Порог комфорта" + + friction_brake: + initial_fb: "Начальный фрикцион" + max_fb: "Макс. фрикцион" + start_delay: "Задержка старта" + increase_delay: "Задержка увеличения" + sensitivity: "Чувствительность" + + calculate: + tackle_stats: "Характеристики снасти" + results: "Результаты" + weakest_part: "Слабое звено" + recommend_fb: "Рекомендуемый фрикцион" + commands_help: "Команды:\nr: Перезапуск\ns: Пропустить часть\nS: Пропустить оставшиеся части\np: Использовать предыдущее значение\nP: Использовать предыдущее значение для оставшихся\nq: Выход" + hint_view_parts: "Нажмите V и кликните на иконку шестерёнки для просмотра частей." + value_not_found: "Значение %{name} не найдено." + wear_range: "Износ должен быть от 0 до 100." + non_negative: "Значение должно быть неотрицательным." + invalid_input_retry: "Неверный ввод. Попробуйте ещё раз." + + part: + rod: "Удилище" + reel_mechanism: "Механизм катушки" + reel_fb: "Фрикцион катушки" + fishing_line: "Леска" + leader: "Поводок" + hook: "Крючок" + + prompt: + load_capacity: "Нагрузка (кг)" + mech: "Мех (кг)" + drag: "Тяга (кг)" + wear: "Износ (%)" + + result: + stop_reason: "Причина остановки" + start_time: "Время начала" + end_time: "Время окончания" + running_time: "Время работы" + bite_rate: "Частота поклёвок" + total_fish: "Всего рыб" + kept_fish: "Оставлено рыб" + kept_ratio: "Доля оставленных" + green_tag: "Зелёная метка" + yellow_tag: "Жёлтая метка" + blue_tag: "Синяя метка" + purple_tag: "Фиолетовая метка" + pink_tag: "Розовая метка" + card: "Карточка" + gift: "Подарок" + tea: "Чай выпито" + carrot: "Морковь съедено" + alcohol: "Алкоголь выпито" + coffee: "Кофе выпито" + bait: "Наживки собрано" + ticket: "Билетов использовано" + success_crafts: "Успешных крафтов" + fail_crafts: "Неудачных крафтов" + materials: "Материалов использовано" + + stop: + keepnet_full: "Садок полон" + tackle_broken: "Снасть сломана" + line_at_end: "Леска закончилась" + coffee_limit: "Лимит кофе достигнут" + no_bait: "Наживка закончилась" + no_rods: "Все удилища недоступны" + lure_broken: "Приманка сломана" + line_snagged: "Леска зацепилась" + ticket_expired: "Билет на лодку истёк" + no_ticket: "Новый билет на лодку не найден" + no_favorite: "Избранный предмет не найден" + disconnected: "Игра отключена" + terminated: "Остановлено пользователем" diff --git a/rf4s/i18n/zh-TW.yml b/rf4s/i18n/zh-TW.yml new file mode 100644 index 0000000..e4505d4 --- /dev/null +++ b/rf4s/i18n/zh-TW.yml @@ -0,0 +1,167 @@ +zh-TW: + common: + bye: "再見。" + invalid_input: "輸入無效,請再試一次。" + press_any_key: "按任意鍵退出。" + using: "您正在使用:%{msg}" + hint: "提示:%{msg}" + + main: + select_feature: "選擇要啟動的功能 🚀" + features: "功能" + enter_fid: "輸入功能 id,h 查看說明,q 退出。" + enter_launch_options: "輸入啟動選項,Enter 跳過,h 查看說明,q 退出。" + game_language_prompt: "您的遊戲語言是什麼?[(1) en (2) ru (3) q (退出)]" + click_lock_prompt: "是否啟用了 Windows 滑鼠鎖定點擊?[(1) 是 (2) 否 (3) q (退出)]" + + feature: + bot: "釣魚機器人" + craft: "製作物品" + move: "向前移動" + harvest: "收集餌料" + frictionbrake: "自動煞車" + calculate: "計算釣具數據" + + help: + bot: "啟動釣魚機器人" + craft: "製作物品" + move: "切換向前移動" + harvest: "收集餌料" + frictionbrake: "自動化煞車" + calculate: "計算釣具數據" + tag: "只保留有標記的魚" + coffee: "戰鬥中體力低時喝咖啡" + alcohol: "保留魚之前喝酒" + refill: "飢餓或舒適度低時消耗茶和胡蘿蔔" + harvest_arg: "拋竿前收集餌料" + lure: "用隨機收藏的餌更換當前餌,模式:路亞" + mouse: "拋竿前隨機移動滑鼠" + pause: "偶爾在拋竿前暫停腳本" + random_cast: "隨機進行多餘的拋竿" + skip_cast: "跳過第一次拋竿" + lift: "戰鬥中持續提竿" + electro: "啟用 Electro Raptor 系列捲線器的電動模式" + friction_brake: "自動調整煞車" + gear_ratio: "收線超時後切換齒輪比或模式" + bite: "魚咬鉤時在 screenshots/ 保存截圖" + screenshot: "釣到魚後在 screenshots/ 保存截圖" + data: "將釣魚數據保存到 /logs" + email: "腳本停止後發送電子郵件通知" + miaotixing: "腳本停止後發送喵提醒通知" + discord: "腳本停止後發送 Discord 通知" + telegram: "腳本停止後發送 Telegram 通知" + shutdown: "腳本停止後關閉電腦" + signout: "登出而不是關閉遊戲" + broken_lure: "用收藏的餌替換損壞的餌" + spod_rod: "重新拋擲誘餌竿" + dry_mix: "啟用乾粉補充,模式:底釣" + groundbait: "啟用窩料補充,模式:底釣" + pva: "啟用 PVA 補充,模式:底釣" + no_animation: "停用獎盃和禮物動畫等待\n在遊戲設定中將「捕獲畫面樣式」改為「簡單」以使用此選項" + opts: "覆寫設定" + pid: "指定要使用的設定檔 id" + pname: "指定要使用的設定檔名稱" + fishes_in_keepnet: "指定魚護中的魚數,(預設:%(default)s)" + trolling: "啟用拖釣模式並指定方向\n(預設:%(default)s,無參數:%(const)s)" + rainbow: "啟用彩虹線模式並指定提竿公尺數\n(預設:%(default)s,無參數:%(const)s)" + boat_ticket: "啟用船票續期並指定時長\n(預設:%(default)s,無參數:%(const)s)" + discard: "丟棄所有製作的物品(用於窩料)" + ignore: "忽略未選擇的材料欄位" + craft_limit: "指定要製作的物品數量,(預設:%(default)s)" + shift: "移動時按住 Shift 鍵" + refill_harvest: "透過消耗茶和胡蘿蔔來補充飢餓和舒適度" + + ui: + settings: "設定" + launch_options_final: "啟動選項(最終)" + running_result: "執行結果" + select_profile: "選擇要啟動的設定檔 ⚙️" + enter_pid: "輸入設定檔 id,q 退出。" + invalid_pid: "無效的設定檔 id,請再試一次。" + press_pause_quit: "按 %{pause} 暫停,%{quit} 退出。" + press_quit: "按 %{quit} 退出。" + press_move: "按 %{pause} 暫停,%{quit} 退出。" + press_restart: "按 %{pause} 重新載入設定並重啟。" + hint_launch_ignored: "對 LAUNCH_OPTIONS 的任何修改將被忽略。" + press_fb: "按 %{reset} 重置煞車,%{quit} 退出。" + + harvest: + power_saving: "省電模式" + check_delay: "檢查延遲" + energy_threshold: "能量閾值" + hunger_threshold: "飢餓閾值" + comfort_threshold: "舒適度閾值" + + friction_brake: + initial_fb: "初始煞車" + max_fb: "最大煞車" + start_delay: "啟動延遲" + increase_delay: "增加延遲" + sensitivity: "靈敏度" + + calculate: + tackle_stats: "釣具數據" + results: "結果" + weakest_part: "最弱部件" + recommend_fb: "建議煞車值" + commands_help: "指令:\nr:重新開始\ns:跳過一個部件\nS:跳過剩餘部件\np:使用上次的值\nP:對剩餘部件使用上次的值\nq:退出" + hint_view_parts: "按 V 並點擊齒輪圖示查看部件。" + value_not_found: "找不到 %{name} 的值。" + wear_range: "磨損必須在 0 到 100 之間。" + non_negative: "值必須為非負數。" + invalid_input_retry: "輸入無效,請再試一次。" + + part: + rod: "釣竿" + reel_mechanism: "捲線器機構" + reel_fb: "捲線器煞車" + fishing_line: "釣線" + leader: "子線" + hook: "魚鉤" + + prompt: + load_capacity: "承載力(kg)" + mech: "機械(kg)" + drag: "阻力(kg)" + wear: "磨損(%)" + + result: + stop_reason: "停止原因" + start_time: "開始時間" + end_time: "結束時間" + running_time: "執行時間" + bite_rate: "咬鉤率" + total_fish: "總魚數" + kept_fish: "保留魚數" + kept_ratio: "保留比例" + green_tag: "綠色標記魚" + yellow_tag: "黃色標記魚" + blue_tag: "藍色標記魚" + purple_tag: "紫色標記魚" + pink_tag: "粉色標記魚" + card: "卡片" + gift: "禮物" + tea: "消耗茶" + carrot: "消耗胡蘿蔔" + alcohol: "消耗酒" + coffee: "消耗咖啡" + bait: "收穫餌料" + ticket: "使用船票" + success_crafts: "成功製作" + fail_crafts: "失敗製作" + materials: "使用材料" + + stop: + keepnet_full: "魚護已滿" + tackle_broken: "釣具損壞" + line_at_end: "釣線已到盡頭" + coffee_limit: "咖啡上限已達" + no_bait: "餌料用完" + no_rods: "所有釣竿不可用" + lure_broken: "餌已損壞" + line_snagged: "釣線纏住" + ticket_expired: "船票已過期" + no_ticket: "找不到新船票" + no_favorite: "找不到收藏物品" + disconnected: "遊戲已斷線" + terminated: "使用者終止" diff --git a/rf4s/result/result.py b/rf4s/result/result.py index 4882f90..ed77a6d 100644 --- a/rf4s/result/result.py +++ b/rf4s/result/result.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from rf4s.controller.timer import Timer +from rf4s.i18n import t @dataclass @@ -38,28 +39,30 @@ def as_dict(self, msg: str, timer: Timer) -> dict: kept_ratio = "0%" bite_rate = "0/hr" + stop_reason = t(msg) + return { - "Stop reason": msg, - "Start time": timer.get_start_datetime(), - "End time": timer.get_cur_datetime(), - "Running time": timer.get_running_time_str(), - "Bite rate": bite_rate, - "Total fish": self.total, - "Kept fish": self.kept, - "Kept ratio": kept_ratio, - "Green tag fish": self.green, - "Yellow tag fish": self.yellow, - "Blue tag fish": self.blue, - "Purple tag fish": self.purple, - "Pink tag fish": self.pink, - "Card": self.card, - "Gift": self.gift, - "Tea consumed": self.tea, - "Carrot consumed": self.carrot, - "Alcohol consumed": self.alcohol, - "Coffee consumed": self.coffee, - "Bait harvested": self.bait, - "Ticket used": self.ticket, + t("result.stop_reason"): stop_reason, + t("result.start_time"): timer.get_start_datetime(), + t("result.end_time"): timer.get_cur_datetime(), + t("result.running_time"): timer.get_running_time_str(), + t("result.bite_rate"): bite_rate, + t("result.total_fish"): self.total, + t("result.kept_fish"): self.kept, + t("result.kept_ratio"): kept_ratio, + t("result.green_tag"): self.green, + t("result.yellow_tag"): self.yellow, + t("result.blue_tag"): self.blue, + t("result.purple_tag"): self.purple, + t("result.pink_tag"): self.pink, + t("result.card"): self.card, + t("result.gift"): self.gift, + t("result.tea"): self.tea, + t("result.carrot"): self.carrot, + t("result.alcohol"): self.alcohol, + t("result.coffee"): self.coffee, + t("result.bait"): self.bait, + t("result.ticket"): self.ticket, } @@ -71,9 +74,9 @@ class CraftResult: def as_dict(self) -> dict: return { - "Successful crafts": self.success, - "Failed crafts": self.fail, - "Materials used": self.material, + t("result.success_crafts"): self.success, + t("result.fail_crafts"): self.fail, + t("result.materials"): self.material, } @@ -85,7 +88,7 @@ class HarvestResult: def as_dict(self) -> dict: return { - "Tea consumed": self.tea, - "Carrot consumed": self.carrot, - "Bait harvested": self.bait, + t("result.tea"): self.tea, + t("result.carrot"): self.carrot, + t("result.bait"): self.bait, } diff --git a/rf4s/utils.py b/rf4s/utils.py index 47edcd6..bfc81bb 100644 --- a/rf4s/utils.py +++ b/rf4s/utils.py @@ -20,6 +20,7 @@ from rich.panel import Panel from rf4s.controller.console import console +from rf4s.i18n import t LOOP_DELAY = 1 @@ -125,11 +126,11 @@ def print_usage_box(msg: str) -> None: def print_description_box(msg: str) -> None: - print(Panel.fit(f"You're now using: {msg}")) + print(Panel.fit(t("common.using", msg=msg))) def print_hint_box(msg: str) -> None: - print(Panel.fit(f"Hint: {msg}", style="green")) + print(Panel.fit(t("common.hint", msg=msg), style="green")) def print_error(msg: str) -> None: @@ -138,7 +139,7 @@ def print_error(msg: str) -> None: def safe_exit(): if is_run_by_clicking(): - print_usage_box("Press any key to quit.") + print_usage_box(t("common.press_any_key")) # KeyboardInterrupt will mess with stdin, input will crash silently # Use msvcrt.getch() because it doesn't depends on stdin msvcrt.getch()