Language linting (#1382)

This commit is contained in:
Luke H-W 2022-07-11 23:51:05 +09:30 committed by GitHub
parent bb84432b4c
commit 8843276c41
8 changed files with 676 additions and 300 deletions

27
.github/workflows/language_lint.yml vendored Normal file
View File

@ -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

321
manage_languages.py Normal file
View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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());
}
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());
}
}

View File

@ -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" -> {

View File

@ -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"
}
}
}
}