From 8843276c41b1c6682cf9f9566bed5355829cbf18 Mon Sep 17 00:00:00 2001 From: Luke H-W <Birdulon@users.noreply.github.com> Date: Mon, 11 Jul 2022 23:51:05 +0930 Subject: [PATCH] Language linting (#1382) --- .github/workflows/language_lint.yml | 27 + manage_languages.py | 321 ++++++++++++ .../auth/DefaultAuthentication.java | 2 +- .../emu/grasscutter/command/CommandMap.java | 18 +- .../command/commands/GiveCommand.java | 1 - .../command/commands/HelpCommand.java | 118 ++--- .../command/commands/SendMailCommand.java | 2 +- src/main/resources/languages/pl-PL.json | 487 ++++++++++-------- 8 files changed, 676 insertions(+), 300 deletions(-) create mode 100644 .github/workflows/language_lint.yml create mode 100644 manage_languages.py diff --git a/.github/workflows/language_lint.yml b/.github/workflows/language_lint.yml new file mode 100644 index 00000000..17ee7d7e --- /dev/null +++ b/.github/workflows/language_lint.yml @@ -0,0 +1,27 @@ +name: "Language Lint" +on: + workflow_dispatch: ~ + push: + paths: + - "**.java" + - "**.json" + branches: + - "stable" + - "development" + pull_request: + paths: + - "**.java" + - "**.json" + types: + - opened + - synchronize + - reopened +jobs: + Lint-Language-Keys: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax + - run: python3 manage_languages.py -l diff --git a/manage_languages.py b/manage_languages.py new file mode 100644 index 00000000..ee7d1a5c --- /dev/null +++ b/manage_languages.py @@ -0,0 +1,321 @@ +# Written for Python 3.6+ +# Older versions don't retain insertion order of regular dicts +import argparse +import cmd +import json +import os +import re +from pprint import pprint + +INDENT = 2 +PRIMARY_LANGUAGE = 'en-US.json' +PRIMARY_FALLBACK_PREFIX = '馃嚭馃嚫' # This is invisible in-game, terminal emulators might render it +LANGUAGE_FOLDER = 'src/main/resources/languages/' +LANGUAGE_FILENAMES = sorted(os.listdir(LANGUAGE_FOLDER), key=lambda x: 'AAA' if x == PRIMARY_LANGUAGE else x) +SOURCE_FOLDER = 'src/' +SOURCE_EXTENSIONS = ('java') + + +def ppprint(data): + pprint(data, width=130, sort_dicts=False, compact=True) + + +class JsonHelpers: + @staticmethod + def load(filename: str) -> dict: + with open(filename, 'r') as file: + return json.load(file) + + @staticmethod + def save(filename: str, data: dict) -> None: + with open(filename, 'w', encoding='utf-8', newline='\n') as file: + json.dump(data, file, ensure_ascii=False, indent=INDENT) + + @staticmethod + def flatten(data: dict, prefix='') -> dict: + output = {} + for key, value in data.items(): + if isinstance(value, dict): + for k,v in JsonHelpers.flatten(value, f'{prefix}{key}.').items(): + output[k] = v + else: + output[f'{prefix}{key}'] = value + return output + + @staticmethod + def unflatten(data: dict) -> dict: + output = {} + def add_key(k: list, value, d: dict): + if len(k) == 1: + d[k[0]] = value + else: + d[k[0]] = d.get(k[0], {}) + add_key(k[1:], value, d[k[0]]) + for key, value in data.items(): + add_key(key.split('.'), value, output) + return output + + @staticmethod + def pprint_keys(keys, indent=4) -> str: + # Only strip down to one level + padding = ' ' * indent + roots = {} + for key in keys: + root, _, k = key.rpartition('.') + roots[root] = roots.get(root, []) + roots[root].append(k) + lines = [] + for root, ks in roots.items(): + if len(ks) > 1: + lines.append(f'{padding}{root}.[{", ".join(ks)}]') + else: + lines.append(f'{padding}{root}.{ks[0]}') + return ',\n'.join(lines) + + @staticmethod + def deep_clone_and_fill(d1: dict, d2: dict, fallback_prefix=PRIMARY_FALLBACK_PREFIX) -> dict: + out = {} + for key, value in d1.items(): + if isinstance(value, dict): + out[key] = JsonHelpers.deep_clone_and_fill(value, d2.get(key, {}), fallback_prefix) + else: + v2 = d2.get(key, value) + if type(value) == str and v2 == value: + out[key] = fallback_prefix + value + else: + out[key] = v2 + return out + + +class LanguageManager: + TRANSLATION_KEY = re.compile(r'[Tt]ranslate.*"(\w+\.[\w\.]+)"') + POTENTIAL_KEY = re.compile(r'"(\w+\.[\w\.]+)"') + + def __init__(self): + self.load_jsons() + + def load_jsons(self): + self.language_jsons = [JsonHelpers.load(LANGUAGE_FOLDER + filename) for filename in LANGUAGE_FILENAMES] + self.flattened_jsons = [JsonHelpers.flatten(j) for j in self.language_jsons] + self.update_keys() + + def update_keys(self): + self.key_sets = [set(j.keys()) for j in self.flattened_jsons] + self.common_keys = set.intersection(*self.key_sets) + self.all_keys = set.union(*self.key_sets) + self.used_keys = self.find_all_used_keys(self.all_keys) + self.missing_keys = self.used_keys - self.common_keys + self.unused_keys = self.all_keys - self.used_keys + + def find_all_used_keys(self, expected_keys=[]) -> set: + # Note that this will only find string literals passed to the translate() or sendTranslatedMessage() methods! + # String variables passed to them can be checked against expected_keys + used = set() + potential = set() + for root, dirs, files in os.walk(SOURCE_FOLDER): + for file in files: + if file.rpartition('.')[-1] in SOURCE_EXTENSIONS: + filename = os.path.join(root, file) + with open(filename, 'r') as f: + data = f.read() # Loads in entire file at once + for k in self.TRANSLATION_KEY.findall(data): + used.add(k) + for k in self.POTENTIAL_KEY.findall(data): + potential.add(k) + return used | (potential & expected_keys) + + def _lint_report_language(self, lang: str, keys: set, flattened: dict, primary_language_flattened: dict) -> None: + missing = self.used_keys - keys + unused = keys - self.used_keys + identical_keys = set() if (lang == PRIMARY_LANGUAGE) else {key for key in keys if primary_language_flattened.get(key, None) == flattened.get(key)} + placeholder_keys = {key for key in keys if flattened.get(key).startswith(PRIMARY_FALLBACK_PREFIX)} + p1 = f'Language {lang} has {len(missing)} missing keys and {len(unused)} unused keys.' + p2 = 'This is the primary language.' if (lang == PRIMARY_LANGUAGE) else f'{len(identical_keys)} match {PRIMARY_LANGUAGE}, {len(placeholder_keys)} have the placeholder mark.' + print(f'{p1} {p2}') + + lint_categories = { + 'Missing': missing, + 'Unused': unused, + f'Matches {PRIMARY_LANGUAGE}': identical_keys, + 'Placeholder': placeholder_keys, + } + for name, category in lint_categories.items(): + if len(category) > 0: + print(name + ':') + print(JsonHelpers.pprint_keys(sorted(category))) + + def lint_report(self) -> None: + print(f'There are {len(self.missing_keys)} translation keys in use that are missing from one or more language files.') + print(f'There are {len(self.unused_keys)} translation keys in language files that are not used.') + primary_language_flattened = self.flattened_jsons[LANGUAGE_FILENAMES.index(PRIMARY_LANGUAGE)] + for lang, keys, flattened in zip(LANGUAGE_FILENAMES, self.key_sets, self.flattened_jsons): + print('') + self._lint_report_language(lang, keys, flattened, primary_language_flattened) + + def rename_keys(self, key_remappings: dict) -> None: + # Unfortunately we can't rename keys in-place preserving insertion order, so we have to make new dicts + for i in range(len(self.flattened_jsons)): + self.flattened_jsons[i] = {key_remappings.get(k,k):v for k,v in self.flattened_jsons[i].items()} + + def update_secondary_languages(self): + # Push en_US fallback + primary_language_json = self.language_jsons[LANGUAGE_FILENAMES.index(PRIMARY_LANGUAGE)] + for filename, lang in zip(LANGUAGE_FILENAMES, self.language_jsons): + if filename != PRIMARY_LANGUAGE: + js = JsonHelpers.deep_clone_and_fill(primary_language_json, lang) + JsonHelpers.save(LANGUAGE_FOLDER + filename, js) + + def update_all_languages_from_flattened(self): + for filename, flat in zip(LANGUAGE_FILENAMES, self.flattened_jsons): + JsonHelpers.save(LANGUAGE_FOLDER + filename, JsonHelpers.unflatten(flat)) + + def save_flattened_languages(self, prefix='flat_'): + for filename, flat in zip(LANGUAGE_FILENAMES, self.flattened_jsons): + JsonHelpers.save(prefix + filename, flat) + + +class InteractiveRename(cmd.Cmd): + intro = 'Welcome to the interactive rename shell. Type help or ? to list commands.\n' + prompt = '(rename) ' + file = None + + def __init__(self, language_manager: LanguageManager) -> None: + super().__init__() + self.language_manager = language_manager + self.flat_keys = [key for key in language_manager.flattened_jsons[LANGUAGE_FILENAMES.index(PRIMARY_LANGUAGE)].keys()] + self.mappings = {} + + def do_add(self, arg): + ''' + Prepare to rename an existing translation key. Will not actually rename anything until you confirm all your pending changes with 'rename'. + e.g. a single string: add commands.execution.argument_error commands.generic.invalid.argument + e.g. a group: add commands.enter_dungeon commands.new_enter_dungeon + ''' + args = arg.split() + if len(args) < 2: + self.do_help('add') + return + old, new = args[:2] + if old in self.flat_keys: + self.mappings[old] = new + else: + # Check if we are renaming a higher level + if not old.endswith('.'): + old = old + '.' + results = [key for key in self.flat_keys if key.startswith(old)] + if len(results) > 0: + if not new.endswith('.'): + new = new + '.' + new_mappings = {key: key.replace(old, new) for key in results} + # Ask for confirmation + print('Will add the following mappings:') + ppprint(new_mappings) + print('Add these mappings? [y/N]') + if self.prompt_yn(): + for k,v in new_mappings.items(): + self.mappings[k] = v + else: + print('No translation keys matched!') + + def complete_add(self, text: str, line: str, begidx: int, endidx: int) -> list: + if text == '': + return [k for k in {key.partition('.')[0] for key in self.flat_keys}] + results = [key for key in self.flat_keys if key.startswith(text)] + if len(results) > 40: + # Collapse categories + if text[-1] != '.': + text = text + '.' + level = text.count('.') + 1 + new_results = {'.'.join(key.split('.')[:level]) for key in results} + return list(new_results) + return results + + def do_remove(self, arg): + ''' + Remove a pending rename mapping. Takes the old name of the key, not the new one. + e.g. a single key: remove commands.execution.argument_error + e.g. a group: remove commands.enter_dungeon + ''' + old = arg.split()[0] + if old in self.mappings: + self.mappings.pop(old) + else: + # Check if we are renaming a higher level + if not old.endswith('.'): + old = old + '.' + results = [key for key in self.mappings if key.startswith(old)] + if len(results) > 0: + # Ask for confirmation + print('Will remove the following pending mappings:') + print(JsonHelpers.pprint_keys(results)) + print('Delete these mappings? [y/N]') + if self.prompt_yn(): + for key in results: + self.mappings.pop(key) + else: + print('No pending rename mappings matched!') + + def complete_remove(self, text: str, line: str, begidx: int, endidx: int) -> list: + return [key for key in self.mappings if key.startswith(text)] + + def do_rename(self, _arg): + 'Applies pending renames and overwrites language jsons.' + # Ask for confirmation + print('Will perform the following mappings:') + ppprint(self.mappings) + print('Perform and save these rename mappings? [y/N]') + if self.prompt_yn(): + self.language_manager.rename_keys(self.mappings) + self.language_manager.update_all_languages_from_flattened() + print('Renamed keys, closing') + return True + else: + print('Do you instead wish to quit without saving? [yes/N]') + if self.prompt_yn(True): + print('Left rename shell without renaming') + return True + + def prompt_yn(self, strict_yes=False): + if strict_yes: + return input('(yes/N) ').lower() == 'yes' + return input('(y/N) ').lower()[0] == 'y' + + +def main(args: argparse.Namespace): + # print(args) + language_manager = LanguageManager() + errors = None + if args.lint_report: + language_manager.lint_report() + missing = language_manager.used_keys - language_manager.key_sets[LANGUAGE_FILENAMES.index(PRIMARY_LANGUAGE)] + if len(missing) > 0: + errors = f'[ERROR] {len(missing)} keys missing from primary language json!\n{JsonHelpers.pprint_keys(missing)}' + if prefix := args.save_flattened: + language_manager.save_flattened_languages(prefix) + if args.update: + print('Updating secondary languages') + language_manager.update_secondary_languages() + if args.interactive_rename: + language_manager.load_jsons() # Previous actions may have changed them on-disk + try: + InteractiveRename(language_manager).cmdloop() + except KeyboardInterrupt: + print('Left rename shell without renaming') + if errors: + print(errors) + exit(1) + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Manage Grasscutter's language json files.") + parser.add_argument('-u', '--update', action='store_true', + help=f'Update secondary language files to conform to the layout of the primary language file ({PRIMARY_LANGUAGE}) and contain any new keys from it.') + parser.add_argument('-l', '--lint-report', action='store_true', + help='Prints a lint report, listing unused, missing, and untranslated keys among all language jsons.') + parser.add_argument('-f', '--save-flattened', const='./flat_', metavar='prefix', nargs='?', + help='Save copies of all the language jsons in a flattened key form.') + parser.add_argument('-i', '--interactive-rename', action='store_true', + help='Enter interactive rename mode, in which you can specify keys in flattened form to be renamed.') + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java index ba77e7d6..efe63753 100644 --- a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java @@ -31,7 +31,7 @@ public final class DefaultAuthentication implements AuthenticationSystem { @Override public Account verifyUser(String details) { - Grasscutter.getLogger().info(translate("dispatch.authentication.default_unable_to_verify")); + Grasscutter.getLogger().info(translate("messages.dispatch.authentication.default_unable_to_verify")); return null; } diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index bc15ec9a..fe19075b 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -106,7 +106,12 @@ public final class CommandMap { * @return The command handler. */ public CommandHandler getHandler(String label) { - return this.commands.get(label); + CommandHandler handler = this.commands.get(label); + if (handler == null) { + // Try getting by alias + handler = this.aliases.get(label); + } + return handler; } private Player getTargetPlayer(String playerId, Player player, Player targetPlayer, List<String> args) { @@ -129,7 +134,7 @@ public final class CommandMap { } return targetPlayer; } catch (NumberFormatException e) { - CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); + CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); throw new IllegalArgumentException(); } } @@ -177,7 +182,7 @@ public final class CommandMap { CommandHandler.sendTranslatedMessage(player, targetPlayer.isOnline()? "commands.execution.set_target_online" : "commands.execution.set_target_offline", targetUid); return true; } catch (NumberFormatException e) { - CommandHandler.sendTranslatedMessage(player, "commands.execution.uid_error"); + CommandHandler.sendTranslatedMessage(player, "commands.generic.invalid.uid"); return false; } } @@ -220,12 +225,9 @@ public final class CommandMap { } // Get command handler. - CommandHandler handler = this.commands.get(label); - if(handler == null) - // Try to get the handler by alias. - handler = this.aliases.get(label); + CommandHandler handler = this.getHandler(label); - // Check if the handler is still null. + // Check if the handler is null. if (handler == null) { CommandHandler.sendTranslatedMessage(player, "commands.generic.unknown_command", label); return; diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 74d36e93..dab45378 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -221,7 +221,6 @@ public final class GiveCommand implements CommandHandler { case ITEM_RELIQUARY: targetPlayer.getInventory().addItems(makeArtifacts(param), ActionReason.SubfieldDrop); CommandHandler.sendTranslatedMessage(sender, "commands.give.given_level", Integer.toString(param.id), Integer.toString(param.lvl), Integer.toString(param.amount), Integer.toString(targetPlayer.getUid())); - //CommandHandler.sendTranslatedMessage(sender, "commands.giveArtifact.success", Integer.toString(param.id), Integer.toString(targetPlayer.getUid())); return; default: targetPlayer.getInventory().addItem(new GameItem(param.data, param.amount), ActionReason.SubfieldDrop); diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 8a00d9cf..65e7228c 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandMap; @@ -13,6 +12,28 @@ import static emu.grasscutter.utils.Language.translate; @Command(label = "help", usage = "help [command]", description = "commands.help.description", targetRequirement = Command.TargetRequirement.NONE) public final class HelpCommand implements CommandHandler { + private void createCommand(StringBuilder builder, Player player, Command annotation) { + builder.append("\n").append(annotation.label()).append(" - ").append(translate(player, annotation.description())); + builder.append("\n\t").append(translate(player, "commands.help.usage")); + if (annotation.aliases().length >= 1) { + builder.append("\n\t").append(translate(player, "commands.help.aliases")); + for (String alias : annotation.aliases()) { + builder.append(alias).append(" "); + } + } + builder.append("\n\t").append(translate(player, "commands.help.tip_need_permission")); + if(annotation.permission().isEmpty() || annotation.permission().isBlank()) { + builder.append(translate(player, "commands.help.tip_need_no_permission")); + } else { + builder.append(annotation.permission()); + } + + if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { + String permissionTargeted = annotation.permissionTargeted(); + builder.append(" ").append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); + } + } + @Override public void execute(Player player, Player targetPlayer, List<String> args) { if (args.size() < 1) { @@ -32,38 +53,16 @@ public final class HelpCommand implements CommandHandler { } else { String command = args.get(0); CommandHandler handler = CommandMap.getInstance().getHandler(command); - StringBuilder builder = new StringBuilder(player == null ? "\n" + translate(player, "commands.status.help") + " - " : translate(player, "commands.status.help") + " - ").append(command).append(": \n"); + StringBuilder builder = new StringBuilder(""); if (handler == null) { builder.append(translate(player, "commands.generic.command_exist_error")); } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(translate(player, annotation.description())).append("\n"); - builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); - if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate(player, "commands.help.aliases")); - for (String alias : annotation.aliases()) { - builder.append(alias).append(" "); - } - } - - builder.append("\n").append(translate(player, "commands.help.tip_need_permission")); - if(annotation.permission().isEmpty() || annotation.permission().isBlank()) { - builder.append(translate(player, "commands.help.tip_need_no_permission")); - } - else { - builder.append(annotation.permission()); - } - builder.append(" "); - - if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { - String permissionTargeted = annotation.permissionTargeted(); - builder.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); - } + this.createCommand(builder, player, annotation); if (player != null && !Objects.equals(annotation.permission(), "") && !player.getAccount().hasPermission(annotation.permission())) { - builder.append("\n "); - builder.append(translate(player, "commands.help.warn_player_has_no_permission")); + builder.append("\n\t").append(translate(player, "commands.help.warn_player_has_no_permission")); } } @@ -72,67 +71,12 @@ public final class HelpCommand implements CommandHandler { } void SendAllHelpMessage(Player player, List<Command> annotations) { - if (player == null) { - StringBuilder builder = new StringBuilder("\n" + translate(player, "commands.help.available_commands") + "\n"); - annotations.forEach(annotation -> { - builder.append(annotation.label()).append("\n"); - builder.append(" ").append(translate(player, annotation.description())).append("\n"); - builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); - if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate(player, "commands.help.aliases")); - for (String alias : annotation.aliases()) { - builder.append(alias).append(" "); - } - } - builder.append("\n").append(translate(player, "commands.help.tip_need_permission")); - if(annotation.permission().isEmpty() || annotation.permission().isBlank()) { - builder.append(translate(player, "commands.help.tip_need_no_permission")); - } - else { - builder.append(annotation.permission()); - } - - builder.append(" "); - - if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { - String permissionTargeted = annotation.permissionTargeted(); - builder.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); - } + StringBuilder builder = new StringBuilder(translate(player, "commands.help.available_commands")); + annotations.forEach(annotation -> { + this.createCommand(builder, player, annotation); + builder.append("\n"); + }); - builder.append("\n"); - }); - - CommandHandler.sendMessage(null, builder.toString()); - } else { - CommandHandler.sendMessage(player, translate(player, "commands.help.available_commands")); - annotations.forEach(annotation -> { - StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(translate(player, annotation.description())).append("\n"); - builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); - if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate(player, "commands.help.aliases")); - for (String alias : annotation.aliases()) { - builder.append(alias).append(" "); - } - } - builder.append("\n").append(translate(player, "commands.help.tip_need_permission")); - if(annotation.permission().isEmpty() || annotation.permission().isBlank()) { - builder.append(translate(player, "commands.help.tip_need_no_permission")); - } - else { - builder.append(annotation.permission()); - } - - builder.append(" "); - - if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { - String permissionTargeted = annotation.permissionTargeted(); - builder.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); - } - - - CommandHandler.sendMessage(player, builder.toString()); - }); - } + CommandHandler.sendMessage(player, builder.toString()); } } diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index c4369fa8..79f13e20 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -65,7 +65,7 @@ public final class SendMailCommand implements CommandHandler { switch (args.get(0).toLowerCase()) { case "stop" -> { mailBeingConstructed.remove(senderId); - CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.sendCancel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_cancel")); return; } case "finish" -> { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index edc44465..41d63f70 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -1,99 +1,106 @@ { "messages": { "game": { - "port_bind": "Serwer gry uruchomiony na porcie: %s", - "connect": "Klient po艂膮czy艂 si臋 z %s", - "disconnect": "Klient roz艂膮czy艂 si臋 z %s", + "port_bind": "Serwer gry zosta艂 uruchomiony na porcie %s.", + "connect": "Klient po艂膮czy艂 si臋 z %s.", + "disconnect": "Klient roz艂膮czy艂 si臋 z %s.", "game_update_error": "Wyst膮pi艂 b艂膮d podczas aktualizacji gry.", - "command_error": "B艂膮d komendy:" + "command_error": "B艂膮d komendy: " }, "dispatch": { - "port_bind": "[Dispatch] Serwer dispatch wystartowa艂 na porcie %s", - "request": "[Dispatch] Klient %s %s zapytanie: %s", + "port_bind": "[Dispatch] Serwer Dispatch zosta艂 uruchomiony na porcie %s.", + "request": "[Dispatch] 呕膮danie klienta %s (metoda %s): %s", "keystore": { - "general_error": "[Dispatch] B艂膮d 艂膮dowania keystore!", - "password_error": "[Dispatch] Nie mo偶na za艂adowa膰 keystore. Pr贸ba z domy艣lnym has艂em keystore...", - "no_keystore_error": "[Dispatch] Brak certyfikatu SSL! Przej艣cie na serwer HTTP.", - "default_password": "[Dispatch] Domy艣lne has艂o keystore zadzia艂a艂o. Rozwa偶 ustawienie go na 123456 w pliku config.json." + "general_error": "[Keystore] Wyst膮pi艂 b艂膮d podczas 艂adowania keystore.", + "password_error": "[Keystore] Nie mo偶na za艂adowa膰 keystore. Spr贸buj臋 u偶y膰 domy艣lnego has艂a...", + "no_keystore_error": "[Keystore] Brak certyfikatu SSL. Przej艣cie na serwer HTTP.", + "default_password": "[Keystore] Domy艣lne has艂o keystore zosta艂o za艂adowane. Rozwa偶 ustawienie tego has艂a jako \"123456\" w pliku config.json." + }, + "authentication": { + "default_unable_to_verify": "[Authentication] Co艣 wywo艂a艂o metod臋 \"verifyUser\", kt贸ra jest niedost臋pna w domy艣lnym kontrolerze uwierzytelniaj膮cym." }, "no_commands_error": "Komendy nie s膮 wspierane w trybie DISPATCH_ONLY.", - "unhandled_request_error": "[Dispatch] Potencjalnie niepodtrzymane %s zapytanie: %s", + "unhandled_request_error": "[Dispatch] Potencjalnie nierozstrzygni臋te 偶膮danie (metoda %s): %s", "account": { - "login_attempt": "[Dispatch] Klient %s pr贸buje si臋 zalogowa膰", - "login_success": "[Dispatch] Klient %s zalogowa艂 si臋 jako %s", - "login_max_player_limit": "[Dispatch] Klient %s nie powiod艂o si臋: Liczba graczy online osi膮gn臋艂a limit", - "login_token_attempt": "[Dispatch] Klient %s pr贸buje si臋 zalogowa膰 poprzez token", - "login_token_error": "[Dispatch] Klient %s nie m贸g艂 si臋 zalogowa膰 poprzez token", - "login_token_success": "[Dispatch] Klient %s zalogowa艂 si臋 poprzez token jako %s", - "combo_token_success": "[Dispatch] Klient %s pomy艣lnie wymieni艂 combo token", - "combo_token_error": "[Dispatch] Klient %s nie wymieni艂 combo token'u", - "account_login_create_success": "[Dispatch] Klient %s nie m贸g艂 si臋 zalogowa膰: Konto %s stworzone", - "account_login_create_error": "[Dispatch] Klient %s nie m贸g艂 si臋 zalogowa膰: Tworzenie konta nie powiod艂o si臋", - "account_login_exist_error": "[Dispatch] Klient %s nie m贸g艂 si臋 zalogowa膰: Nie znaleziono konta", - "account_cache_error": "B艂膮d pami臋ci cache konta gry", + "login_attempt": "[Account] Klient %s pr贸buje si臋 zalogowa膰.", + "login_success": "[Account] Klient %s zalogowa艂 si臋 jako %s.", + "login_max_player_limit": "[Account] Logowanie klienta %s nie powiod艂o si臋: liczba graczy online osi膮gn臋艂a sw贸j limit.", + "login_token_attempt": "[Account] Klient %s pr贸buje si臋 zalogowa膰 poprzez token.", + "login_token_error": "[Account] Logowanie klienta %s poprzez token nie powiod艂o si臋.", + "login_token_success": "[Account] Klient %s zalogowa艂 si臋 poprzez token jako %s.", + "combo_token_success": "[Account] Klient %s pomy艣lnie wymieni艂 token combo.", + "combo_token_error": "[Account] Wymienienie tokena combo klienta %s nie powiod艂o si臋.", + "account_login_create_success": "[Account] Logowanie klienta %s powiod艂o si臋: konto %s zosta艂o stworzone.", + "account_login_create_error": "[Account] Logowanie klienta %s nie powiod艂o si臋: tworzenie konta nie powiod艂o si臋.", + "account_login_exist_error": "[Account] Logowanie klienta %s nie powiod艂o si臋: nie znaleziono konta.", + "account_cache_error": "B艂膮d pami臋ci cache konta gry.", "session_key_error": "B艂臋dny klucz sesji.", - "username_error": "Nazwa u偶ytkownika nie znaleziona.", - "username_create_error": "Nazwa u偶ytkownika nie znaleziona, tworzenie nie powiod艂o si臋.", - "server_max_player_limit": "Liczba graczy online osi膮gn臋艂a limit" - } + "username_error": "Podana nazwa u偶ytkownika nie istnieje.", + "username_create_error": "Podana nazwa u偶ytkownika nie istnieje. Automatyczne tworzenie nowego konta nie powiod艂o si臋.", + "server_max_player_limit": "Liczba graczy online osi膮gn臋艂a sw贸j limit." + }, + "router_error": "[Dispatch] Wyst膮pi艂 b艂膮d podczas tworzenia routera." }, "status": { - "free_software": "Grasscutter to DARMOWE oprogramowanie. Je偶eli kto艣 Ci je sprzeda艂, to zosta艂e艣 oscamowany. Strona domowa: https://github.com/Grasscutters/Grasscutter", + "free_software": "Grasscutter to DARMOWE oprogramowanie oparte na licencji AGPL-3.0. Je偶eli za nie zap艂aci艂e艣, zosta艂e艣 oszukany. Strona projektu: https://github.com/Grasscutters/Grasscutter", "starting": "Uruchamianie Grasscutter...", - "shutdown": "Wy艂膮czanie...", - "done": "Gotowe! Wpisz \"help\" aby uzyska膰 pomoc", + "shutdown": "Zatrzymywanie Grasscutter...", + "done": "Gotowe! Wpisz \"help\", aby uzyska膰 pomoc.", "error": "Wyst膮pi艂 b艂膮d.", - "welcome": "Witamy w Grasscutter", + "welcome": "Witamy w Grasscutter!", "run_mode_error": "B艂臋dny tryb pracy serwera: %s.", - "run_mode_help": "Tryb pracy serwera musi by膰 ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie mo偶na wystartowa膰 Grasscutter...", - "create_resources": "Tworzenie folderu resources...", - "resources_error": "Umie艣膰 kopi臋 'BinOutput' i 'ExcelBinOutput' w folderze resources.", - "version": "Grasscutter versi贸n: %s-%s", - "game_version": "Game versi贸n: %s", + "run_mode_help": "Tryb pracy serwera musi by膰 ustawiony na \"HYBRID\", \"DISPATCH_ONLY\", lub \"GAME_ONLY\".", + "create_resources": "Tworzenie folderu \"resources\"...", + "resources_error": "Umie艣膰 kopi臋 folder贸w \"BinOutput\" oraz \"ExcelBinOutput\" w folderze \"resources\" i spr贸buj ponownie.", + "version": "Wersja Grasscutter: %s-%s", + "game_version": "Wersja gry: %s", "resources": { - "loading": "Loading resources...", - "finish": "Finished loading resources." + "loading": "艁adowanie zasob贸w...", + "finish": "Za艂adowano zasoby." } } }, "commands": { "generic": { "not_specified": "Nie podano komendy.", - "unknown_command": "Nieznana komenda: %s", - "permission_error": "Nie masz uprawnie艅 do tej komendy.", - "console_execute_error": "T膮 komende mo偶na wywo艂a膰 tylko z konsoli.", - "player_execute_error": "Wywo艂aj t膮 komend臋 w grze.", + "unknown_command": "Nieznana komenda: %s.", + "permission_error": "Nie masz wystarczaj膮cych uprawnie艅 do u偶ywania tej komendy.", + "console_execute_error": "Ta komenda mo偶e by膰 u偶yta tylko w konsoli.", + "player_execute_error": "Ta komenda mo偶e by膰 u偶yta tylko w grze.", "command_exist_error": "Nie znaleziono komendy.", + "no_usage_specified": "Brak przyk艂adu zastosowania.", + "no_description_specified": "Brak opisu.", "set_to": "%s ustawiono na %s.", - "set_for_to": "%s dla %s ustawiono na %s.", + "set_for_to": "%s (UID %s) ustawiono na %s.", "invalid": { "amount": "B艂臋dna ilo艣膰.", "artifactId": "B艂臋dne ID artefaktu.", - "avatarId": "B艂臋dne id postaci.", + "avatarId": "B艂臋dne ID postaci.", "avatarLevel": "B艂臋dny poziom postaci.", - "entityId": "B艂臋dne id obiektu.", - "id przedmiotu": "B艂臋dne id przedmiotu.", + "entityId": "B艂臋dne ID obiektu.", + "itemId": "B艂臋dne ID przedmiotu.", "itemLevel": "B艂臋dny poziom przedmiotu.", "itemRefinement": "B艂臋dne ulepszenie.", - "value_between": "Invalid value: %s must be between %s and %s.", - "playerId": "B艂臋dne playerId.", + "statValue": "B艂臋dna warto艣膰 atrybutu.", + "value_between": "B艂臋dna warto艣膰: %s musi by膰 pomi臋dzy %s a %s.", + "playerId": "B艂臋dne ID gracza.", "uid": "B艂臋dne UID.", "id": "B艂臋dne ID." } }, "execution": { - "player_exist_error": "Gracz nie znaleziony.", + "player_exist_error": "Gracz nie istnieje.", "player_offline_error": "Gracz nie jest online.", - "item_player_exist_error": "B艂臋dny przedmiot lub UID.", - "player_exist_offline_error": "Gracz nie znaleziony lub jest offline.", - "argument_error": "B艂臋dne argumenty.", - "clear_target": "Cel wyczyszczony.", - "set_target": "Nast臋pne komendy b臋d膮 celowa膰 w @%s.", - "set_target_online": "@%s jest online. Niekt贸re polecenia mog膮 wymaga膰 celu offline.", - "set_target_offline": "@%s jest offline. Niekt贸re polecenia mog膮 wymaga膰 celu online.", - "need_target": "Ta komenda wymaga docelowego UID. Dodaj argument <@UID> lub ustaw sta艂y cel poleceniem /target @UID.", - "need_target_online": "To polecenie wymaga identyfikatora UID celu w trybie online, ale bie偶膮cy cel jest w trybie offline. Dodaj inny argument <@UID> lub ustaw trwa艂y cel za pomoc膮 /target @UID.", - "need_target_offline": "To polecenie wymaga identyfikatora UID celu offline, ale bie偶膮cy cel jest online. Dodaj inny argument <@UID> lub ustaw trwa艂y cel za pomoc膮 /target @UID." + "item_player_exist_error": "B艂臋dny przedmiot lub ID.", + "player_exist_offline_error": "Gracz nie istnieje lub nie jest online.", + "argument_error": "B艂臋dny argument lub argumenty.", + "clear_target": "Nast臋pne komendy nie b臋d膮 dotyczy艂y nikogo. B臋dziesz musia艂/a sam/a doda膰 ID gracza docelowego do ka偶dej kolejnej komendy.", + "set_target": "Nast臋pne komendy b臋d膮 dotyczy艂y gracza @%s.", + "set_target_online": "Gracz @%s jest online. Niekt贸re polecenia wymagaj膮 trybu offline i mog膮 nie dzia艂a膰.", + "set_target_offline": "Gracz @%s jest offline. Niekt贸re polecenia wymagaj膮 trybu online i mog膮 nie dzia艂a膰.", + "need_target": "Ta komenda wymaga ID gracza. Dodaj argument <@ID>, lub ustaw ID tego gracza na sta艂e dla ka偶dej kolejnej komendy poleceniem \"set_target @ID\".", + "need_target_online": "Ta komenda wymaga ID gracza, kt贸ry jest online, ale bie偶膮cy gracz docelowy jest w trybie offline. Dodaj inny argument <@ID> lub ustaw ID gracza na sta艂e dla ka偶dej kolejnej komendy poleceniem \"set_target @ID\".", + "need_target_offline": "Ta komenda wymaga ID gracza, kt贸ry jest offline, ale bie偶膮cy gracz docelowy jest w trybie online. Dodaj inny argument <@ID> lub ustaw ID gracza na sta艂e dla ka偶dej kolejnej komendy poleceniem \"set_target @ID\"." }, "status": { "enabled": "W艂膮czone", @@ -102,221 +109,297 @@ "success": "Sukces" }, "account": { - "modify": "Modyfikuj konta u偶ytkownik贸w", - "invalid": "B艂臋dne UID.", - "exists": "Konto ju偶 istnieje.", + "usage": "account <create|delete> <nazwa gracza> [UID]", + "invalid": "B艂臋dne UID gracza.", + "exists": "Konto o tej nazwie u偶ytkownika i/lub UID ju偶 istnieje.", "create": "Stworzono konto z UID %s.", - "delete": "Konto usuni臋te.", + "delete": "Konto zosta艂o usuni臋te.", "no_account": "Nie znaleziono konta.", - "command_usage": "U偶ycie: account <create|delete> <nazwa> [uid]" + "description": "Tw贸rz lub usu艅 konta." + }, + "announce": { + "usage": "announce <tpl> <ID szablonu> LUB announce <refresh> LUB announce <wiadomo艣膰> LUB announce <revoke> <ID szablonu>", + "send_success": "Og艂oszenie zosta艂o pomy艣lnie wys艂ane. Mo偶esz je odwo艂a膰 u偶ywaj膮c \"announce revoke %s\".", + "refresh_success": "Od艣wie偶ono konfiguracj臋 og艂osze艅 (w sumie jest ich %s).", + "revoke_done": "Pomy艣lnie odwo艂ano og艂oszenie %s.", + "not_found": "Nie znaleziono og艂oszenia %s.", + "description": "Wysy艂aj i zarz膮dzaj og艂oszeniami." }, "clear": { - "command_usage": "U偶ycie: clear <all|wp|art|mat> [lv<max level>] [r<max refinement>] [<max rarity>*]", - "weapons": "Wyczyszczono bronie dla %s.", - "artifacts": "Wyczyszczono artefakty dla %s.", - "materials": "Wyczyszczono materia艂y dla %s.", - "furniture": "Wyczyszczono meble dla %s.", - "displays": "Wyczyszczono displays dla %s.", - "virtuals": "Wyczyszczono virtuals dla %s.", - "everything": "Wyczyszczono wszystko dla %s." + "usage": "U偶ycie: clear <all|wp|art|mat> [lv<max level>] [r<max refinement>] [<max rarity>*]", + "weapons": "Usuni臋to bronie gracza %s.", + "artifacts": "Usuni臋to artefakty gracza %s.", + "materials": "Usuni臋to materia艂y gracza %s.", + "furniture": "Usuni臋to meble gracza %s.", + "displays": "Usuni臋to displaye gracza %s.", + "virtuals": "Usuni臋to virtuale gracza %s.", + "everything": "Usuni臋to wszystkie niewyposa偶one przedmioty gracza %s.", + "description": "Usu艅 niewyposa偶one przedmioty wskazanego gracza." }, "coop": { - "usage": "U偶ycie: coop [host uid]", - "success": "Przyzwano %s do 艣wiata %s." + "usage": "coop @[ID gracza]", + "success": "Pomy艣lnie dodano gracza %s do 艣wiata gracza %s.", + "description": "Dodaj wskazanego gracza do swojego 艣wiata." }, "enter_dungeon": { - "usage": "U偶ycie: enterdungeon <ID lochu>", - "changed": "Zmieniono loch na %s", - "not_found_error": "Ten loch nie istnieje", - "in_dungeon_error": "Ju偶 jeste艣 w tym lochu" - }, - "giveAll": { - "usage": "U偶ycie: giveall [gracz] [ilo艣膰]", - "started": "Dodawanie wszystkich przedmiot贸w...", - "success": "Pomy艣lnie dodano wszystkie przedmioty dla %s.", - "invalid_amount_or_playerId": "B艂臋dna ilo艣膰 lub ID gracza." - }, - "giveArtifact": { - "usage": "U偶ycie: giveart|gart [gracz] <id artefaktu> <mainPropId> [<appendPropId>[,<razy>]]... [poziom]", - "id_error": "B艂臋dne ID artefaktu.", - "success": "Dano %s dla %s." + "usage": "enterdungeon <ID lochu>", + "changed": "Pomy艣lnie zmieniono loch na %s.", + "not_found_error": "Podane ID lochu jest nieprawid艂owe.", + "in_dungeon_error": "Wskazany gracz ju偶 jest w tym lochu.", + "description": "Zmie艅 loch, w kt贸rym ma si臋 znajdowa膰 wskazany gracz." }, "give": { - "usage": "U偶ycie: give <gracz> <id przedmiotu | avatarID> [ilo艣膰] [poziom] [refinement]", - "refinement_only_applicable_weapons": "Ulepszenie mo偶na zastosowa膰 tylko dla broni.", - "refinement_must_between_1_and_5": "Ulepszenie musi by膰 pomi臋dzy 1, a 5.", - "given": "Dano %s %s dla %s.", - "given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s", - "given_level": "Dano %s z poziomem %s %s razy dla %s", - "given_avatar": "Dano %s z poziomem %s dla %s." - }, - "godmode": { - "success": "Godmode jest teraz %s dla %s." + "usage": "give <ID przedmiotu|ID awataru|all|weapons|mats|avatars> [x<ilo艣膰>] [lv<poziom>] [r<poziom ulepszenia>]", + "usage_relic": "give <ID reliktu> [ID pierwszego przedmiotu] [<ID drugiego przedmiotu>[, <ile razy je po艂膮czy膰>]]... [lv<poziom od 0 do 20>]", + "illegal_relic": "Ten ID reliktu znajduje si臋 na czarnej li艣cie i mo偶e by膰 nie tym, czego szukasz.", + "given": "Dodano %s przedmiot贸w o ID %s graczowi o ID %s.", + "given_with_level_and_refinement": "Dodano %s przedmiot贸w o poziomie %s oraz poziomie ulepszenia %s i ID %s graczowi o ID %s.", + "given_level": "Dodano %s artefakt贸w o poziomie %s oraz ID %s graczowi o ID %s.", + "given_avatar": "Dodano awatar o ID %s oraz poziomie %s graczowi o ID %s.", + "giveall_success": "Pomy艣lnie dodano wybrane przedmioty.", + "description": "Dodaj wybrane przedmioty do ekwipunku wybranego gracza." }, "heal": { - "success": "Wszystkie postacie zosta艂y wyleczone." + "usage": "heal", + "success": "Wszystkie postacie zosta艂y uleczone.", + "description": "Ulecz wszystkie postacie w swoim zespole." + }, + "help": { + "usage": "help [nazwa komendy]", + "usage_prefix": "U偶ycie: ", + "aliases": "Aliasy: ", + "available_commands": "Dost臋pne komendy: ", + "description": "Wy艣wietl wszystkie komendy lub informacje na temat danej komendy.", + "tip_need_permission": "Wymagane uprawnienie: ", + "tip_need_no_permission": "brak", + "tip_permission_targeted": "(u偶ycie tego polecenia na innych graczach r贸wnie偶 wymaga uprawnienia %s)", + "warn_player_has_no_permission": "Nie masz wystarczaj膮cych uprawnie艅 do u偶ywania tej komendy." }, "kick": { - "player_kick_player": "Gracz [%s:%s] wyrzuci艂 gracza [%s:%s]", - "server_kick_player": "Wyrzucono gracza [%s:%s]" + "usage": "kick", + "player_kick_player": "Gracz [%s:%s] wyrzuci艂 gracza [%s:%s].", + "server_kick_player": "Wyrzucono gracza [%s:%s].", + "description": "Wyrzu膰 wskazanego gracza z gry." }, "killall": { - "usage": "U偶ycie: killall [UID gracza] [ID sceny]", - "scene_not_found_in_player_world": "Scena nie znaleziona w 艣wiecie gracza", - "kill_monsters_in_scene": "Zabito %s potwor贸w w scenie %s" + "usage": "killall @[ID gracza] [ID sceny]", + "scene_not_found_in_player_world": "B艂臋dny ID sceny.", + "kill_monsters_in_scene": "Zabito %s potwor贸w w scenie %s.", + "description": "Zabij wszystkie potwory we wskazanej scenie." }, "killCharacter": { - "usage": "U偶ycie: /killcharacter [ID gracza]", - "success": "Zabito aktualn膮 posta膰 gracza %s." + "usage": "killcharacter @[ID gracza]", + "success": "Pomy艣lnie zabito posta膰 gracza %s.", + "description": "Zabij posta膰 wskazanego gracza." + }, + "language": { + "usage": "language [kod j臋zyka]", + "current_language": "Bie偶膮cy kod j臋zyka to %s.", + "language_changed": "Zmieniono j臋zyk na ten o kodzie %s.", + "language_not_found": "Nie znaleziono j臋zyka o kodzie \"%s\".", + "description": "Poka偶 lub zmie艅 bie偶膮cy kod j臋zyka." }, "list": { - "success": "Teraz jest %s gracz(y) online:" + "usage": "list @[ID gracza]", + "success": "%s graczy online:", + "description": "Poka偶 ile jest graczy na serwerze." }, "permission": { - "usage": "U偶ycie: permission <add|remove> <nazwa gracza> <uprawnienie>", - "add": "Dodano uprawnienie", - "has_error": "To konto ju偶 ma to uprawnienie!", - "remove": "Usuni臋to uprawnienie.", - "not_have_error": "To konto nie ma tych uprawnie艅!", - "account_error": "Konto nie mo偶e zosta膰 znalezione." + "usage": "permission <add|remove> <nazwa gracza> <uprawnienie>", + "add": "Pomy艣lnie dodano uprawnienie.", + "has_error": "Ten gracz ju偶 ma to uprawnienie.", + "remove": "Pomy艣lnie usuni臋to uprawnienie.", + "not_have_error": "Ten gracz nie ma tego uprawnienia.", + "account_error": "Podana nazwa gracza nie istnieje.", + "description": "Dodaj lub usu艅 uprawnienia podanego gracza." }, "position": { - "success": "Koordynaty: %s, %s, %s\nID sceny: %s" + "usage": "position", + "success": "Koordynaty: (%s, %s, %s).\nID sceny: %s.", + "description": "Poka偶 gdzie znajduje si臋 dany gracz." + }, + "quest": { + "usage": "quest <add|finish> [ID zadania]", + "added": "Zadanie %s zosta艂o dodane.", + "finished": "Zadanie %s zosta艂o zako艅czone.", + "not_found": "Nie ma zadania o podanym ID.", + "invalid_id": "B艂臋dny format ID zadania.", + "description": "Dodaj lub wykonaj wskazane zadanie." }, "reload": { - "reload_start": "Ponowne 艂adowanie konfiguracji.", - "reload_done": "Ponowne 艂adowanie zako艅czone." + "usage": "reload", + "reload_start": "Ponowne 艂adowanie konfiguracji...", + "reload_done": "Ponowne 艂adowanie konfiguracji zako艅czone.", + "description": "Ponownie za艂aduj j臋zyk, konfiguracj臋 oraz inne dane gry." }, "resetConst": { - "reset_all": "Resetuj konstelacje wszystkich postaci.", - "success": "Konstelacje dla %s zosta艂y zresetowane. Prosz臋 zalogowa膰 si臋 ponownie aby zobaczy膰 zmiany." + "usage": "resetconst [all]", + "reset_all": "Zresetowano konstelacje dla wszystkich postaci. Aby zobaczy膰 zmiany, zaloguj si臋 ponownie.", + "success": "Konstelacje awatara %s zosta艂y zresetowane. Aby zobaczy膰 zmiany, zaloguj si臋 ponownie.", + "description": "Resetuj konstelacje wszystkich lub wybranej postaci." }, "resetShopLimit": { - "usage": "U偶ycie: /resetshop <ID gracza>", - "success": "Reset complete.", - "description": "Reset target player's shop refresh time" + "usage": "resetshop @<ID gracza>", + "success": "Zresetowano czas od艣wie偶ania sklepu podanego gracza.", + "description": "Resetuj czas od艣wie偶ania sklepu podanego gracza." }, "sendMail": { - "usage": "U偶ycie: /sendmail <ID gracza | all | help> [id szablonu]", - "user_not_exist": "Gracz o ID '%s' nie istnieje", - "start_composition": "Komponowanie wiadomo艣ci.\nProsz臋 u偶yj '/sendmail <tytu艂>' aby kontynuowa膰.\nMo偶esz u偶y膰 '/sendmail stop' w dowolnym momencie", - "templates": "Szablony zostan膮 zaimplementowane nied艂ugo...", + "usage": "sendmail <@<ID gracza>|all|help> [ID szablonu]", + "user_not_exist": "Gracz o podanym ID %s nie istnieje.", + "start_composition": "Tworzenie wiadomo艣ci.\nU偶yj \"sendmail <tytu艂>\", aby kontynuowa膰.\nMo偶esz u偶y膰 \"sendmail stop\" w dowolnym momencie, aby przesta膰.", + "templates": "Szablony nie s膮 jeszcze gotowe do u偶ycia.", "invalid_arguments": "B艂臋dne argumenty.", "send_cancel": "Anulowano wysy艂anie wiadomo艣ci", - "send_done": "Wys艂ano wiadomo艣膰 do gracza %s!", - "send_all_done": "Wys艂ano wiadomo艣c do wszystkich graczy!", - "not_composition_end": "Komponowanie nie jest na ostatnim etapie.\nProsz臋 u偶yj '/sendmail %s' lub '/sendmail stop' aby anulowa膰", - "please_use": "Prosz臋 u偶yj '/sendmail %s'", - "set_title": "Tytu艂 wiadomo艣ci to teraz: '%s'.\nU偶yj '/sendmail <tre艣膰>' aby kontynuowa膰.", - "set_contents": "Tre艣膰 wiadomo艣ci to teraz '%s'.\nU偶yj '/sendmail <nadawca>' aby kontynuowa膰.", - "set_message_sender": "Nadawca wiadomo艣ci to teraz '%s'.\nU偶yj '/sendmail <id przedmiotu | nazwa przedmiotu | zako艅cz> [ilo艣膰] [poziom]' aby kontynuowa膰.", - "send": "Za艂膮czono %s %s (poziom %s) do wiadomo艣ci.\nDodaj wi臋cej przedmiot贸w lub u偶yj '/sendmail finish' aby wys艂a膰 wiadomo艣膰.", - "invalid_arguments_please_use": "B艂臋dne argumenty.\nProsz臋 u偶yj '/sendmail %s'", + "send_done": "Wys艂ano wiadomo艣膰 do gracza %s.", + "send_all_done": "Wys艂ano wiadomo艣膰 do wszystkich graczy.", + "not_composition_end": "Tworzenie wiadomo艣ci nie jest jeszcze na ostatnim etapie. Je偶eli naprawd臋 chcesz teraz przesta膰, u偶yj \"sendmail %s\" lub \"sendmail stop\".", + "please_use": "U偶ycie: \"sendmail %s\".", + "set_title": "Tytu艂 wiadomo艣ci to teraz:\n\n%s\n\nU偶yj \"sendmail <tre艣膰>\", aby kontynuowa膰.", + "set_contents": "Tre艣膰 wiadomo艣ci to teraz:\n\n%s\n\nU偶yj \"sendmail <nadawca>\", aby kontynuowa膰.", + "set_message_sender": "Nadawca wiadomo艣ci to teraz:\n\n%s\n\nU偶yj \"sendmail %s\", aby kontynuowa膰.", + "send": "Za艂膮czono %s przedmiot贸w %s o poziomie %s do wiadomo艣ci.\nMo偶esz doda膰 wi臋cej przedmiot贸w lub u偶y膰 \"sendmail finish\", aby wys艂a膰 wiadomo艣膰.", + "invalid_arguments_please_use": "B艂臋dne argumenty.\nProsz臋 u偶yj \"sendmail %s\"", "title": "<tytu艂>", "message": "<wiadomo艣膰>", "sender": "<nadawca>", - "arguments": "<id przedmiotu | nazwa przedmiotu | zako艅cz> [ilo艣膰] [poziom]", - "error": "B艁膭D: niepoprawny etap konstrukcji: %s. Sprawd藕 konsol臋 aby dowiedzie膰 si臋 wi臋cej." + "arguments": "<ID przedmiotu|nazwa przedmiotu|finish> [ilo艣膰] [poziom] [poziom ulepszenia]", + "error": "B艁膭D: niepoprawny etap konstrukcji: %s.", + "description": "Wy艣lij wiadomo艣膰 wraz z przedmiotami do wybranego lub wszystkich graczy." }, "sendMessage": { - "usage": "U偶ycie: /sendmessage <player> <message>", - "success": "Wiadomo艣膰 wys艂ana." + "usage": "sendmessage @<ID gracza> <wiadomo艣膰>", + "success": "Wiadomo艣膰 wys艂ana.", + "description": "Wy艣lij wiadomo艣膰 do gracza jako serwer. Je艣li nie okre艣lono celu, wysy艂a do wszystkich graczy na serwerze." }, "setFetterLevel": { - "usage": "U偶ycie: setfetterlevel <poziom>", - "range_error": "Poziom przyja藕ni musi by膰 pomi臋dzy 0,a 10.", - "success": "Poziom przyja藕ni ustawiono na: %s", - "level_error": "B艂臋dny poziom przyja藕ni." + "usage": "setfetterlevel <poziom przyja藕ni>", + "range_error": "Poziom przyja藕ni musi by膰 pomi臋dzy 0 a 10.", + "success": "Poziom przyja藕ni zosta艂 pomy艣lnie ustawiony na %s.", + "level_error": "B艂臋dny poziom przyja藕ni.", + "description": "Ustaw poziom przyja藕ni obecnej postaci." }, "setProp": { - "usage": "Usage: setprop|prop <prop> <value>\n\tValues for <prop>: godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel\n\t(cont.) see PlayerProperty enum for other possible values, of form PROP_MAX_SPRING_VOLUME -> max_spring_volume", - "description": "Sets accountwide properties. Things like godmode can be enabled this way, as well as changing things like unlocked abyss floor and battle pass progress." + "usage": "setprop <nazwa w艂asno艣ci> <warto艣膰>\n\tMo偶liwe nazwy w艂asno艣ci: godmode | nostamina | unlimitedenergy | abyss | worldlevel | bplevel | ...\n\tTa komenda ma wi臋cej nazw w艂asno艣ci, kt贸re mo偶e otrzyma膰. Mo偶esz je wszystkie zobaczy膰 w pliku \"game/props/PlayerProperty.java\".\n\tW tym pliku, przyjmuj膮 one form臋 \"PROP_XXX_YYY_ZZZ\", ale powiniene艣 je zapisywa膰 jako \"xxx_yyy_zzz\" je艣li chcesz je u偶y膰 w tej komendzie.", + "description": "Ustaw pewne w艂asno艣ci konta, takie jak tryb nie艣miertelno艣ci (godmode) czy te偶 zmiana post臋pu Battle Pass." }, "setStats": { - "usage": "U偶ycie: setstats|stats <statystyka> <warto艣膰>\n\tWarto艣ci dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG 偶ywio艂u: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na 偶ywio艂: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", - "description": "Sets fight property for your current active character" - }, - "setWorldLevel": { - "usage": "U偶ycie: setworldlevel <poziom>", - "value_error": "Poziom 艣wiata musi by膰 pomi臋dzy 0, a 8", - "success": "Ustawiono poziom 艣wiata na: %s.", - "invalid_world_level": "Invalid world level." + "usage": "setstats <nazwa statystyki> <warto艣膰>\n\tMo偶liwe nazwy statystyki: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\tDodatkowe obra偶enia od 偶ywio艂u: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\tOdporno艣膰 na 偶ywio艂: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys", + "description": "Ustaw statystyk臋 walki dla obecnie wybranej postaci wybranego gracza." }, "spawn": { - "usage": "U偶ycie: /spawn <id obiektu> [ilo艣膰] [poziom(tylko potwory)]", - "success": "Stworzono %s %s." + "usage": "spawn <ID obiektu> [ilo艣膰] [poziom (tylko potwory)] [<x> <y> <z> (tylko potwory)]", + "success": "Stworzono %s obiekt贸w o ID %s.", + "limit_reached": "Osi膮gni臋to maksymaln膮 ilo艣膰 obiekt贸w w scenie. Dodane zostan膮 tylko %s.", + "description": "Dodaj wskazane obiekty do sceny wybranego gracza." }, "stop": { - "success": "Serwer wy艂膮cza si臋..." + "usage": "stop", + "success": "Serwer zatrzymuje si臋...", + "description": "Zatrzymaj serwer." }, "talent": { - "usage_1": "Aby ustawi膰 poziom talentu: /talent set <ID talentu> <warto艣膰>", - "usage_2": "Inny spos贸b na ustawienie poziomu talentu: /talent <n lub e lub q> <warto艣膰>", - "usage_3": "Aby uzyska膰 ID talentu: /talent getid", - "lower_16": "B艂臋dny poziom talentu. Poziom powinien by膰 mniejszy ni偶 16", - "set_id": "Ustawiono talent na %s.", - "set_atk": "Ustawiono talent Atak Podstawowy na poziom %s.", + "usage_1": "Aby ustawi膰 poziom talentu, u偶yj: \"talent set <ID talentu> <warto艣膰>\".", + "usage_2": "Mo偶esz te偶 u偶y膰: \"talent <n lub e lub q> <warto艣膰>\"", + "usage_3": "Aby uzyska膰 ID talentu, u偶yj: \"talent getid\"", + "lower_16": "B艂臋dny poziom talentu. Poziom ten powinien by膰 mniejszy ni偶 16.", + "set_id": "Ustawiono poziom talentu na %s.", + "set_atk": "Ustawiono poziom talentu Atak Podstawowy na %s.", "set_e": "Ustawiono poziom talentu E na %s.", "set_q": "Ustawiono poziom talentu Q na %s.", "invalid_skill_id": "B艂臋dne ID umiej臋tno艣ci.", - "set_this": "Ustawiono ten talent na poziom %s.", + "set_this": "Ustawiono obecny talent na poziom %s.", "invalid_level": "B艂臋dny poziom talentu.", "normal_attack_id": "ID podstawowego ataku: %s.", "e_skill_id": "ID umiej臋tno艣ci E: %s.", - "q_skill_id": "ID umiej臋tno艣ci Q: %s." + "q_skill_id": "ID umiej臋tno艣ci Q: %s.", + "description": "Ustaw poziomu talentu obecnie wybranej postaci wybranego gracza." + }, + "team": { + "usage": "team <add|remove|set> [ID awatara, ...] [indeks|pierwszy|ostatni|pierwszy_indeks-ostatni_indeks, ...]", + "invalid_usage": "Nieprawid艂owe u偶ycie komendy.", + "add_usage": "team add <ID awatara, ...> [indeks]", + "invalid_index": "B艂臋dny indeks.", + "add_too_much": "Mo偶na mie膰 maksymalnie %d postaci w zespole.", + "failed_to_add_avatar": "B艂膮d podczas dodawania awatara o ID \"%s\".", + "remove_usage": "team remove <indeks|pierwszy|ostatni|pierwszy_indeks-ostatni_indeks, ...>", + "failed_to_parse_index": "B艂膮d podczas przetwarzania indeksu \"%s\".", + "remove_too_much": "Nie mo偶esz usun膮膰 wszystkich awatar贸w w zespole.", + "ignore_index": "Ignorowanie indeksu/贸w %s.", + "set_usage": "team set <indeks> <ID awatara>", + "index_out_of_range": "Podany indeks nie mie艣ci si臋 w swoim zakresie.", + "failed_parse_avatar_id": "B艂臋dny ID awatara \"%s\".", + "avatar_already_in_team": "Podany awatar jest ju偶 w zespole wybranego gracza.", + "avatar_not_found": "Awatar o ID \"%d\" nie istnieje.", + "description": "Modyfikuj zesp贸艂 wybranego gracza." }, "teleportAll": { - "success": "Przyzwano wszystkich graczy do Ciebie.", - "error": "Mo偶esz u偶y膰 tej komendy wy艂膮cznie w trybie MP." + "usage": "tpall", + "success": "Przyzwano wszystkich graczy do wybranego gracza.", + "error": "Mo偶esz u偶y膰 tej komendy wy艂膮cznie w trybie MP.", + "description": "Przyzwij wszystkich graczy do wybranego gracza." }, "teleport": { - "usage_server": "U偶ycie: /tp @<ID gracza> <x> <y> <z> [ID sceny]", - "usage": "U偶ycie: /tp [@<ID gracza>] <x> <y> <z> [ID sceny]", - "specify_player_id": "Musisz okre艣li膰 ID gracza.", - "invalid_position": "B艂臋dna pozycja.", - "exists_error": "Ta scena nie istenieje.", - "success": "Przeteleportowano %s do %s, %s, %s w scenie %s" + "usage_server": "tp @<ID gracza> <x> <y> <z> [ID sceny]", + "usage": "tp [@<ID gracza>] <x> <y> <z> [ID sceny]", + "specify_player_id": "Musisz poda膰 ID gracza.", + "invalid_position": "B艂臋dna pozycja xyz.", + "exists_error": "Ta scena nie istenieje.", + "success": "Gracz %s zosta艂 przeniesiony do pozycji (%s, %s, %s) w scenie o ID %s.", + "description": "Przemie艣膰 wybranego gracza do podanej pozycji w podanej scenie." }, "weather": { - "description": "Changes the weather.Weather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", - "usage": "Usage: weather [weatherId] [climateType]\nWeather IDs can be found in WeatherExcelConfigData.json.\nClimate types: sunny, cloudy, rain, thunderstorm, snow, mist.", - "success": "Set weather ID to %s with climate type %s.", - "status": "Current weather ID is %s with climate type %s." - }, - "help": { - "usage": "U偶ycie: ", - "aliases": "Aliasy: ", - "available_commands": "Dost臋pne komendy: " - }, - "unlocktower": { - "success": "odblokowa膰 gotowe", - "description": "Odblokuj g艂臋bok膮 spiral臋" + "usage": "weather [ID pogody] [typ klimatu]\n\tID pogody mo偶na znale藕膰 w pliku \"WeatherExcelConfigData.json\".\n\tMo偶liwe typy klimatu: sunny (s艂oneczny), cloudy (pochmurny), rain (deszcz), thunderstorm (burza), snow (艣nieg), mist (mg艂a)", + "success": "ID pogody zosta艂 ustawiony na %s, a typ klimatu na %s.", + "status": "Bie偶膮ce ID pogody to %s, a typ klimatu to %s.", + "description": "Zmie艅 ID pogody i typ klimatu." }, "ban": { - "command_usage": "Usage: ban <@playerId> [timestamp] [reason]", - "success": "Successful.", - "failure": "Failed, player not found.", - "invalid_time": "Unable to parse timestamp.", - "description": "Ban a player" + "usage": "ban @<ID gracza> [na ile czasu] [pow贸d]", + "success": "Pomy艣lnie zbanowano podanego gracza.", + "failure": "Gracz o podanym ID nie istnieje.", + "invalid_time": "Nieprawid艂owy czas bana.", + "description": "Zbanuj podanego gracza." }, "unban": { - "command_usage": "Usage: unban <@playerId>", - "success": "Successful.", - "failure": "Failed, player not found.", - "description": "Unban a player" + "usage": "unban @<ID gracza>", + "success": "Pomy艣lnie odbanowano podanego gracza.", + "failure": "Gracz o podanym ID nie istnieje.", + "description": "Odbanuj podanego gracza." } }, "gacha": { "details": { - "title": "Banner Details", - "available_five_stars": "Available 5-star Items", - "available_four_stars": "Available 4-star Items", - "available_three_stars": "Available 3-star Items" + "title": "Szczeg贸艂y losowania", + "available_five_stars": "Dost臋pne 5-gwiazdkowe przedmioty", + "available_four_stars": "Dost臋pne 4-gwiazdkowe przedmioty", + "available_three_stars": "Dost臋pne 3-gwiazdkowe przedmioty" }, "records": { - "title": "Gacha Records", - "date": "Date", - "item": "Item" + "title": "Rekordy gracza", + "date": "Data", + "item": "Przedmiot" + } + }, + "documentation": { + "handbook": { + "title": "GM Handbook", + "title_commands": "Komendy", + "title_avatars": "Awatary", + "title_items": "Przedmioty", + "title_scenes": "Sceny", + "title_monsters": "Potwory", + "header_id": "ID", + "header_command": "Komenda", + "header_description": "Opis", + "header_avatar": "Awatar", + "header_item": "Przedmiot", + "header_scene": "Scena", + "header_monster": "Potw贸r" + }, + "index": { + "title": "Dokumentacja", + "handbook": "GM Handbook", + "gacha_mapping": "Losowanie w formacie JSON" } } -} \ No newline at end of file +} -- GitLab