From 8843276c41b1c6682cf9f9566bed5355829cbf18 Mon Sep 17 00:00:00 2001 From: Luke H-W 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 000000000..17ee7d7e7 --- /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 000000000..ee7d1a5c1 --- /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 ba77e7d6e..efe637530 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 bc15ec9ab..fe19075bf 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 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 74d36e93d..dab45378d 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 8a00d9cfc..65e7228cc 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 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 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()); - } + StringBuilder builder = new StringBuilder(translate(player, "commands.help.available_commands")); + annotations.forEach(annotation -> { + this.createCommand(builder, player, annotation); + builder.append("\n"); + }); - builder.append(" "); - - if(!annotation.permissionTargeted().isEmpty() && !annotation.permissionTargeted().isBlank()) { - String permissionTargeted = annotation.permissionTargeted(); - builder.append(translate(player, "commands.help.tip_permission_targeted", permissionTargeted)); - } - - 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 c4369fa86..79f13e208 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 edc444652..41d63f708 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 [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 [uid]" + "description": "Twórz lub usuń konta." + }, + "announce": { + "usage": "announce LUB announce LUB announce LUB announce ", + "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 [lv] [r] [*]", - "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 [lv] [r] [*]", + "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 ", - "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] [[,]]... [poziom]", - "id_error": "Błędne ID artefaktu.", - "success": "Dano %s dla %s." + "usage": "enterdungeon ", + "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 [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 [x] [lv] [r]", + "usage_relic": "give [ID pierwszego przedmiotu] [[, ]]... [lv]", + "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": "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": "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 [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 ", - "success": "Reset complete.", - "description": "Reset target player's shop refresh time" + "usage": "resetshop @", + "success": "Zresetowano czas odświeżania sklepu podanego gracza.", + "description": "Resetuj czas odświeżania sklepu podanego gracza." }, "sendMail": { - "usage": "Użycie: /sendmail [id szablonu]", - "user_not_exist": "Gracz o ID '%s' nie istnieje", - "start_composition": "Komponowanie wiadomości.\nProszę użyj '/sendmail ' aby kontynuować.\nMożesz użyć '/sendmail stop' w dowolnym momencie", - "templates": "Szablony zostaną zaimplementowane niedługo...", + "usage": "sendmail <@|all|help> [ID szablonu]", + "user_not_exist": "Gracz o podanym ID %s nie istnieje.", + "start_composition": "Tworzenie wiadomości.\nUżyj \"sendmail \", 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 ' aby kontynuować.", - "set_contents": "Treść wiadomości to teraz '%s'.\nUżyj '/sendmail ' aby kontynuować.", - "set_message_sender": "Nadawca wiadomości to teraz '%s'.\nUżyj '/sendmail [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 \", aby kontynuować.", + "set_contents": "Treść wiadomości to teraz:\n\n%s\n\nUżyj \"sendmail \", 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": "", "message": "", "sender": "", - "arguments": " [ilość] [poziom]", - "error": "BŁĄD: niepoprawny etap konstrukcji: %s. Sprawdź konsolę aby dowiedzieć się więcej." + "arguments": " [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 ", - "success": "Wiadomość wysłana." + "usage": "sendmessage @ ", + "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 ", - "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 ", + "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 \n\tValues for : 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 \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 \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 ", - "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 \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 [ilość] [poziom(tylko potwory)]", - "success": "Stworzono %s %s." + "usage": "spawn [ilość] [poziom (tylko potwory)] [ (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 ", - "usage_2": "Inny sposób na ustawienie poziomu talentu: /talent ", - "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 \".", + "usage_2": "Możesz też użyć: \"talent \"", + "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 [ID awatara, ...] [indeks|pierwszy|ostatni|pierwszy_indeks-ostatni_indeks, ...]", + "invalid_usage": "Nieprawidłowe użycie komendy.", + "add_usage": "team add [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 ", + "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 ", + "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 sceny]", - "usage": "Użycie: /tp [@] [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 sceny]", + "usage": "tp [@] [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 @ [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 @", + "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 +}