Merge branch 'master' into develop

This commit is contained in:
Griatch 2020-04-20 23:15:53 +02:00
commit dc81d275e6
18 changed files with 411 additions and 71 deletions

12
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: griatch
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://www.paypal.me/GriatchEvennia

View file

@ -275,11 +275,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
raise RuntimeError("Session not found") raise RuntimeError("Session not found")
if self.get_puppet(session) == obj: if self.get_puppet(session) == obj:
# already puppeting this object # already puppeting this object
self.msg("You are already puppeting this object.") self.msg(_("You are already puppeting this object."))
return return
if not obj.access(self, "puppet"): if not obj.access(self, "puppet"):
# no access # no access
self.msg(f"You don't have permission to puppet '{obj.key}'.") self.msg(_("You don't have permission to puppet '{key}'.").format(key=obj.key))
return return
if obj.account: if obj.account:
# object already puppeted # object already puppeted
@ -295,12 +295,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
else: else:
txt1 = f"Taking over |c{obj.name}|n from another of your sessions." txt1 = f"Taking over |c{obj.name}|n from another of your sessions."
txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n" txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n"
self.msg(txt1, session=session) self.msg(_(txt1), session=session)
self.msg(txt2, session=obj.sessions.all()) self.msg(_(txt2), session=obj.sessions.all())
self.unpuppet_object(obj.sessions.get()) self.unpuppet_object(obj.sessions.get())
elif obj.account.is_connected: elif obj.account.is_connected:
# controlled by another account # controlled by another account
self.msg(f"|c{obj.key}|R is already puppeted by another Account.") self.msg(_("|c{key}|R is already puppeted by another Account.").format(key=obj.key))
return return
# do the puppeting # do the puppeting
@ -496,7 +496,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# See if authentication is currently being throttled # See if authentication is currently being throttled
if ip and LOGIN_THROTTLE.check(ip): if ip and LOGIN_THROTTLE.check(ip):
errors.append("Too many login failures; please try again in a few minutes.") errors.append(_("Too many login failures; please try again in a few minutes."))
# With throttle active, do not log continued hits-- it is a # With throttle active, do not log continued hits-- it is a
# waste of storage and can be abused to make your logs harder to # waste of storage and can be abused to make your logs harder to
@ -508,8 +508,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if banned: if banned:
# this is a banned IP or name! # this is a banned IP or name!
errors.append( errors.append(
"|rYou have been banned and cannot continue from here." _("|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x" "\nIf you feel this ban is in error, please email an admin.|x")
) )
logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).") logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).")
LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.") LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.")
@ -519,7 +519,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
account = authenticate(username=username, password=password) account = authenticate(username=username, password=password)
if not account: if not account:
# User-facing message # User-facing message
errors.append("Username and/or password is incorrect.") errors.append(_("Username and/or password is incorrect."))
# Log auth failures while throttle is inactive # Log auth failures while throttle is inactive
logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).") logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).")
@ -688,7 +688,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
ip = kwargs.get("ip", "") ip = kwargs.get("ip", "")
if ip and CREATION_THROTTLE.check(ip): if ip and CREATION_THROTTLE.check(ip):
errors.append( errors.append(
"You are creating too many accounts. Please log into an existing account." _("You are creating too many accounts. Please log into an existing account.")
) )
return None, errors return None, errors
@ -717,8 +717,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if banned: if banned:
# this is a banned IP or name! # this is a banned IP or name!
string = ( string = (
"|rYou have been banned and cannot continue from here." _("|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x" "\nIf you feel this ban is in error, please email an admin.|x")
) )
errors.append(string) errors.append(string)
return None, errors return None, errors
@ -733,7 +733,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
except Exception as e: except Exception as e:
errors.append( errors.append(
"There was an error creating the Account. If this problem persists, contact an admin." _("There was an error creating the Account. If this problem persists, contact an admin.")
) )
logger.log_trace() logger.log_trace()
return None, errors return None, errors
@ -785,7 +785,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# We are in the middle between logged in and -not, so we have # We are in the middle between logged in and -not, so we have
# to handle tracebacks ourselves at this point. If we don't, # to handle tracebacks ourselves at this point. If we don't,
# we won't see any errors at all. # we won't see any errors at all.
errors.append("An error occurred. Please e-mail an admin if the problem persists.") errors.append(_("An error occurred. Please e-mail an admin if the problem persists."))
logger.log_trace() logger.log_trace()
# Update the throttle to indicate a new account was created from this IP # Update the throttle to indicate a new account was created from this IP
@ -1253,21 +1253,21 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if session: if session:
session.msg(logged_in={}) session.msg(logged_in={})
self._send_to_connect_channel(f"|G{self.key} connected|n") self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
if _MULTISESSION_MODE == 0: if _MULTISESSION_MODE == 0:
# in this mode we should have only one character available. We # in this mode we should have only one character available. We
# try to auto-connect to our last conneted object, if any # try to auto-connect to our last conneted object, if any
try: try:
self.puppet_object(session, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
except RuntimeError: except RuntimeError:
self.msg("The Character does not exist.") self.msg(_("The Character does not exist."))
return return
elif _MULTISESSION_MODE == 1: elif _MULTISESSION_MODE == 1:
# in this mode all sessions connect to the same puppet. # in this mode all sessions connect to the same puppet.
try: try:
self.puppet_object(session, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
except RuntimeError: except RuntimeError:
self.msg("The Character does not exist.") self.msg(_("The Character does not exist."))
return return
elif _MULTISESSION_MODE in (2, 3): elif _MULTISESSION_MODE in (2, 3):
# In this mode we by default end up at a character selection # In this mode we by default end up at a character selection
@ -1305,7 +1305,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
""" """
reason = f" ({reason if reason else ''})" reason = f" ({reason if reason else ''})"
self._send_to_connect_channel(f"|R{self.key} disconnected{reason}|n") self._send_to_connect_channel(_("|R{key} disconnected{reason}|n").format(key=self.key, reason=reason))
def at_post_disconnect(self, **kwargs): def at_post_disconnect(self, **kwargs):
""" """
@ -1411,7 +1411,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if hasattr(target, "return_appearance"): if hasattr(target, "return_appearance"):
return target.return_appearance(self) return target.return_appearance(self)
else: else:
return "{} has no in-game appearance.".format(target) return _("{target} has no in-game appearance.").format(target=target)
else: else:
# list of targets - make list to disconnect from db # list of targets - make list to disconnect from db
characters = list(tar for tar in target if tar) if target else [] characters = list(tar for tar in target if tar) if target else []
@ -1454,7 +1454,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if is_su or len(characters) < charmax: if is_su or len(characters) < charmax:
if not characters: if not characters:
result.append( result.append(
"\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one." _("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.")
) )
else: else:
result.append("\n |w@charcreate <name> [=description]|n - create new character") result.append("\n |w@charcreate <name> [=description]|n - create new character")
@ -1534,7 +1534,7 @@ class DefaultGuest(DefaultAccount):
# check if guests are enabled. # check if guests are enabled.
if not settings.GUEST_ENABLED: if not settings.GUEST_ENABLED:
errors.append("Guest accounts are not enabled on this server.") errors.append(_("Guest accounts are not enabled on this server."))
return None, errors return None, errors
try: try:
@ -1544,7 +1544,7 @@ class DefaultGuest(DefaultAccount):
username = name username = name
break break
if not username: if not username:
errors.append("All guest accounts are in use. Please try again later.") errors.append(_("All guest accounts are in use. Please try again later."))
if ip: if ip:
LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.") LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.")
return None, errors return None, errors
@ -1572,7 +1572,7 @@ class DefaultGuest(DefaultAccount):
# We are in the middle between logged in and -not, so we have # We are in the middle between logged in and -not, so we have
# to handle tracebacks ourselves at this point. If we don't, # to handle tracebacks ourselves at this point. If we don't,
# we won't see any errors at all. # we won't see any errors at all.
errors.append("An error occurred. Please e-mail an admin if the problem persists.") errors.append(_("An error occurred. Please e-mail an admin if the problem persists."))
logger.log_trace() logger.log_trace()
return None, errors return None, errors
@ -1589,7 +1589,7 @@ class DefaultGuest(DefaultAccount):
overriding the call (unused by default). overriding the call (unused by default).
""" """
self._send_to_connect_channel(f"|G{self.key} connected|n") self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
self.puppet_object(session, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
def at_server_shutdown(self): def at_server_shutdown(self):

View file

@ -10,6 +10,7 @@ from evennia.accounts.accounts import DefaultAccount
from evennia.scripts.scripts import DefaultScript from evennia.scripts.scripts import DefaultScript
from evennia.utils import search from evennia.utils import search
from evennia.utils import utils from evennia.utils import utils
from django.utils.translation import gettext as _
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
@ -328,7 +329,7 @@ class IRCBot(Bot):
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
for obj in self._nicklist_callers: for obj in self._nicklist_callers:
obj.msg(f"Nicks at {chstr}:\n {nicklist}") obj.msg(_("Nicks at {chstr}:\n {nicklist}").format(chstr=chstr, nicklist=nicklist))
self._nicklist_callers = [] self._nicklist_callers = []
return return
@ -337,7 +338,7 @@ class IRCBot(Bot):
if hasattr(self, "_ping_callers") and self._ping_callers: if hasattr(self, "_ping_callers") and self._ping_callers:
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
for obj in self._ping_callers: for obj in self._ping_callers:
obj.msg(f"IRC ping return from {chstr} took {kwargs['timing']}s.") obj.msg(_("IRC ping return from {chstr} took {time}s.").format(chstr=chstr, time=kwargs['timing']))
self._ping_callers = [] self._ping_callers = []
return return

View file

@ -743,7 +743,7 @@ def cmdhandler(
sysarg = raw_string sysarg = raw_string
else: else:
# fallback to default error text # fallback to default error text
sysarg = _("Command '%s' is not available.") % raw_string sysarg = _("Command '{command}' is not available.").format(command=raw_string)
suggestions = string_suggestions( suggestions = string_suggestions(
raw_string, raw_string,
cmdset.get_all_cmd_keys_and_aliases(caller), cmdset.get_all_cmd_keys_and_aliases(caller),
@ -751,9 +751,7 @@ def cmdhandler(
maxnum=3, maxnum=3,
) )
if suggestions: if suggestions:
sysarg += _(" Maybe you meant %s?") % utils.list_to_string( sysarg += _(" Maybe you meant {command}?").format(command=utils.list_to_string(suggestions, _("or"), addquote=True))
suggestions, _("or"), addquote=True
)
else: else:
sysarg += _(' Type "help" for help.') sysarg += _(' Type "help" for help.')
raise ExecSystemCommand(syscmd, sysarg) raise ExecSystemCommand(syscmd, sysarg)

View file

@ -184,7 +184,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
raise exc.with_traceback(tb) raise exc.with_traceback(tb)
else: else:
# try next suggested path # try next suggested path
errstring += _("\n(Unsuccessfully tried '%s')." % python_path) errstring += _("\n(Unsuccessfully tried '{path}').").format(path=python_path)
continue continue
try: try:
cmdsetclass = getattr(module, classname) cmdsetclass = getattr(module, classname)
@ -194,7 +194,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
dum, dum, tb = sys.exc_info() dum, dum, tb = sys.exc_info()
raise exc.with_traceback(tb) raise exc.with_traceback(tb)
else: else:
errstring += _("\n(Unsuccessfully tried '%s')." % python_path) errstring += _("\n(Unsuccessfully tried '{path}').").format(path=python_path)
continue continue
_CACHED_CMDSETS[python_path] = cmdsetclass _CACHED_CMDSETS[python_path] = cmdsetclass

View file

@ -119,17 +119,17 @@ class ChannelCommand(command.Command):
caller = caller if not hasattr(caller, "account") else caller.account caller = caller if not hasattr(caller, "account") else caller.account
unmuted = channel.unmute(caller) unmuted = channel.unmute(caller)
if unmuted: if unmuted:
self.msg("You start listening to %s." % channel) self.msg(_("You start listening to %s.") % channel)
return return
self.msg("You were already listening to %s." % channel) self.msg(_("You were already listening to %s.") % channel)
return return
if msg == "off": if msg == "off":
caller = caller if not hasattr(caller, "account") else caller.account caller = caller if not hasattr(caller, "account") else caller.account
muted = channel.mute(caller) muted = channel.mute(caller)
if muted: if muted:
self.msg("You stop listening to %s." % channel) self.msg(_("You stop listening to %s.") % channel)
return return
self.msg("You were already not listening to %s." % channel) self.msg(_("You were already not listening to %s.") % channel)
return return
if self.history_start is not None: if self.history_start is not None:
# Try to view history # Try to view history
@ -144,7 +144,7 @@ class ChannelCommand(command.Command):
else: else:
caller = caller if not hasattr(caller, "account") else caller.account caller = caller if not hasattr(caller, "account") else caller.account
if caller in channel.mutelist: if caller in channel.mutelist:
self.msg("You currently have %s muted." % channel) self.msg(_("You currently have %s muted.") % channel)
return return
channel.msg(msg, senders=self.caller, online=True) channel.msg(msg, senders=self.caller, online=True)

View file

@ -131,7 +131,7 @@ class HelpEntryManager(TypedObjectManager):
for topic in topics: for topic in topics:
topic.help_category = default_category topic.help_category = default_category
topic.save() topic.save()
string = "Help database moved to category %s" % default_category string = _("Help database moved to category {default_category}").format(default_category=default_category)
logger.log_info(string) logger.log_info(string)
def search_help(self, ostring, help_category=None): def search_help(self, ostring, help_category=None):

Binary file not shown.

View file

@ -0,0 +1,299 @@
msgid ""
msgstr ""
"Project-Id-Version: Evennia Russian Translation v0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 12:13+0000\n"
"PO-Revision-Date: 2020-04-19 18:32+0000\n"
"Last-Translator: 3eluk\n"
"Language-Team: Russian (Russia)\n"
"Language: ru-RU\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && "
"n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Loco-Source-Locale: ru_RU\n"
"X-Generator: Loco https://localise.biz/\n"
"X-Loco-Parser: loco_parse_po"
#: accounts/accounts.py:440
msgid "Account being deleted."
msgstr "Аккаунт удаляется."
#: commands/cmdhandler.py:681
msgid "There were multiple matches."
msgstr "Здесь было несколько совпадений."
#: commands/cmdhandler.py:704
#, python-format
msgid "Command '%s' is not available."
msgstr "Команда '%s' недоступна."
#: commands/cmdhandler.py:709
#, python-format
msgid " Maybe you meant %s?"
msgstr "Возможно, вы имели ввиду %s?"
#: commands/cmdhandler.py:709
msgid "or"
msgstr "или"
#: commands/cmdhandler.py:711
msgid " Type \"help\" for help."
msgstr " Введи \"справка\" для получения помощи."
#: commands/cmdsethandler.py:89
msgid ""
"{traceback}\n"
"Error loading cmdset '{path}'\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:94
msgid ""
"Error loading cmdset: No cmdset class '{classname}' in '{path}'.\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:98
msgid ""
"{traceback}\n"
"SyntaxError encountered when loading cmdset '{path}'.\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:103
msgid ""
"{traceback}\n"
"Compile/Run error when loading cmdset '{path}'.\",\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:108
msgid ""
"\n"
"Error encountered for cmdset at path '{path}'.\n"
"Replacing with fallback '{fallback_path}'.\n"
msgstr ""
#: commands/cmdsethandler.py:114
msgid "Fallback path '{fallback_path}' failed to generate a cmdset."
msgstr ""
#: commands/cmdsethandler.py:182 commands/cmdsethandler.py:192
#, python-format
msgid ""
"\n"
"(Unsuccessfully tried '%s')."
msgstr ""
"\n"
"(Безуспешно пробую '%s')."
#: commands/cmdsethandler.py:311
msgid "custom {mergetype} on cmdset '{cmdset}'"
msgstr ""
#: commands/cmdsethandler.py:314
msgid " <Merged {mergelist} {mergetype}, prio {prio}>: {current}"
msgstr ""
#: commands/cmdsethandler.py:322
msgid ""
" <{key} ({mergetype}, prio {prio}, {permstring})>:\n"
" {keylist}"
msgstr ""
#: commands/cmdsethandler.py:426
msgid "Only CmdSets can be added to the cmdsethandler!"
msgstr ""
#: comms/channelhandler.py:100
msgid "Say what?"
msgstr "Сказать что?"
#: comms/channelhandler.py:105
#, python-format
msgid "Channel '%s' not found."
msgstr "Канал '%s' не обнаружен."
#: comms/channelhandler.py:108
#, python-format
msgid "You are not connected to channel '%s'."
msgstr "Ты не соединён с каналом '%s'."
#: comms/channelhandler.py:112
#, python-format
msgid "You are not permitted to send to channel '%s'."
msgstr "У тебя нет разрешения слать в канал '%s'."
#: comms/channelhandler.py:155
msgid " (channel)"
msgstr " (канал)"
#: locks/lockhandler.py:236
#, python-format
msgid "Lock: lock-function '%s' is not available."
msgstr ""
#: locks/lockhandler.py:249
#, python-format
msgid "Lock: definition '%s' has syntax errors."
msgstr ""
#: locks/lockhandler.py:253
#, python-format
msgid ""
"LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)"
"s' to '%(goal)s' "
msgstr ""
#: locks/lockhandler.py:320
msgid "Lock: '{lockdef}' contains no colon (:)."
msgstr ""
#: locks/lockhandler.py:328
msgid "Lock: '{lockdef}' has no access_type (left-side of colon is empty)."
msgstr ""
#: locks/lockhandler.py:336
msgid "Lock: '{lockdef}' has mismatched parentheses."
msgstr ""
#: locks/lockhandler.py:343
msgid "Lock: '{lockdef}' has no valid lock functions."
msgstr ""
#: objects/objects.py:732
#, python-format
msgid "Couldn't perform move ('%s'). Contact an admin."
msgstr "Не удалось выполнить действие ('%s'). Свяжитесь с администратором."
#: objects/objects.py:742
msgid "The destination doesn't exist."
msgstr "Такой точки назначения нету."
#: objects/objects.py:833
#, python-format
msgid "Could not find default home '(#%d)'."
msgstr "Не обнаружен дом по умолчанию '(#%d)'."
#: objects/objects.py:849
msgid "Something went wrong! You are dumped into nowhere. Contact an admin."
msgstr ""
"Что-то пошло не так! Тебя выбрасывает в пустоту. Свяжитесь с администратором."
#: objects/objects.py:915
#, python-format
msgid "Your character %s has been destroyed."
msgstr "Ваш персонаж %s был уничтожен."
#: scripts/scripthandler.py:53
#, python-format
msgid ""
"\n"
" '%(key)s' (%(next_repeat)s/%(interval)s, %(repeats)s repeats): %(desc)s"
msgstr ""
#: scripts/scripts.py:205
#, python-format
msgid ""
"Script %(key)s(#%(dbid)s) of type '%(cname)s': at_repeat() error '%(err)s'."
msgstr ""
#: server/initial_setup.py:28
msgid ""
"\n"
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
"you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Account #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build|n.\n"
" "
msgstr ""
"\n"
"Добро пожаловать в твою новую игру, основанную на |wEvennia|n! Посети http:"
"//www.evennia.com\n"
"если тебе нужна помощь, хочешь помочь, сообщить об ошибках, lили просто "
"присоединиться к сообществу.\n"
"Как Аккаунт №1, ты можешь создать зону для демонстрации/обучения командой "
"|w@batchcommand tutorial_world.build|n.\n"
" "
#: server/initial_setup.py:92
msgid "This is User #1."
msgstr "Это Пользователь №1."
#: server/initial_setup.py:105
msgid "Limbo"
msgstr "Лимб"
#: server/server.py:139
msgid "idle timeout exceeded"
msgstr "время бездействия превышено"
#: server/sessionhandler.py:386
msgid " ... Server restarted."
msgstr " ... Сервер перезапущен."
#: server/sessionhandler.py:606
msgid "Logged in from elsewhere. Disconnecting."
msgstr "Выполнено соединение в другом месте. Отключение."
#: server/sessionhandler.py:634
msgid "Idle timeout exceeded, disconnecting."
msgstr "Время бездействия превышено, отключение."
#: server/validators.py:50
#, python-format
msgid ""
"%s From a terminal client, you can also use a phrase of multiple words if "
"you enclose the password in double quotes."
msgstr ""
"%s Если вы используете терминал, вы можете использовать фразу из нескольких "
"слов если возьмёте пароль в двойные скобки."
#: utils/evmenu.py:192
msgid ""
"Menu node '{nodename}' is either not implemented or caused an error. Make "
"another choice."
msgstr ""
#: utils/evmenu.py:194
msgid "Error in menu node '{nodename}'."
msgstr ""
#: utils/evmenu.py:195
msgid "No description."
msgstr "Нет описания."
#: utils/evmenu.py:196
msgid "Commands: <menu option>, help, quit"
msgstr "Команды: <menu option>, справка, выход"
#: utils/evmenu.py:197
msgid "Commands: <menu option>, help"
msgstr "Команды: <menu option>, справка"
#: utils/evmenu.py:198
msgid "Commands: help, quit"
msgstr ""
#: utils/evmenu.py:199
msgid "Commands: help"
msgstr "Команды: справка"
#: utils/evmenu.py:200
msgid "Choose an option or try 'help'."
msgstr "Выберите опцию или введите \"справка\"."
#: utils/utils.py:1866
#, python-format
msgid "Could not find '%s'."
msgstr "Не обнаружено '%s'."
#: utils/utils.py:1873
#, python-format
msgid ""
"More than one match for '%s' (please narrow target):\n"
msgstr ""
"Больше одного подходящего варианта для '%s' (уточните цель):\n"

View file

@ -246,7 +246,7 @@ class LockHandler(object):
evalstring = " ".join(_RE_OK.findall(evalstring)) evalstring = " ".join(_RE_OK.findall(evalstring))
eval(evalstring % tuple(True for func in funclist), {}, {}) eval(evalstring % tuple(True for func in funclist), {}, {})
except Exception: except Exception:
elist.append(_("Lock: definition '%s' has syntax errors.") % raw_lockstring) elist.append(_("Lock: definition '{lock_string}' has syntax errors.").format(lock_string=raw_lockstring))
continue continue
if access_type in locks: if access_type in locks:
duplicates += 1 duplicates += 1

View file

@ -8,7 +8,7 @@ from django.contrib import admin
from evennia.typeclasses.admin import AttributeInline, TagInline from evennia.typeclasses.admin import AttributeInline, TagInline
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
from django.contrib.admin.utils import flatten_fieldsets from django.contrib.admin.utils import flatten_fieldsets
from django.utils.translation import gettext as _
class ObjectAttributeInline(AttributeInline): class ObjectAttributeInline(AttributeInline):
""" """
@ -61,7 +61,7 @@ class ObjectCreateForm(forms.ModelForm):
required=False, required=False,
widget=forms.TextInput(attrs={"size": "78"}), widget=forms.TextInput(attrs={"size": "78"}),
help_text="Most non-character objects don't need a cmdset" help_text="Most non-character objects don't need a cmdset"
" and can leave this field blank.", " and can leave this field blank."
) )
raw_id_fields = ("db_destination", "db_location", "db_home") raw_id_fields = ("db_destination", "db_location", "db_home")

View file

@ -1059,7 +1059,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# See if we need to kick the account off. # See if we need to kick the account off.
for session in self.sessions.all(): for session in self.sessions.all():
session.msg(_("Your character %s has been destroyed.") % self.key) session.msg(_("Your character {key} has been destroyed.").format(key=self.key))
# no need to disconnect, Account just jumps to OOC mode. # no need to disconnect, Account just jumps to OOC mode.
# sever the connection (important!) # sever the connection (important!)
if self.account: if self.account:

View file

@ -465,11 +465,12 @@ def getKeyPair(pubkeyfile, privkeyfile):
if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)):
# No keypair exists. Generate a new RSA keypair # No keypair exists. Generate a new RSA keypair
from Crypto.PublicKey import RSA from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
rsa_key = Key(RSA.generate(_KEY_LENGTH)) rsa_key = Key(rsa.generate_private_key(public_exponent=65537, key_size=_KEY_LENGTH, backend=default_backend()))
public_key_string = rsa_key.public().toString(type="OPENSSH") public_key_string = rsa_key.public().toString(type="OPENSSH").decode()
private_key_string = rsa_key.toString(type="OPENSSH") private_key_string = rsa_key.toString(type="OPENSSH").decode()
# save keys for the future. # save keys for the future.
with open(privkeyfile, "wt") as pfile: with open(privkeyfile, "wt") as pfile:

