diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..30ef101a02 --- /dev/null +++ b/.github/FUNDING.yml @@ -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 diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 397ea287b6..8986d18473 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -275,11 +275,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): raise RuntimeError("Session not found") if self.get_puppet(session) == obj: # already puppeting this object - self.msg("You are already puppeting this object.") + self.msg(_("You are already puppeting this object.")) return if not obj.access(self, "puppet"): # 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 if obj.account: # object already puppeted @@ -295,12 +295,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): else: 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" - self.msg(txt1, session=session) - self.msg(txt2, session=obj.sessions.all()) + self.msg(_(txt1), session=session) + self.msg(_(txt2), session=obj.sessions.all()) self.unpuppet_object(obj.sessions.get()) elif obj.account.is_connected: # 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 # do the puppeting @@ -496,7 +496,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): # See if authentication is currently being throttled 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 # waste of storage and can be abused to make your logs harder to @@ -508,8 +508,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if banned: # this is a banned IP or name! errors.append( - "|rYou have been banned and cannot continue from here." - "\nIf you feel this ban is in error, please email an admin.|x" + _("|rYou have been banned and cannot continue from here." + "\nIf you feel this ban is in error, please email an admin.|x") ) logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).") 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) if not account: # 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 logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).") @@ -688,7 +688,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): ip = kwargs.get("ip", "") if ip and CREATION_THROTTLE.check(ip): 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 @@ -717,8 +717,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if banned: # this is a banned IP or name! string = ( - "|rYou have been banned and cannot continue from here." - "\nIf you feel this ban is in error, please email an admin.|x" + _("|rYou have been banned and cannot continue from here." + "\nIf you feel this ban is in error, please email an admin.|x") ) errors.append(string) return None, errors @@ -733,7 +733,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): except Exception as e: 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() 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 # to handle tracebacks ourselves at this point. If we don't, # 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() # Update the throttle to indicate a new account was created from this IP @@ -1253,21 +1253,21 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if session: 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: # in this mode we should have only one character available. We # try to auto-connect to our last conneted object, if any try: self.puppet_object(session, self.db._last_puppet) except RuntimeError: - self.msg("The Character does not exist.") + self.msg(_("The Character does not exist.")) return elif _MULTISESSION_MODE == 1: # in this mode all sessions connect to the same puppet. try: self.puppet_object(session, self.db._last_puppet) except RuntimeError: - self.msg("The Character does not exist.") + self.msg(_("The Character does not exist.")) return elif _MULTISESSION_MODE in (2, 3): # 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 ''})" - 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): """ @@ -1411,7 +1411,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): if hasattr(target, "return_appearance"): return target.return_appearance(self) else: - return "{} has no in-game appearance.".format(target) + return _("{target} has no in-game appearance.").format(target=target) else: # list of targets - make list to disconnect from db 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 not characters: 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: result.append("\n |w@charcreate [=description]|n - create new character") @@ -1534,7 +1534,7 @@ class DefaultGuest(DefaultAccount): # check if guests are 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 try: @@ -1544,7 +1544,7 @@ class DefaultGuest(DefaultAccount): username = name break 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: LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.") return None, errors @@ -1572,7 +1572,7 @@ class DefaultGuest(DefaultAccount): # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # 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() return None, errors @@ -1589,7 +1589,7 @@ class DefaultGuest(DefaultAccount): 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) def at_server_shutdown(self): diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py index 455e10b629..6c6a40d0db 100644 --- a/evennia/accounts/bots.py +++ b/evennia/accounts/bots.py @@ -10,6 +10,7 @@ from evennia.accounts.accounts import DefaultAccount from evennia.scripts.scripts import DefaultScript from evennia.utils import search from evennia.utils import utils +from django.utils.translation import gettext as _ _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})" nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower())) 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 = [] return @@ -337,7 +338,7 @@ class IRCBot(Bot): if hasattr(self, "_ping_callers") and self._ping_callers: chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})" 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 = [] return diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 6f8f247a72..a0d9be8842 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -743,7 +743,7 @@ def cmdhandler( sysarg = raw_string else: # 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( raw_string, cmdset.get_all_cmd_keys_and_aliases(caller), @@ -751,9 +751,7 @@ def cmdhandler( maxnum=3, ) if suggestions: - sysarg += _(" Maybe you meant %s?") % utils.list_to_string( - suggestions, _("or"), addquote=True - ) + sysarg += _(" Maybe you meant {command}?").format(command=utils.list_to_string(suggestions, _("or"), addquote=True)) else: sysarg += _(' Type "help" for help.') raise ExecSystemCommand(syscmd, sysarg) diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 395c9c2ba3..4a746df33e 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -184,7 +184,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False): raise exc.with_traceback(tb) else: # try next suggested path - errstring += _("\n(Unsuccessfully tried '%s')." % python_path) + errstring += _("\n(Unsuccessfully tried '{path}').").format(path=python_path) continue try: 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() raise exc.with_traceback(tb) else: - errstring += _("\n(Unsuccessfully tried '%s')." % python_path) + errstring += _("\n(Unsuccessfully tried '{path}').").format(path=python_path) continue _CACHED_CMDSETS[python_path] = cmdsetclass diff --git a/evennia/comms/channelhandler.py b/evennia/comms/channelhandler.py index 98e28786a0..c99288c337 100644 --- a/evennia/comms/channelhandler.py +++ b/evennia/comms/channelhandler.py @@ -119,17 +119,17 @@ class ChannelCommand(command.Command): caller = caller if not hasattr(caller, "account") else caller.account unmuted = channel.unmute(caller) if unmuted: - self.msg("You start listening to %s." % channel) + self.msg(_("You start listening to %s.") % channel) return - self.msg("You were already listening to %s." % channel) + self.msg(_("You were already listening to %s.") % channel) return if msg == "off": caller = caller if not hasattr(caller, "account") else caller.account muted = channel.mute(caller) if muted: - self.msg("You stop listening to %s." % channel) + self.msg(_("You stop listening to %s.") % channel) return - self.msg("You were already not listening to %s." % channel) + self.msg(_("You were already not listening to %s.") % channel) return if self.history_start is not None: # Try to view history @@ -144,7 +144,7 @@ class ChannelCommand(command.Command): else: caller = caller if not hasattr(caller, "account") else caller.account if caller in channel.mutelist: - self.msg("You currently have %s muted." % channel) + self.msg(_("You currently have %s muted.") % channel) return channel.msg(msg, senders=self.caller, online=True) diff --git a/evennia/help/manager.py b/evennia/help/manager.py index 3459efe951..646758d202 100644 --- a/evennia/help/manager.py +++ b/evennia/help/manager.py @@ -131,7 +131,7 @@ class HelpEntryManager(TypedObjectManager): for topic in topics: topic.help_category = default_category 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) def search_help(self, ostring, help_category=None): diff --git a/evennia/locale/ru/LC_MESSAGES/django.mo b/evennia/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..8a3f6b3aba Binary files /dev/null and b/evennia/locale/ru/LC_MESSAGES/django.mo differ diff --git a/evennia/locale/ru/LC_MESSAGES/django.po b/evennia/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000000..e42efc8362 --- /dev/null +++ b/evennia/locale/ru/LC_MESSAGES/django.po @@ -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 " : {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: , help, quit" +msgstr "Команды: , справка, выход" + +#: utils/evmenu.py:197 +msgid "Commands: , help" +msgstr "Команды: , справка" + +#: 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" diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index ac8c85abc8..0f96b1d6a7 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -246,7 +246,7 @@ class LockHandler(object): evalstring = " ".join(_RE_OK.findall(evalstring)) eval(evalstring % tuple(True for func in funclist), {}, {}) 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 if access_type in locks: duplicates += 1 diff --git a/evennia/objects/admin.py b/evennia/objects/admin.py index 49bec928c3..5e18e8e0a4 100644 --- a/evennia/objects/admin.py +++ b/evennia/objects/admin.py @@ -8,7 +8,7 @@ from django.contrib import admin from evennia.typeclasses.admin import AttributeInline, TagInline from evennia.objects.models import ObjectDB from django.contrib.admin.utils import flatten_fieldsets - +from django.utils.translation import gettext as _ class ObjectAttributeInline(AttributeInline): """ @@ -61,7 +61,7 @@ class ObjectCreateForm(forms.ModelForm): required=False, widget=forms.TextInput(attrs={"size": "78"}), 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") diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index ff69863be8..8ee0d5bfa4 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1059,7 +1059,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # See if we need to kick the account off. 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. # sever the connection (important!) if self.account: diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 1fcf296058..73d9773583 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -465,11 +465,12 @@ def getKeyPair(pubkeyfile, privkeyfile): if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): # 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)) - public_key_string = rsa_key.public().toString(type="OPENSSH") - private_key_string = rsa_key.toString(type="OPENSSH") + 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").decode() + private_key_string = rsa_key.toString(type="OPENSSH").decode() # save keys for the future. with open(privkeyfile, "wt") as pfile: diff --git a/evennia/settings_default.py b/evennia/settings_default.py index e9e7e98bb2..67f6331e9c 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -4,7 +4,7 @@ Master configuration file for Evennia. NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE! All settings changes should be done by copy-pasting the variable and -its value to /conf/settings.py. +its value to /server/conf/settings.py. 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 diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index c886a67400..4b83160e4c 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -31,7 +31,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): # Attribute manager methods 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 @@ -55,6 +56,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): attrype (str, optional): An attribute-type to search for. By default this is either `None` (normal Attributes) or `"nick"`. + kwargs (any): Currently unused. Reserved for future use. Returns: attributes (list): The matching Attributes. @@ -102,7 +104,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): 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, 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. By default this is either `None` (normal Attributes) or `"nick"`. + kwargs (any): Currently unused. Reserved for future use. Returns: obj (list): Objects having the matching Attributes. @@ -488,12 +492,12 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): def get_typeclass_totals(self, *args, **kwargs) -> object: """ Returns a queryset of typeclass composition statistics. - + 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 percentage of objects associated with the typeclass. - + """ return ( self.values("db_typeclass_path") diff --git a/evennia/utils/search.py b/evennia/utils/search.py index 1a788300cf..45bdc86a87 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -205,27 +205,31 @@ help_entries = search_help # 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( - 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( - 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( - 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( - 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) -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. @@ -252,6 +256,11 @@ def search_object_by_tag(key=None, category=None): category (str, optional): The category of tag to search for. If not set, uncategorized 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: matches (list): List of Objects with tags matching @@ -259,13 +268,13 @@ def search_object_by_tag(key=None, category=None): 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 -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. @@ -274,6 +283,11 @@ def search_account_tag(key=None, category=None): category (str, optional): The category of tag to search for. If not set, uncategorized 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: matches (list): List of Accounts with tags matching @@ -281,10 +295,10 @@ def search_account_tag(key=None, category=None): 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. @@ -293,6 +307,11 @@ def search_script_tag(key=None, category=None): category (str, optional): The category of tag to search for. If not set, uncategorized 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: matches (list): List of Scripts with tags matching @@ -300,10 +319,10 @@ def search_script_tag(key=None, category=None): 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. @@ -312,6 +331,11 @@ def search_channel_tag(key=None, category=None): category (str, optional): The category of tag to search for. If not set, uncategorized 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: matches (list): List of Channels with tags matching @@ -319,7 +343,7 @@ def search_channel_tag(key=None, category=None): 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 diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 4faf76ca42..5b6aa0708b 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2041,7 +2041,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs): if multimatch_string: error = "%s\n" % multimatch_string 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): # we need to consider Commands, where .aliases is a list diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py index bca25874cb..e49bb716a8 100644 --- a/evennia/utils/validatorfuncs.py +++ b/evennia/utils/validatorfuncs.py @@ -15,6 +15,7 @@ from django.core.exceptions import ValidationError as _error from django.core.validators import validate_email as _val_email from evennia.utils.ansi import strip_ansi 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} @@ -58,7 +59,7 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs) """ 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: from_tz = _pytz.UTC if account: @@ -66,7 +67,7 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs) try: from_tz = _pytz.timezone(acct_tz) 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: from_tz = _pytz.UTC