View file

@ -4,7 +4,7 @@ Master configuration file for Evennia.
NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE! NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE!
All settings changes should be done by copy-pasting the variable and All settings changes should be done by copy-pasting the variable and
its value to <gamedir>/conf/settings.py. its value to <gamedir>/server/conf/settings.py.
Hint: Don't copy&paste over more from this file than you actually want Hint: Don't copy&paste over more from this file than you actually want
to change. Anything you don't copy&paste will thus retain its default to change. Anything you don't copy&paste will thus retain its default

View file

@ -31,7 +31,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
# Attribute manager methods # Attribute manager methods
def get_attribute( def get_attribute(
self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None, **kwargs
): ):
""" """
Return Attribute objects by key, by category, by value, by Return Attribute objects by key, by category, by value, by
@ -55,6 +56,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
attrype (str, optional): An attribute-type to search for. attrype (str, optional): An attribute-type to search for.
By default this is either `None` (normal Attributes) or By default this is either `None` (normal Attributes) or
`"nick"`. `"nick"`.
kwargs (any): Currently unused. Reserved for future use.
Returns: Returns:
attributes (list): The matching Attributes. attributes (list): The matching Attributes.
@ -102,7 +104,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
key=key, category=category, value=value, strvalue=strvalue, obj=obj key=key, category=category, value=value, strvalue=strvalue, obj=obj
) )
def get_by_attribute(self, key=None, category=None, value=None, strvalue=None, attrtype=None): def get_by_attribute(self, key=None, category=None, value=None,
strvalue=None, attrtype=None, **kwargs):
""" """
Return objects having attributes with the given key, category, Return objects having attributes with the given key, category,
value, strvalue or combination of those criteria. value, strvalue or combination of those criteria.
@ -122,6 +125,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
attrype (str, optional): An attribute-type to search for. attrype (str, optional): An attribute-type to search for.
By default this is either `None` (normal Attributes) or By default this is either `None` (normal Attributes) or
`"nick"`. `"nick"`.
kwargs (any): Currently unused. Reserved for future use.
Returns: Returns:
obj (list): Objects having the matching Attributes. obj (list): Objects having the matching Attributes.
@ -488,12 +492,12 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
def get_typeclass_totals(self, *args, **kwargs) -> object: def get_typeclass_totals(self, *args, **kwargs) -> object:
""" """
Returns a queryset of typeclass composition statistics. Returns a queryset of typeclass composition statistics.
Returns: Returns:
qs (Queryset): A queryset of dicts containing the typeclass (name), qs (Queryset): A queryset of dicts containing the typeclass (name),
the count of objects with that typeclass and a float representing the count of objects with that typeclass and a float representing
the percentage of objects associated with the typeclass. the percentage of objects associated with the typeclass.
""" """
return ( return (
self.values("db_typeclass_path") self.values("db_typeclass_path")

View file

@ -205,27 +205,31 @@ help_entries = search_help
# not the attribute object itself (this is usually what you want) # not the attribute object itself (this is usually what you want)
def search_object_attribute(key=None, category=None, value=None, strvalue=None): def search_object_attribute(key=None, category=None, value=None,
strvalue=None, attrtype=None, **kwargs):
return ObjectDB.objects.get_by_attribute( return ObjectDB.objects.get_by_attribute(
key=key, category=category, value=value, strvalue=strvalue key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
) )
def search_account_attribute(key=None, category=None, value=None, strvalue=None): def search_account_attribute(key=None, category=None, value=None,
strvalue=None, attrtype=None, **kwargs):
return AccountDB.objects.get_by_attribute( return AccountDB.objects.get_by_attribute(
key=key, category=category, value=value, strvalue=strvalue key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
) )
def search_script_attribute(key=None, category=None, value=None, strvalue=None): def search_script_attribute(key=None, category=None, value=None,
strvalue=None, attrtype=None, **kwargs):
return ScriptDB.objects.get_by_attribute( return ScriptDB.objects.get_by_attribute(
key=key, category=category, value=value, strvalue=strvalue key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
) )
def search_channel_attribute(key=None, category=None, value=None, strvalue=None): def search_channel_attribute(key=None, category=None, value=None,
strvalue=None, attrtype=None, **kwargs):
return Channel.objects.get_by_attribute( return Channel.objects.get_by_attribute(
key=key, category=category, value=value, strvalue=strvalue key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
) )
@ -243,7 +247,7 @@ search_attribute_object = ObjectDB.objects.get_attribute
# object itself (this is usually what you want) # object itself (this is usually what you want)
def search_object_by_tag(key=None, category=None): def search_object_by_tag(key=None, category=None, tagtype=None, **kwargs):
""" """
Find object based on tag or category. Find object based on tag or category.
@ -252,6 +256,11 @@ def search_object_by_tag(key=None, category=None):
category (str, optional): The category of tag category (str, optional): The category of tag
to search for. If not set, uncategorized to search for. If not set, uncategorized
tags will be searched. tags will be searched.
tagtype (str, optional): 'type' of Tag, by default
this is either `None` (a normal Tag), `alias` or
`permission`. This always apply to all queried tags.
kwargs (any): Other optional parameter that may be supported
by the manager method.
Returns: Returns:
matches (list): List of Objects with tags matching matches (list): List of Objects with tags matching
@ -259,13 +268,13 @@ def search_object_by_tag(key=None, category=None):
matches were found. matches were found.
""" """
return ObjectDB.objects.get_by_tag(key=key, category=category) return ObjectDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
search_tag = search_object_by_tag # this is the most common case search_tag = search_object_by_tag # this is the most common case
def search_account_tag(key=None, category=None): def search_account_tag(key=None, category=None, tagtype=None, **kwargs):
""" """
Find account based on tag or category. Find account based on tag or category.
@ -274,6 +283,11 @@ def search_account_tag(key=None, category=None):
category (str, optional): The category of tag category (str, optional): The category of tag
to search for. If not set, uncategorized to search for. If not set, uncategorized
tags will be searched. tags will be searched.
tagtype (str, optional): 'type' of Tag, by default
this is either `None` (a normal Tag), `alias` or
`permission`. This always apply to all queried tags.
kwargs (any): Other optional parameter that may be supported
by the manager method.
Returns: Returns:
matches (list): List of Accounts with tags matching matches (list): List of Accounts with tags matching
@ -281,10 +295,10 @@ def search_account_tag(key=None, category=None):
matches were found. matches were found.
""" """
return AccountDB.objects.get_by_tag(key=key, category=category) return AccountDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
def search_script_tag(key=None, category=None): def search_script_tag(key=None, category=None, tagtype=None, **kwargs):
""" """
Find script based on tag or category. Find script based on tag or category.
@ -293,6 +307,11 @@ def search_script_tag(key=None, category=None):
category (str, optional): The category of tag category (str, optional): The category of tag
to search for. If not set, uncategorized to search for. If not set, uncategorized
tags will be searched. tags will be searched.
tagtype (str, optional): 'type' of Tag, by default
this is either `None` (a normal Tag), `alias` or
`permission`. This always apply to all queried tags.
kwargs (any): Other optional parameter that may be supported
by the manager method.
Returns: Returns:
matches (list): List of Scripts with tags matching matches (list): List of Scripts with tags matching
@ -300,10 +319,10 @@ def search_script_tag(key=None, category=None):
matches were found. matches were found.
""" """
return ScriptDB.objects.get_by_tag(key=key, category=category) return ScriptDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
def search_channel_tag(key=None, category=None): def search_channel_tag(key=None, category=None, tagtype=None, **kwargs):
""" """
Find channel based on tag or category. Find channel based on tag or category.
@ -312,6 +331,11 @@ def search_channel_tag(key=None, category=None):
category (str, optional): The category of tag category (str, optional): The category of tag
to search for. If not set, uncategorized to search for. If not set, uncategorized
tags will be searched. tags will be searched.
tagtype (str, optional): 'type' of Tag, by default
this is either `None` (a normal Tag), `alias` or
`permission`. This always apply to all queried tags.
kwargs (any): Other optional parameter that may be supported
by the manager method.
Returns: Returns:
matches (list): List of Channels with tags matching matches (list): List of Channels with tags matching
@ -319,7 +343,7 @@ def search_channel_tag(key=None, category=None):
matches were found. matches were found.
""" """
return Channel.objects.get_by_tag(key=key, category=category) return Channel.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
# search for tag objects (not the objects they are attached to # search for tag objects (not the objects they are attached to

View file

@ -2041,7 +2041,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
if multimatch_string: if multimatch_string:
error = "%s\n" % multimatch_string error = "%s\n" % multimatch_string
else: else:
error = _("More than one match for '%s' (please narrow target):\n" % query) error = _("More than one match for '{query}' (please narrow target):\n").format(query=query)
for num, result in enumerate(matches): for num, result in enumerate(matches):
# we need to consider Commands, where .aliases is a list # we need to consider Commands, where .aliases is a list

View file

@ -15,6 +15,7 @@ from django.core.exceptions import ValidationError as _error
from django.core.validators import validate_email as _val_email from django.core.validators import validate_email as _val_email
from evennia.utils.ansi import strip_ansi from evennia.utils.ansi import strip_ansi
from evennia.utils.utils import string_partial_matching as _partial from evennia.utils.utils import string_partial_matching as _partial
from django.utils.translation import gettext as _
_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones} _TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
@ -58,7 +59,7 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs)
""" """
if not entry: if not entry:
raise ValueError(f"No {option_key} entered!") raise ValueError(_("No {option_key} entered!").format(option_key=option_key))
if not from_tz: if not from_tz:
from_tz = _pytz.UTC from_tz = _pytz.UTC
if account: if account:
@ -66,7 +67,7 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs)
try: try:
from_tz = _pytz.timezone(acct_tz) from_tz = _pytz.timezone(acct_tz)
except Exception as err: except Exception as err:
raise ValueError(f"Timezone string '{acct_tz}' is not a valid timezone ({err})") raise ValueError(_("Timezone string '{acct_tz}' is not a valid timezone ({err})").format(acct_tz=acct_tz, err=err))
else: else:
from_tz = _pytz.UTC from_tz = _pytz.UTC