Repository: aiogram/aiogram
Pull request #1302 (2023-09-14 15:50:30)

Corrected grammatical errors, improved sentence structures, translation for migration 2.x-3.x
Corrected grammatical errors, improved sentence structures, translation for migration 2.x-3.x

# Description

Corrected grammatical errors, improved sentence structures for clarity across various sections of the aiogram 3 library documentation.

## Type of change

Please delete options that are not relevant.

- [x] Documentation (typos, code examples or any documentation update)


# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


github-actions[bot]: # :x: Changelog is required!

You need to add a brief description of the changes to the `CHANGES` directory.

For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.

Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)

Patch:
diff --git a/.apiversion b/.apiversion
index 21afad37..12e41412 100644
--- a/.apiversion
+++ b/.apiversion
@@ -1,1 +1,1 @@
-6.8
+6.9
diff --git a/.butcher/methods/banChatMember/entity.json b/.butcher/methods/banChatMember/entity.json
index c6ee2e8e..9a5eda0d 100644
--- a/.butcher/methods/banChatMember/entity.json
+++ b/.butcher/methods/banChatMember/entity.json
@@ -30,9 +30,9 @@
       {
         "type": "Integer",
         "required": false,
-        "description": "Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.",
-        "html_description": "<td>Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.</td>",
-        "rst_description": "Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.\n",
+        "description": "Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.",
+        "html_description": "<td>Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.</td>",
+        "rst_description": "Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.\n",
         "name": "until_date"
       },
       {
diff --git a/.butcher/methods/restrictChatMember/entity.json b/.butcher/methods/restrictChatMember/entity.json
index f0ed7537..38d78844 100644
--- a/.butcher/methods/restrictChatMember/entity.json
+++ b/.butcher/methods/restrictChatMember/entity.json
@@ -46,9 +46,9 @@
       {
         "type": "Integer",
         "required": false,
-        "description": "Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever",
-        "html_description": "<td>Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever</td>",
-        "rst_description": "Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever\n",
+        "description": "Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever",
+        "html_description": "<td>Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever</td>",
+        "rst_description": "Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever\n",
         "name": "until_date"
       }
     ],
diff --git a/.butcher/schema/schema.json b/.butcher/schema/schema.json
index d4ec9ad1..7f4457cc 100644
--- a/.butcher/schema/schema.json
+++ b/.butcher/schema/schema.json
@@ -1,7 +1,7 @@
 {
   "api": {
-    "version": "6.8",
-    "release_date": "2023-08-18"
+    "version": "6.9",
+    "release_date": "2023-09-22"
   },
   "items": [
     {
@@ -549,9 +549,9 @@
             },
             {
               "type": "Integer",
-              "description": "Expiration date of the emoji status of the other party in a private chat, if any. Returned only in getChat.",
-              "html_description": "<td><em>Optional</em>. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in <a href=\"#getchat\">getChat</a>.</td>",
-              "rst_description": "*Optional*. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`.\n",
+              "description": "Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in getChat.",
+              "html_description": "<td><em>Optional</em>. Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in <a href=\"#getchat\">getChat</a>.</td>",
+              "rst_description": "*Optional*. Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`.\n",
               "name": "emoji_status_expiration_date",
               "required": false
             },
@@ -1167,9 +1167,9 @@
             },
             {
               "type": "WriteAccessAllowed",
-              "description": "Service message: the user allowed the bot added to the attachment menu to write messages",
-              "html_description": "<td><em>Optional</em>. Service message: the user allowed the bot added to the attachment menu to write messages</td>",
-              "rst_description": "*Optional*. Service message: the user allowed the bot added to the attachment menu to write messages\n",
+              "description": "Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method requestWriteAccess",
+              "html_description": "<td><em>Optional</em>. Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a></td>",
+              "rst_description": "*Optional*. Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
               "name": "write_access_allowed",
               "required": false
             },
@@ -2454,17 +2454,33 @@
         {
           "anchor": "writeaccessallowed",
           "name": "WriteAccessAllowed",
-          "description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
-          "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.</p>",
-          "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+          "description": "This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method requestWriteAccess.",
+          "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a>.</p>",
+          "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.",
           "annotations": [
+            {
+              "type": "Boolean",
+              "description": "True, if the access was granted after the user accepted an explicit request from a Web App sent by the method requestWriteAccess",
+              "html_description": "<td><em>Optional</em>. True, if the access was granted after the user accepted an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a></td>",
+              "rst_description": "*Optional*. True, if the access was granted after the user accepted an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
+              "name": "from_request",
+              "required": false
+            },
             {
               "type": "String",
-              "description": "Name of the Web App which was launched from a link",
-              "html_description": "<td><em>Optional</em>. Name of the Web App which was launched from a link</td>",
-              "rst_description": "*Optional*. Name of the Web App which was launched from a link\n",
+              "description": "Name of the Web App, if the access was granted when the Web App was launched from a link",
+              "html_description": "<td><em>Optional</em>. Name of the Web App, if the access was granted when the Web App was launched from a link</td>",
+              "rst_description": "*Optional*. Name of the Web App, if the access was granted when the Web App was launched from a link\n",
               "name": "web_app_name",
               "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the access was granted when the bot was added to the attachment or side menu",
+              "html_description": "<td><em>Optional</em>. True, if the access was granted when the bot was added to the attachment or side menu</td>",
+              "rst_description": "*Optional*. True, if the access was granted when the bot was added to the attachment or side menu\n",
+              "name": "from_attachment_menu",
+              "required": false
             }
           ],
           "category": "types"
@@ -2610,8 +2626,8 @@
             {
               "type": "String",
               "description": "An HTTPS URL of a Web App to be opened with additional data as specified in Initializing Web Apps",
-              "html_description": "<td>An HTTPS URL of a Web App to be opened with additional data as specified in <a href=\"/bots/webapps#initializing-web-apps\">Initializing Web Apps</a></td>",
-              "rst_description": "An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-web-apps>`_\n",
+              "html_description": "<td>An HTTPS URL of a Web App to be opened with additional data as specified in <a href=\"/bots/webapps#initializing-mini-apps\">Initializing Web Apps</a></td>",
+              "rst_description": "An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
               "name": "url",
               "required": true
             }
@@ -3335,9 +3351,9 @@
             },
             {
               "type": "Boolean",
-              "description": "True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
-              "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
-              "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
+              "description": "True, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
+              "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
+              "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
               "name": "can_manage_chat",
               "required": true
             },
@@ -3391,9 +3407,9 @@
             },
             {
               "type": "Boolean",
-              "description": "True, if the administrator can post in the channel; channels only",
-              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post in the channel; channels only</td>",
-              "rst_description": "*Optional*. :code:`True`, if the administrator can post in the channel; channels only\n",
+              "description": "True, if the administrator can post messages in the channel; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post messages in the channel; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only\n",
               "name": "can_post_messages",
               "required": false
             },
@@ -3413,6 +3429,30 @@
               "name": "can_pin_messages",
               "required": false
             },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can post stories in the channel; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post stories in the channel; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only\n",
+              "name": "can_post_stories",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can edit stories posted by other users; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit stories posted by other users; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only\n",
+              "name": "can_edit_stories",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can delete stories posted by other users",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can delete stories posted by other users</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can delete stories posted by other users\n",
+              "name": "can_delete_stories",
+              "required": false
+            },
             {
               "type": "Boolean",
               "description": "True, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only",
@@ -3516,9 +3556,9 @@
             },
             {
               "type": "Boolean",
-              "description": "True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
-              "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
-              "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
+              "description": "True, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
+              "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
+              "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
               "name": "can_manage_chat",
               "required": true
             },
@@ -3572,9 +3612,9 @@
             },
             {
               "type": "Boolean",
-              "description": "True, if the administrator can post in the channel; channels only",
-              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post in the channel; channels only</td>",
-              "rst_description": "*Optional*. :code:`True`, if the administrator can post in the channel; channels only\n",
+              "description": "True, if the administrator can post messages in the channel; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post messages in the channel; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only\n",
               "name": "can_post_messages",
               "required": false
             },
@@ -3594,6 +3634,30 @@
               "name": "can_pin_messages",
               "required": false
             },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can post stories in the channel; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post stories in the channel; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only\n",
+              "name": "can_post_stories",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can edit stories posted by other users; channels only",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit stories posted by other users; channels only</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only\n",
+              "name": "can_edit_stories",
+              "required": false
+            },
+            {
+              "type": "Boolean",
+              "description": "True, if the administrator can delete stories posted by other users",
+              "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can delete stories posted by other users</td>",
+              "rst_description": "*Optional*. :code:`True`, if the administrator can delete stories posted by other users\n",
+              "name": "can_delete_stories",
+              "required": false
+            },
             {
               "type": "Boolean",
               "description": "True, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only",
@@ -3784,9 +3848,9 @@
             },
             {
               "type": "Integer",
-              "description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever",
-              "html_description": "<td>Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever</td>",
-              "rst_description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever\n",
+              "description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever",
+              "html_description": "<td>Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever</td>",
+              "rst_description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever\n",
               "name": "until_date",
               "required": true
             }
@@ -3844,9 +3908,9 @@
             },
             {
               "type": "Integer",
-              "description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever",
-              "html_description": "<td>Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever</td>",
-              "rst_description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever\n",
+              "description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever",
+              "html_description": "<td>Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever</td>",
+              "rst_description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever\n",
               "name": "until_date",
               "required": true
             }
@@ -6821,9 +6885,9 @@
             {
               "type": "Integer",
               "required": false,
-              "description": "Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.",
-              "html_description": "<td>Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.</td>",
-              "rst_description": "Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.\n",
+              "description": "Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.",
+              "html_description": "<td>Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.</td>",
+              "rst_description": "Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.\n",
               "name": "until_date"
             },
             {
@@ -6913,9 +6977,9 @@
             {
               "type": "Integer",
               "required": false,
-              "description": "Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever",
-              "html_description": "<td>Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever</td>",
-              "rst_description": "Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever\n",
+              "description": "Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever",
+              "html_description": "<td>Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever</td>",
+              "rst_description": "Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever\n",
               "name": "until_date"
             }
           ],
@@ -9681,8 +9745,8 @@
             {
               "type": "WebAppInfo",
               "description": "Description of the Web App that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method switchInlineQuery inside the Web App.",
-              "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <a href=\"/bots/webapps#initializing-web-apps\">switchInlineQuery</a> inside the Web App.</td>",
-              "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_ inside the Web App.\n",
+              "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <a href=\"/bots/webapps#initializing-mini-apps\">switchInlineQuery</a> inside the Web App.</td>",
+              "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_ inside the Web App.\n",
               "name": "web_app",
               "required": false
             },
diff --git a/.butcher/types/Chat/entity.json b/.butcher/types/Chat/entity.json
index 4bc1fb60..0a65849d 100644
--- a/.butcher/types/Chat/entity.json
+++ b/.butcher/types/Chat/entity.json
@@ -93,9 +93,9 @@
       },
       {
         "type": "Integer",
-        "description": "Expiration date of the emoji status of the other party in a private chat, if any. Returned only in getChat.",
-        "html_description": "<td><em>Optional</em>. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in <a href=\"#getchat\">getChat</a>.</td>",
-        "rst_description": "*Optional*. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`.\n",
+        "description": "Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in getChat.",
+        "html_description": "<td><em>Optional</em>. Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in <a href=\"#getchat\">getChat</a>.</td>",
+        "rst_description": "*Optional*. Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`.\n",
         "name": "emoji_status_expiration_date",
         "required": false
       },
diff --git a/.butcher/types/ChatAdministratorRights/entity.json b/.butcher/types/ChatAdministratorRights/entity.json
index 6d5951e8..eb3b1c14 100644
--- a/.butcher/types/ChatAdministratorRights/entity.json
+++ b/.butcher/types/ChatAdministratorRights/entity.json
@@ -21,9 +21,9 @@
       },
       {
         "type": "Boolean",
-        "description": "True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
-        "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
-        "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
+        "description": "True, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
+        "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
+        "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
         "name": "can_manage_chat",
         "required": true
       },
@@ -77,9 +77,9 @@
       },
       {
         "type": "Boolean",
-        "description": "True, if the administrator can post in the channel; channels only",
-        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post in the channel; channels only</td>",
-        "rst_description": "*Optional*. :code:`True`, if the administrator can post in the channel; channels only\n",
+        "description": "True, if the administrator can post messages in the channel; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post messages in the channel; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only\n",
         "name": "can_post_messages",
         "required": false
       },
@@ -99,6 +99,30 @@
         "name": "can_pin_messages",
         "required": false
       },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can post stories in the channel; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post stories in the channel; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only\n",
+        "name": "can_post_stories",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can edit stories posted by other users; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit stories posted by other users; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only\n",
+        "name": "can_edit_stories",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can delete stories posted by other users",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can delete stories posted by other users</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can delete stories posted by other users\n",
+        "name": "can_delete_stories",
+        "required": false
+      },
       {
         "type": "Boolean",
         "description": "True, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only",
diff --git a/.butcher/types/ChatMemberAdministrator/entity.json b/.butcher/types/ChatMemberAdministrator/entity.json
index 577d9daf..65981631 100644
--- a/.butcher/types/ChatMemberAdministrator/entity.json
+++ b/.butcher/types/ChatMemberAdministrator/entity.json
@@ -45,9 +45,9 @@
       },
       {
         "type": "Boolean",
-        "description": "True, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
-        "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
-        "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
+        "description": "True, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege",
+        "html_description": "<td><em>True</em>, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege</td>",
+        "rst_description": ":code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege\n",
         "name": "can_manage_chat",
         "required": true
       },
@@ -101,9 +101,9 @@
       },
       {
         "type": "Boolean",
-        "description": "True, if the administrator can post in the channel; channels only",
-        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post in the channel; channels only</td>",
-        "rst_description": "*Optional*. :code:`True`, if the administrator can post in the channel; channels only\n",
+        "description": "True, if the administrator can post messages in the channel; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post messages in the channel; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only\n",
         "name": "can_post_messages",
         "required": false
       },
@@ -123,6 +123,30 @@
         "name": "can_pin_messages",
         "required": false
       },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can post stories in the channel; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can post stories in the channel; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only\n",
+        "name": "can_post_stories",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can edit stories posted by other users; channels only",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit stories posted by other users; channels only</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only\n",
+        "name": "can_edit_stories",
+        "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the administrator can delete stories posted by other users",
+        "html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can delete stories posted by other users</td>",
+        "rst_description": "*Optional*. :code:`True`, if the administrator can delete stories posted by other users\n",
+        "name": "can_delete_stories",
+        "required": false
+      },
       {
         "type": "Boolean",
         "description": "True, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only",
diff --git a/.butcher/types/ChatMemberBanned/entity.json b/.butcher/types/ChatMemberBanned/entity.json
index 49d42e20..4da411ec 100644
--- a/.butcher/types/ChatMemberBanned/entity.json
+++ b/.butcher/types/ChatMemberBanned/entity.json
@@ -29,9 +29,9 @@
       },
       {
         "type": "Integer",
-        "description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever",
-        "html_description": "<td>Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever</td>",
-        "rst_description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever\n",
+        "description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever",
+        "html_description": "<td>Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever</td>",
+        "rst_description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever\n",
         "name": "until_date",
         "required": true
       }
diff --git a/.butcher/types/ChatMemberRestricted/entity.json b/.butcher/types/ChatMemberRestricted/entity.json
index 958ad57a..1c46132d 100644
--- a/.butcher/types/ChatMemberRestricted/entity.json
+++ b/.butcher/types/ChatMemberRestricted/entity.json
@@ -149,9 +149,9 @@
       },
       {
         "type": "Integer",
-        "description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever",
-        "html_description": "<td>Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever</td>",
-        "rst_description": "Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever\n",
+        "description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever",
+        "html_description": "<td>Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever</td>",
+        "rst_description": "Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever\n",
         "name": "until_date",
         "required": true
       }
diff --git a/.butcher/types/InlineQueryResultsButton/entity.json b/.butcher/types/InlineQueryResultsButton/entity.json
index ce4cdf1e..a66c2f3b 100644
--- a/.butcher/types/InlineQueryResultsButton/entity.json
+++ b/.butcher/types/InlineQueryResultsButton/entity.json
@@ -22,8 +22,8 @@
       {
         "type": "WebAppInfo",
         "description": "Description of the Web App that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method switchInlineQuery inside the Web App.",
-        "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <a href=\"/bots/webapps#initializing-web-apps\">switchInlineQuery</a> inside the Web App.</td>",
-        "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_ inside the Web App.\n",
+        "html_description": "<td><em>Optional</em>. Description of the <a href=\"/bots/webapps\">Web App</a> that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method <a href=\"/bots/webapps#initializing-mini-apps\">switchInlineQuery</a> inside the Web App.</td>",
+        "rst_description": "*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_ inside the Web App.\n",
         "name": "web_app",
         "required": false
       },
diff --git a/.butcher/types/Message/entity.json b/.butcher/types/Message/entity.json
index ef639796..993d37eb 100644
--- a/.butcher/types/Message/entity.json
+++ b/.butcher/types/Message/entity.json
@@ -469,9 +469,9 @@
       },
       {
         "type": "WriteAccessAllowed",
-        "description": "Service message: the user allowed the bot added to the attachment menu to write messages",
-        "html_description": "<td><em>Optional</em>. Service message: the user allowed the bot added to the attachment menu to write messages</td>",
-        "rst_description": "*Optional*. Service message: the user allowed the bot added to the attachment menu to write messages\n",
+        "description": "Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method requestWriteAccess",
+        "html_description": "<td><em>Optional</em>. Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a></td>",
+        "rst_description": "*Optional*. Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
         "name": "write_access_allowed",
         "required": false
       },
diff --git a/.butcher/types/WebAppInfo/entity.json b/.butcher/types/WebAppInfo/entity.json
index a0ec3c45..aaeed289 100644
--- a/.butcher/types/WebAppInfo/entity.json
+++ b/.butcher/types/WebAppInfo/entity.json
@@ -14,8 +14,8 @@
       {
         "type": "String",
         "description": "An HTTPS URL of a Web App to be opened with additional data as specified in Initializing Web Apps",
-        "html_description": "<td>An HTTPS URL of a Web App to be opened with additional data as specified in <a href=\"/bots/webapps#initializing-web-apps\">Initializing Web Apps</a></td>",
-        "rst_description": "An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-web-apps>`_\n",
+        "html_description": "<td>An HTTPS URL of a Web App to be opened with additional data as specified in <a href=\"/bots/webapps#initializing-mini-apps\">Initializing Web Apps</a></td>",
+        "rst_description": "An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
         "name": "url",
         "required": true
       }
diff --git a/.butcher/types/WriteAccessAllowed/entity.json b/.butcher/types/WriteAccessAllowed/entity.json
index 62d83fdd..d054736a 100644
--- a/.butcher/types/WriteAccessAllowed/entity.json
+++ b/.butcher/types/WriteAccessAllowed/entity.json
@@ -7,17 +7,33 @@
   "object": {
     "anchor": "writeaccessallowed",
     "name": "WriteAccessAllowed",
-    "description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
-    "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.</p>",
-    "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.",
+    "description": "This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method requestWriteAccess.",
+    "html_description": "<p>This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a>.</p>",
+    "rst_description": "This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.",
     "annotations": [
+      {
+        "type": "Boolean",
+        "description": "True, if the access was granted after the user accepted an explicit request from a Web App sent by the method requestWriteAccess",
+        "html_description": "<td><em>Optional</em>. True, if the access was granted after the user accepted an explicit request from a Web App sent by the method <a href=\"/bots/webapps#initializing-mini-apps\">requestWriteAccess</a></td>",
+        "rst_description": "*Optional*. True, if the access was granted after the user accepted an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_\n",
+        "name": "from_request",
+        "required": false
+      },
       {
         "type": "String",
-        "description": "Name of the Web App which was launched from a link",
-        "html_description": "<td><em>Optional</em>. Name of the Web App which was launched from a link</td>",
-        "rst_description": "*Optional*. Name of the Web App which was launched from a link\n",
+        "description": "Name of the Web App, if the access was granted when the Web App was launched from a link",
+        "html_description": "<td><em>Optional</em>. Name of the Web App, if the access was granted when the Web App was launched from a link</td>",
+        "rst_description": "*Optional*. Name of the Web App, if the access was granted when the Web App was launched from a link\n",
         "name": "web_app_name",
         "required": false
+      },
+      {
+        "type": "Boolean",
+        "description": "True, if the access was granted when the bot was added to the attachment or side menu",
+        "html_description": "<td><em>Optional</em>. True, if the access was granted when the bot was added to the attachment or side menu</td>",
+        "rst_description": "*Optional*. True, if the access was granted when the bot was added to the attachment or side menu\n",
+        "name": "from_attachment_menu",
+        "required": false
       }
     ],
     "category": "types"
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index ade26bd1..9a4d9351 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -30,6 +30,5 @@ Please describe the tests that you ran to verify your changes. Provide instructi
 - [ ] My code follows the style guidelines of this project
 - [ ] I have performed a self-review of my own code
 - [ ] I have made corresponding changes to the documentation
-- [ ] My changes generate no new warnings
 - [ ] I have added tests that prove my fix is effective or that my feature works
 - [ ] New and existing unit tests pass locally with my changes
diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml
index f803153e..7f9d7b86 100644
--- a/.github/workflows/pull_request_changelog.yml
+++ b/.github/workflows/pull_request_changelog.yml
@@ -57,7 +57,7 @@ jobs:
 
             For example, you can run `towncrier create <issue>.<type>` to create a file in the change directory and then write a description on that file.
 
-            Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments)
+            Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/tutorial.html#creating-news-fragments)
 
       - name: Changelog found
         if: "success()"
diff --git a/CHANGES.rst b/CHANGES.rst
index 245c4f49..359b8f26 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,39 @@ Changelog
 
 .. towncrier release notes start
 
+3.1.1 (2023-09-25)
+===================
+
+Bugfixes
+--------
+
+- Fixed `pydantic` version <2.4, since 2.4 has breaking changes.
+  `#1322 <https://github.com/aiogram/aiogram/issues/1322>`_
+
+
+3.1.0 (2023-09-22)
+===================
+
+Features
+--------
+
+- Added support for custom encoders/decoders for payload (and also for deep-linking).
+  `#1262 <https://github.com/aiogram/aiogram/issues/1262>`_
+- Added :class:`aiogram.utils.input_media.MediaGroupBuilder` for media group construction.
+  `#1293 <https://github.com/aiogram/aiogram/issues/1293>`_
+- Added full support of `Bot API 6.9 <https://core.telegram.org/bots/api-changelog#september-22-2023>`_
+  `#1319 <https://github.com/aiogram/aiogram/issues/1319>`_
+
+
+Bugfixes
+--------
+
+- Added actual param hints for `InlineKeyboardBuilder` and `ReplyKeyboardBuilder`.
+  `#1303 <https://github.com/aiogram/aiogram/issues/1303>`_
+- Fixed priority of events isolation, now user state will be loaded only after lock is acquired
+  `#1317 <https://github.com/aiogram/aiogram/issues/1317>`_
+
+
 3.0.0 (2023-09-01)
 ===================
 
diff --git a/CHANGES/1262.feature b/CHANGES/1262.feature
deleted file mode 100644
index 822c82ae..00000000
--- a/CHANGES/1262.feature
+++ /dev/null
@@ -1,1 +0,0 @@
-Added support for custom encoders/decoders for payload (and also for deep-linking).
diff --git a/CHANGES/1293.feature.rst b/CHANGES/1293.feature.rst
deleted file mode 100644
index f561be61..00000000
--- a/CHANGES/1293.feature.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Added :class:`aiogram.utils.input_media.MediaGroupBuilder` for media group construction.
diff --git a/CHANGES/1302.doc b/CHANGES/1302.doc
new file mode 100644
index 00000000..80cc5492
--- /dev/null
+++ b/CHANGES/1302.doc
@@ -0,0 +1,1 @@
+Corrected grammatical errors, improved sentence structures, translation for migration 2.x-3.x
\ No newline at end of file
diff --git a/README.rst b/README.rst
index af494848..1a2f360a 100644
--- a/README.rst
+++ b/README.rst
@@ -52,7 +52,7 @@ Features
 - Asynchronous (`asyncio docs <https://docs.python.org/3/library/asyncio.html>`_, :pep:`492`)
 - Has type hints (:pep:`484`) and can be used with `mypy <http://mypy-lang.org/>`_
 - Supports `PyPy <https://www.pypy.org/>`_
-- Supports `Telegram Bot API 6.8 <https://core.telegram.org/bots/api>`_ and gets fast updates to the latest versions of the Bot API
+- Supports `Telegram Bot API 6.9 <https://core.telegram.org/bots/api>`_ and gets fast updates to the latest versions of the Bot API
 - Telegram Bot API integration code was `autogenerated <https://github.com/aiogram/tg-codegen>`_ and can be easily re-generated when API gets updated
 - Updates router (Blueprints)
 - Has Finite State Machine
diff --git a/aiogram/__meta__.py b/aiogram/__meta__.py
index bee8e1f3..b591b842 100644
--- a/aiogram/__meta__.py
+++ b/aiogram/__meta__.py
@@ -1,2 +1,2 @@
-__version__ = "3.0.1"
-__api_version__ = "6.8"
+__version__ = "3.1.1"
+__api_version__ = "6.9"
diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py
index 1bf48356..3a360c25 100644
--- a/aiogram/client/bot.py
+++ b/aiogram/client/bot.py
@@ -713,7 +713,7 @@ class Bot:
 
         :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`)
         :param user_id: Unique identifier of the target user
-        :param until_date: Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.
+        :param until_date: Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.
         :param revoke_messages: Pass :code:`True` to delete all messages from the chat for the user that is being removed. If :code:`False`, the user will be able to see messages in the group that were sent before the user was removed. Always :code:`True` for supergroups and channels.
         :param request_timeout: Request timeout
         :return: Returns :code:`True` on success.
@@ -2066,7 +2066,7 @@ class Bot:
         :param user_id: Unique identifier of the target user
         :param permissions: A JSON-serialized object for new user permissions
         :param use_independent_chat_permissions: Pass :code:`True` if chat permissions are set independently. Otherwise, the *can_send_other_messages* and *can_add_web_page_previews* permissions will imply the *can_send_messages*, *can_send_audios*, *can_send_documents*, *can_send_photos*, *can_send_videos*, *can_send_video_notes*, and *can_send_voice_notes* permissions; the *can_send_polls* permission will imply the *can_send_messages* permission.
-        :param until_date: Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever
+        :param until_date: Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever
         :param request_timeout: Request timeout
         :return: Returns :code:`True` on success.
         """
diff --git a/aiogram/client/session/aiohttp.py b/aiogram/client/session/aiohttp.py
index 19a8edbd..b92e32d2 100644
--- a/aiogram/client/session/aiohttp.py
+++ b/aiogram/client/session/aiohttp.py
@@ -139,6 +139,10 @@ class AiohttpSession(BaseSession):
         if self._session is not None and not self._session.closed:
             await self._session.close()
 
+            # Wait 250 ms for the underlying SSL connections to close
+            # https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown
+            await asyncio.sleep(0.25)
+
     def build_form_data(self, bot: Bot, method: TelegramMethod[TelegramType]) -> FormData:
         form = FormData(quote_fields=False)
         files: Dict[str, InputFile] = {}
diff --git a/aiogram/fsm/middleware.py b/aiogram/fsm/middleware.py
index 6de91a83..d1f1d973 100644
--- a/aiogram/fsm/middleware.py
+++ b/aiogram/fsm/middleware.py
@@ -34,8 +34,10 @@ class FSMContextMiddleware(BaseMiddleware):
         context = self.resolve_event_context(bot, data)
         data["fsm_storage"] = self.storage
         if context:
-            data.update({"state": context, "raw_state": await context.get_state()})
+            # Bugfix: https://github.com/aiogram/aiogram/issues/1317
+            # State should be loaded after lock is acquired
             async with self.events_isolation.lock(key=context.key):
+                data.update({"state": context, "raw_state": await context.get_state()})
                 return await handler(event, data)
         return await handler(event, data)
 
diff --git a/aiogram/fsm/storage/redis.py b/aiogram/fsm/storage/redis.py
index 6a55d881..37352a1d 100644
--- a/aiogram/fsm/storage/redis.py
+++ b/aiogram/fsm/storage/redis.py
@@ -138,7 +138,7 @@ class RedisStorage(BaseStorage):
         return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs)
 
     async def close(self) -> None:
-        await self.redis.close()
+        await self.redis.close(close_connection_pool=True)
 
     async def set_state(
         self,
diff --git a/aiogram/methods/ban_chat_member.py b/aiogram/methods/ban_chat_member.py
index bbfc9f75..947fccae 100644
--- a/aiogram/methods/ban_chat_member.py
+++ b/aiogram/methods/ban_chat_member.py
@@ -21,7 +21,7 @@ class BanChatMember(TelegramMethod[bool]):
     user_id: int
     """Unique identifier of the target user"""
     until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
-    """Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only."""
+    """Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only."""
     revoke_messages: Optional[bool] = None
     """Pass :code:`True` to delete all messages from the chat for the user that is being removed. If :code:`False`, the user will be able to see messages in the group that were sent before the user was removed. Always :code:`True` for supergroups and channels."""
 
diff --git a/aiogram/methods/restrict_chat_member.py b/aiogram/methods/restrict_chat_member.py
index 331e95f7..5dfbd01d 100644
--- a/aiogram/methods/restrict_chat_member.py
+++ b/aiogram/methods/restrict_chat_member.py
@@ -26,7 +26,7 @@ class RestrictChatMember(TelegramMethod[bool]):
     use_independent_chat_permissions: Optional[bool] = None
     """Pass :code:`True` if chat permissions are set independently. Otherwise, the *can_send_other_messages* and *can_add_web_page_previews* permissions will imply the *can_send_messages*, *can_send_audios*, *can_send_documents*, *can_send_photos*, *can_send_videos*, *can_send_video_notes*, and *can_send_voice_notes* permissions; the *can_send_polls* permission will imply the *can_send_messages* permission."""
     until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
-    """Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever"""
+    """Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever"""
 
     if TYPE_CHECKING:
         # DO NOT EDIT MANUALLY!!!
diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py
index 921ed3e7..b5bcd5d4 100644
--- a/aiogram/types/chat.py
+++ b/aiogram/types/chat.py
@@ -72,7 +72,7 @@ class Chat(TelegramObject):
     emoji_status_custom_emoji_id: Optional[str] = None
     """*Optional*. Custom emoji identifier of emoji status of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
     emoji_status_expiration_date: Optional[DateTime] = None
-    """*Optional*. Expiration date of the emoji status of the other party in a private chat, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
+    """*Optional*. Expiration date of the emoji status of the other party in a private chat in Unix time, if any. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
     bio: Optional[str] = None
     """*Optional*. Bio of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`."""
     has_private_forwards: Optional[bool] = None
@@ -892,7 +892,7 @@ class Chat(TelegramObject):
         :param user_id: Unique identifier of the target user
         :param permissions: A JSON-serialized object for new user permissions
         :param use_independent_chat_permissions: Pass :code:`True` if chat permissions are set independently. Otherwise, the *can_send_other_messages* and *can_add_web_page_previews* permissions will imply the *can_send_messages*, *can_send_audios*, *can_send_documents*, *can_send_photos*, *can_send_videos*, *can_send_video_notes*, and *can_send_voice_notes* permissions; the *can_send_polls* permission will imply the *can_send_messages* permission.
-        :param until_date: Date when restrictions will be lifted for the user, unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever
+        :param until_date: Date when restrictions will be lifted for the user; Unix time. If user is restricted for more than 366 days or less than 30 seconds from the current time, they are considered to be restricted forever
         :return: instance of method :class:`aiogram.methods.restrict_chat_member.RestrictChatMember`
         """
         # DO NOT EDIT MANUALLY!!!
@@ -959,7 +959,7 @@ class Chat(TelegramObject):
         Source: https://core.telegram.org/bots/api#banchatmember
 
         :param user_id: Unique identifier of the target user
-        :param until_date: Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.
+        :param until_date: Date when the user will be unbanned; Unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Applied for supergroups and channels only.
         :param revoke_messages: Pass :code:`True` to delete all messages from the chat for the user that is being removed. If :code:`False`, the user will be able to see messages in the group that were sent before the user was removed. Always :code:`True` for supergroups and channels.
         :return: instance of method :class:`aiogram.methods.ban_chat_member.BanChatMember`
         """
diff --git a/aiogram/types/chat_administrator_rights.py b/aiogram/types/chat_administrator_rights.py
index 1da998d1..da5179b7 100644
--- a/aiogram/types/chat_administrator_rights.py
+++ b/aiogram/types/chat_administrator_rights.py
@@ -18,7 +18,7 @@ class ChatAdministratorRights(TelegramObject):
     is_anonymous: bool
     """:code:`True`, if the user's presence in the chat is hidden"""
     can_manage_chat: bool
-    """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
+    """:code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
     can_delete_messages: bool
     """:code:`True`, if the administrator can delete messages of other users"""
     can_manage_video_chats: bool
@@ -32,11 +32,17 @@ class ChatAdministratorRights(TelegramObject):
     can_invite_users: bool
     """:code:`True`, if the user is allowed to invite new users to the chat"""
     can_post_messages: Optional[bool] = None
-    """*Optional*. :code:`True`, if the administrator can post in the channel; channels only"""
+    """*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only"""
     can_edit_messages: Optional[bool] = None
     """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only"""
     can_pin_messages: Optional[bool] = None
     """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only"""
+    can_post_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only"""
+    can_edit_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only"""
+    can_delete_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can delete stories posted by other users"""
     can_manage_topics: Optional[bool] = None
     """*Optional*. :code:`True`, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only"""
 
@@ -58,6 +64,9 @@ class ChatAdministratorRights(TelegramObject):
             can_post_messages: Optional[bool] = None,
             can_edit_messages: Optional[bool] = None,
             can_pin_messages: Optional[bool] = None,
+            can_post_stories: Optional[bool] = None,
+            can_edit_stories: Optional[bool] = None,
+            can_delete_stories: Optional[bool] = None,
             can_manage_topics: Optional[bool] = None,
             **__pydantic_kwargs: Any,
         ) -> None:
@@ -77,6 +86,9 @@ class ChatAdministratorRights(TelegramObject):
                 can_post_messages=can_post_messages,
                 can_edit_messages=can_edit_messages,
                 can_pin_messages=can_pin_messages,
+                can_post_stories=can_post_stories,
+                can_edit_stories=can_edit_stories,
+                can_delete_stories=can_delete_stories,
                 can_manage_topics=can_manage_topics,
                 **__pydantic_kwargs,
             )
diff --git a/aiogram/types/chat_member_administrator.py b/aiogram/types/chat_member_administrator.py
index 02a3be39..0db04dce 100644
--- a/aiogram/types/chat_member_administrator.py
+++ b/aiogram/types/chat_member_administrator.py
@@ -25,7 +25,7 @@ class ChatMemberAdministrator(ChatMember):
     is_anonymous: bool
     """:code:`True`, if the user's presence in the chat is hidden"""
     can_manage_chat: bool
-    """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
+    """:code:`True`, if the administrator can access the chat event log, chat statistics, boost list in channels, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
     can_delete_messages: bool
     """:code:`True`, if the administrator can delete messages of other users"""
     can_manage_video_chats: bool
@@ -39,11 +39,17 @@ class ChatMemberAdministrator(ChatMember):
     can_invite_users: bool
     """:code:`True`, if the user is allowed to invite new users to the chat"""
     can_post_messages: Optional[bool] = None
-    """*Optional*. :code:`True`, if the administrator can post in the channel; channels only"""
+    """*Optional*. :code:`True`, if the administrator can post messages in the channel; channels only"""
     can_edit_messages: Optional[bool] = None
     """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only"""
     can_pin_messages: Optional[bool] = None
     """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only"""
+    can_post_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can post stories in the channel; channels only"""
+    can_edit_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can edit stories posted by other users; channels only"""
+    can_delete_stories: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can delete stories posted by other users"""
     can_manage_topics: Optional[bool] = None
     """*Optional*. :code:`True`, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only"""
     custom_title: Optional[str] = None
@@ -70,6 +76,9 @@ class ChatMemberAdministrator(ChatMember):
             can_post_messages: Optional[bool] = None,
             can_edit_messages: Optional[bool] = None,
             can_pin_messages: Optional[bool] = None,
+            can_post_stories: Optional[bool] = None,
+            can_edit_stories: Optional[bool] = None,
+            can_delete_stories: Optional[bool] = None,
             can_manage_topics: Optional[bool] = None,
             custom_title: Optional[str] = None,
             **__pydantic_kwargs: Any,
@@ -93,6 +102,9 @@ class ChatMemberAdministrator(ChatMember):
                 can_post_messages=can_post_messages,
                 can_edit_messages=can_edit_messages,
                 can_pin_messages=can_pin_messages,
+                can_post_stories=can_post_stories,
+                can_edit_stories=can_edit_stories,
+                can_delete_stories=can_delete_stories,
                 can_manage_topics=can_manage_topics,
                 custom_title=custom_title,
                 **__pydantic_kwargs,
diff --git a/aiogram/types/chat_member_banned.py b/aiogram/types/chat_member_banned.py
index 88cfc75e..65776c52 100644
--- a/aiogram/types/chat_member_banned.py
+++ b/aiogram/types/chat_member_banned.py
@@ -22,7 +22,7 @@ class ChatMemberBanned(ChatMember):
     user: User
     """Information about the user"""
     until_date: DateTime
-    """Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever"""
+    """Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever"""
 
     if TYPE_CHECKING:
         # DO NOT EDIT MANUALLY!!!
diff --git a/aiogram/types/chat_member_restricted.py b/aiogram/types/chat_member_restricted.py
index 32d4a0dc..b27d72a2 100644
--- a/aiogram/types/chat_member_restricted.py
+++ b/aiogram/types/chat_member_restricted.py
@@ -52,7 +52,7 @@ class ChatMemberRestricted(ChatMember):
     can_manage_topics: bool
     """:code:`True`, if the user is allowed to create forum topics"""
     until_date: DateTime
-    """Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever"""
+    """Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever"""
 
     if TYPE_CHECKING:
         # DO NOT EDIT MANUALLY!!!
diff --git a/aiogram/types/inline_query_results_button.py b/aiogram/types/inline_query_results_button.py
index c93172bb..6943fb89 100644
--- a/aiogram/types/inline_query_results_button.py
+++ b/aiogram/types/inline_query_results_button.py
@@ -18,7 +18,7 @@ class InlineQueryResultsButton(TelegramObject):
     text: str
     """Label text on the button"""
     web_app: Optional[WebAppInfo] = None
-    """*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_ inside the Web App."""
+    """*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method `switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_ inside the Web App."""
     start_parameter: Optional[str] = None
     """*Optional*. `Deep-linking <https://core.telegram.org/bots/features#deep-linking>`_ parameter for the /start message sent to the bot when a user presses the button. 1-64 characters, only :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed."""
 
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index 5ad60d5b..da3e1f98 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -223,7 +223,7 @@ class Message(TelegramObject):
     connected_website: Optional[str] = None
     """*Optional*. The domain name of the website on which the user has logged in. `More about Telegram Login » <https://core.telegram.org/widgets/login>`_"""
     write_access_allowed: Optional[WriteAccessAllowed] = None
-    """*Optional*. Service message: the user allowed the bot added to the attachment menu to write messages"""
+    """*Optional*. Service message: the user allowed the bot to write messages after adding it to the attachment or side menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_"""
     passport_data: Optional[PassportData] = None
     """*Optional*. Telegram Passport data"""
     proximity_alert_triggered: Optional[ProximityAlertTriggered] = None
diff --git a/aiogram/types/web_app_info.py b/aiogram/types/web_app_info.py
index 01d00d3d..fbf9846e 100644
--- a/aiogram/types/web_app_info.py
+++ b/aiogram/types/web_app_info.py
@@ -13,7 +13,7 @@ class WebAppInfo(TelegramObject):
     """
 
     url: str
-    """An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-web-apps>`_"""
+    """An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_"""
 
     if TYPE_CHECKING:
         # DO NOT EDIT MANUALLY!!!
diff --git a/aiogram/types/write_access_allowed.py b/aiogram/types/write_access_allowed.py
index eb8b1d14..6b8e50a0 100644
--- a/aiogram/types/write_access_allowed.py
+++ b/aiogram/types/write_access_allowed.py
@@ -5,23 +5,37 @@ from aiogram.types import TelegramObject
 
 class WriteAccessAllowed(TelegramObject):
     """
-    This object represents a service message about a user allowing a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link.
+    This object represents a service message about a user allowing a bot to write messages after adding it to the attachment menu, launching a Web App from a link, or accepting an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
 
     Source: https://core.telegram.org/bots/api#writeaccessallowed
     """
 
+    from_request: Optional[bool] = None
+    """*Optional*. True, if the access was granted after the user accepted an explicit request from a Web App sent by the method `requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_"""
     web_app_name: Optional[str] = None
-    """*Optional*. Name of the Web App which was launched from a link"""
+    """*Optional*. Name of the Web App, if the access was granted when the Web App was launched from a link"""
+    from_attachment_menu: Optional[bool] = None
+    """*Optional*. True, if the access was granted when the bot was added to the attachment or side menu"""
 
     if TYPE_CHECKING:
         # DO NOT EDIT MANUALLY!!!
         # This section was auto-generated via `butcher`
 
         def __init__(
-            __pydantic__self__, *, web_app_name: Optional[str] = None, **__pydantic_kwargs: Any
+            __pydantic__self__,
+            *,
+            from_request: Optional[bool] = None,
+            web_app_name: Optional[str] = None,
+            from_attachment_menu: Optional[bool] = None,
+            **__pydantic_kwargs: Any,
         ) -> None:
             # DO NOT EDIT MANUALLY!!!
             # This method was auto-generated via `butcher`
             # Is needed only for type checking and IDE support without any additional plugins
 
-            super().__init__(web_app_name=web_app_name, **__pydantic_kwargs)
+            super().__init__(
+                from_request=from_request,
+                web_app_name=web_app_name,
+                from_attachment_menu=from_attachment_menu,
+                **__pydantic_kwargs,
+            )
diff --git a/aiogram/utils/deep_linking.py b/aiogram/utils/deep_linking.py
index 7f9b3583..bfd62a16 100644
--- a/aiogram/utils/deep_linking.py
+++ b/aiogram/utils/deep_linking.py
@@ -56,10 +56,10 @@ __all__ = [
 ]
 
 import re
-from typing import Callable, Literal, Optional, TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, Callable, Literal, Optional, cast
 
 from aiogram.utils.link import create_telegram_link
-from aiogram.utils.payload import encode_payload, decode_payload
+from aiogram.utils.payload import decode_payload, encode_payload
 
 if TYPE_CHECKING:
     from aiogram import Bot
diff --git a/aiogram/utils/keyboard.py b/aiogram/utils/keyboard.py
index 429cbe46..932f5865 100644
--- a/aiogram/utils/keyboard.py
+++ b/aiogram/utils/keyboard.py
@@ -28,6 +28,8 @@ from aiogram.types import (
     KeyboardButtonPollType,
     LoginUrl,
     ReplyKeyboardMarkup,
+    SwitchInlineQueryChosenChat,
+    WebAppInfo,
 )
 
 ButtonType = TypeVar("ButtonType", InlineKeyboardButton, KeyboardButton)
@@ -299,10 +301,12 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
             *,
             text: str,
             url: Optional[str] = None,
-            login_url: Optional[LoginUrl] = None,
             callback_data: Optional[Union[str, CallbackData]] = None,
+            web_app: Optional[WebAppInfo] = None,
+            login_url: Optional[LoginUrl] = None,
             switch_inline_query: Optional[str] = None,
             switch_inline_query_current_chat: Optional[str] = None,
+            switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat] = None,
             callback_game: Optional[CallbackGame] = None,
             pay: Optional[bool] = None,
             **kwargs: Any,
@@ -349,9 +353,12 @@ class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]):
             self,
             *,
             text: str,
+            request_user: Optional[bool] = None,
+            request_chat: Optional[bool] = None,
             request_contact: Optional[bool] = None,
             request_location: Optional[bool] = None,
             request_poll: Optional[KeyboardButtonPollType] = None,
+            web_app: Optional[WebAppInfo] = None,
             **kwargs: Any,
         ) -> "KeyboardBuilder[KeyboardButton]":
             ...
diff --git a/docs/dispatcher/class_based_handlers/message.rst b/docs/dispatcher/class_based_handlers/message.rst
index 1945e8ba..27629901 100644
--- a/docs/dispatcher/class_based_handlers/message.rst
+++ b/docs/dispatcher/class_based_handlers/message.rst
@@ -21,7 +21,7 @@ Simple usage
 Extension
 =========
 
-This base handler is subclass of [BaseHandler](basics.md#basehandler) with some extensions:
+This base handler is subclass of :ref:`BaseHandler <cbh-base-handler>` with some extensions:
 
 - :code:`self.chat` is alias for :code:`self.event.chat`
 - :code:`self.from_user` is alias for :code:`self.event.from_user`
diff --git a/docs/locale/uk_UA/LC_MESSAGES/api/methods/set_sticker_set_thumb.po b/docs/locale/uk_UA/LC_MESSAGES/api/methods/set_sticker_set_thumb.po
index 8e279db0..786b6309 100644
--- a/docs/locale/uk_UA/LC_MESSAGES/api/methods/set_sticker_set_thumb.po
+++ b/docs/locale/uk_UA/LC_MESSAGES/api/methods/set_sticker_set_thumb.po
@@ -3,19 +3,20 @@
 # This file is distributed under the same license as the aiogram package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2022.
 #
-#, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: aiogram \n"
+"Project-Id-Version: aiogram\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-03-11 01:52+0200\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
+"POT-Creation-Date: 2023-06-01 20:49+0300\n"
+"PO-Revision-Date: 2023-09-14 17:37+0300\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: uk\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.11.0\n"
+"Generated-By: Babel 2.12.1\n"
+"X-Generator: Poedit 3.3.2\n"
 
 #: ../../api/methods/set_sticker_set_thumb.rst:3
 msgid "setStickerSetThumb"
@@ -23,15 +24,38 @@ msgstr ""
 
 #: ../../api/methods/set_sticker_set_thumb.rst:5
 msgid "Returns: :obj:`bool`"
-msgstr ""
+msgstr "Повертає: :obj:`bool`"
+
+#: aiogram.methods.set_sticker_set_thumb.SetStickerSetThumbnail:1 of
+msgid "Use this method to set the thumbnail of a regular or mask sticker set. The format of the thumbnail file must match the format of the stickers in the set. Returns :code:`True` on success."
+msgstr "Використовуйте цей метод, щоб встановити мініатюру звичайного або маскового набору стікерів. Формат файлу мініатюри повинен відповідати формату стікерів у наборі. Повертає :code:`True` при успіху."
+
+#: aiogram.methods.set_sticker_set_thumb.SetStickerSetThumbnail:3 of
+msgid "Source: https://core.telegram.org/bots/api#setstickersetthumbnail"
+msgstr "Джерело: https://core.telegram.org/bots/api#setstickersetthumbnail"
+
+#: ../../docstring
+#: aiogram.methods.set_sticker_set_thumb.SetStickerSetThumbnail.name:1 of
+msgid "Sticker set name"
+msgstr "Назва набору стікерів"
+
+#: ../../docstring
+#: aiogram.methods.set_sticker_set_thumb.SetStickerSetThumbnail.user_id:1 of
+msgid "User identifier of the sticker set owner"
+msgstr "Ідентифікатор користувача власника набору стікерів "
+
+#: ../../docstring
+#: aiogram.methods.set_sticker_set_thumb.SetStickerSetThumbnail.thumbnail:1 of
+msgid "A **.WEBP** or **.PNG** image with the thumbnail, must be up to 128 kilobytes in size and have a width and height of exactly 100px, or a **.TGS** animation with a thumbnail up to 32 kilobytes in size (see `https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_`https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_ for animated sticker technical requirements), or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_`https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files » <sending-files>`. Animated and video sticker set thumbnails can't be uploaded via HTTP URL. If omitted, then the thumbnail is dropped and the first sticker is used as the thumbnail."
+msgstr "Зображення у форматі **.WEBP** або **.PNG** з мініатюрою має бути розміром до 128 кілобайт і мати ширину та висоту рівно 100 пікселів, або анімацією у форматі **.TGS** з мініатюрою розміром до 32 кілобайт (див. `https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram.org/stickers#animated-sticker-requirements>`_`https://core.telegram.org/stickers#animated-sticker-requirements <https://core.telegram. org/stickers#animated-sticker-requirements>`_ технічні вимоги до анімованих наліпок), або **WEBM** відео з мініатюрою розміром до 32 кілобайт; дивіться `https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_`https://core.telegram.org/stickers#video-sticker-requirements <https://core.telegram.org/stickers#video-sticker-requirements>`_ технічні вимоги до відео наліпок. Передайте *file_id* як рядок, щоб надіслати файл, який вже існує на серверах Telegram, передайте HTTP URL як рядок, щоб Telegram отримав файл з Інтернету, або завантажте новий файл за допомогою мультичастини/форма-даних. :ref:`Додаткова інформація про надсилання файлів \" <sending-files>`. Ескізи анімованих і відео-наборів наклейок не можуть бути завантажені через HTTP URL. Якщо цей параметр не вказано, то мініатюру буде вилучено, а замість неї буде використано першу наліпку."
 
 #: ../../api/methods/set_sticker_set_thumb.rst:14
 msgid "Usage"
-msgstr ""
+msgstr "Використання"
 
 #: ../../api/methods/set_sticker_set_thumb.rst:17
 msgid "As bot method"
-msgstr ""
+msgstr "Як метод в bot"
 
 #: ../../api/methods/set_sticker_set_thumb.rst:25
 msgid "Method as object"
@@ -39,12 +63,10 @@ msgstr ""
 
 #: ../../api/methods/set_sticker_set_thumb.rst:27
 msgid "Imports:"
-msgstr ""
+msgstr "Імпорти:"
 
 #: ../../api/methods/set_sticker_set_thumb.rst:29
-msgid ""
-":code:`from aiogram.methods.set_sticker_set_thumb import "
-"SetStickerSetThumb`"
+msgid ":code:`from aiogram.methods.set_sticker_set_thumb import SetStickerSetThumb`"
 msgstr ""
 
 #: ../../api/methods/set_sticker_set_thumb.rst:30
@@ -53,11 +75,11 @@ msgstr ""
 
 #: ../../api/methods/set_sticker_set_thumb.rst:33
 msgid "With specific bot"
-msgstr ""
+msgstr "З конкретним bot"
 
 #: ../../api/methods/set_sticker_set_thumb.rst:40
 msgid "As reply into Webhook in handler"
-msgstr ""
+msgstr "Як відповідь у Webhook в обробнику"
 
 #~ msgid ""
 #~ "Use this method to set the "
diff --git a/docs/locale/uk_UA/LC_MESSAGES/dispatcher/dispatcher.po b/docs/locale/uk_UA/LC_MESSAGES/dispatcher/dispatcher.po
index 099f6e08..6ec3c401 100644
--- a/docs/locale/uk_UA/LC_MESSAGES/dispatcher/dispatcher.po
+++ b/docs/locale/uk_UA/LC_MESSAGES/dispatcher/dispatcher.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version:  aiogram\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-08-26 23:17+0300\n"
+"POT-Creation-Date: 2023-09-14 17:21+0300\n"
 "PO-Revision-Date: 2022-12-10 19:44+0200\n"
 "Last-Translator: \n"
 "Language-Team: \n"
@@ -30,9 +30,10 @@ msgstr ""
 "інших маршрутизаторів до диспетчера."
 
 #: ../../dispatcher/dispatcher.rst:7
+#, fuzzy
 msgid ""
 "Here is only listed base information about Dispatcher. All about writing "
-"handlers, filters and etc. you can found in next pages:"
+"handlers, filters and etc. you can find in next pages:"
 msgstr ""
 "Тут наведена лише базова інформація про диспетчер. Усе про написання "
 "обробників, фільтрів і т.п. ви можете знайти на наступних сторінках:"
@@ -142,7 +143,8 @@ msgid "contextual data"
 msgstr "контекстні дані"
 
 #: aiogram.dispatcher.dispatcher.Dispatcher.run_polling
-#: aiogram.dispatcher.dispatcher.Dispatcher.start_polling of
+#: aiogram.dispatcher.dispatcher.Dispatcher.start_polling
+#: aiogram.dispatcher.dispatcher.Dispatcher.stop_polling of
 msgid "Returns"
 msgstr "Повертає"
 
@@ -161,6 +163,10 @@ msgid ""
 "used update types are enabled (resolved from handlers)"
 msgstr ""
 
+#: aiogram.dispatcher.dispatcher.Dispatcher.stop_polling:1 of
+msgid "Execute this method if you want to stop polling programmatically"
+msgstr ""
+
 #: ../../dispatcher/dispatcher.rst:18
 msgid "Simple usage"
 msgstr "Просте застосування"
@@ -190,3 +196,4 @@ msgstr ""
 
 #~ msgid "`Observer <observer.html>`__"
 #~ msgstr "`Observer <observer.html>`__"
+
diff --git a/docs/locale/uk_UA/LC_MESSAGES/dispatcher/filters/index.po b/docs/locale/uk_UA/LC_MESSAGES/dispatcher/filters/index.po
index efde70b7..a19bb7a1 100644
--- a/docs/locale/uk_UA/LC_MESSAGES/dispatcher/filters/index.po
+++ b/docs/locale/uk_UA/LC_MESSAGES/dispatcher/filters/index.po
@@ -5,100 +5,103 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: aiogram\n"
+"Project-Id-Version:  aiogram\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-10-18 01:50+0300\n"
+"POT-Creation-Date: 2023-09-14 17:21+0300\n"
 "PO-Revision-Date: 2022-10-25 15:53+0300\n"
 "Last-Translator: \n"
 "Language-Team: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.10.3\n"
-"X-Generator: Poedit 3.1.1\n"
+"Generated-By: Babel 2.12.1\n"
 
-#: ../../dispatcher/filters/index.rst:3
+#: ../../dispatcher/filters/index.rst:5
 msgid "Filtering events"
 msgstr "Фільтрування подій"
 
-#: ../../dispatcher/filters/index.rst:5
+#: ../../dispatcher/filters/index.rst:7
+#, fuzzy
 msgid ""
-"Filters is needed for routing updates to the specific handler. Searching of "
-"handler is always stops on first match set of filters are pass."
+"Filters is needed for routing updates to the specific handler. Searching "
+"of handler is always stops on first match set of filters are pass. By "
+"default, all handlers has empty set of filters, so all updates will be "
+"passed to first handler that has empty set of filters."
 msgstr ""
 "Фільтри потрібні для маршрутизації оновлень до конкретного обробника "
-"(handler) . Пошук обробника (handler) завжди зупиняється після першого збігу "
-"набору фільтрів."
+"(handler) . Пошук обробника (handler) завжди зупиняється після першого "
+"збігу набору фільтрів."
 
-#: ../../dispatcher/filters/index.rst:8
-msgid "*aiogram* has some builtin useful filters."
+#: ../../dispatcher/filters/index.rst:11
+#, fuzzy
+msgid "*aiogram* has some builtin useful filters or you can write own filters."
 msgstr "*aiogram* має декілька вбудованих корисних фільтрів."
 
-#: ../../dispatcher/filters/index.rst:11
+#: ../../dispatcher/filters/index.rst:14
 msgid "Builtin filters"
 msgstr "Вбудовані фільтри"
 
-#: ../../dispatcher/filters/index.rst:13
+#: ../../dispatcher/filters/index.rst:16
 msgid "Here is list of builtin filters:"
 msgstr "Ось список вбудованих фільтрів:"
 
-#: ../../dispatcher/filters/index.rst:27
+#: ../../dispatcher/filters/index.rst:29
 msgid "Writing own filters"
 msgstr "Написання власних фільтрів"
 
-#: ../../dispatcher/filters/index.rst:29
+#: ../../dispatcher/filters/index.rst:31
 msgid "Filters can be:"
 msgstr "Фільтри бувають:"
 
-#: ../../dispatcher/filters/index.rst:31
+#: ../../dispatcher/filters/index.rst:33
 msgid "Asynchronous function (:code:`async def my_filter(*args, **kwargs): pass`)"
 msgstr "Асинхронною функцією (:code:`async def my_filter(*args, **kwargs): pass`)"
 
-#: ../../dispatcher/filters/index.rst:32
+#: ../../dispatcher/filters/index.rst:34
 msgid "Synchronous function (:code:`def my_filter(*args, **kwargs): pass`)"
 msgstr "Синхронною функцією (:code:`def my_filter(*args, **kwargs): pass`)"
 
-#: ../../dispatcher/filters/index.rst:33
+#: ../../dispatcher/filters/index.rst:35
 msgid "Anonymous function (:code:`lambda event: True`)"
 msgstr "Анонімною функцією (:code:`lambda event: True`)"
 
-#: ../../dispatcher/filters/index.rst:34
+#: ../../dispatcher/filters/index.rst:36
 msgid "Any awaitable object"
 msgstr ""
 "Будь-яким очікуваним об'єктом (awaitable object, об'єкт, який може бути "
 "використаний в :code:`await` виразі)"
 
-#: ../../dispatcher/filters/index.rst:35
+#: ../../dispatcher/filters/index.rst:37
 msgid "Subclass of :class:`aiogram.filters.base.Filter`"
 msgstr "Підкласом :class:`aiogram.filters.base.Filter`"
 
-#: ../../dispatcher/filters/index.rst:36
+#: ../../dispatcher/filters/index.rst:38
 msgid "Instances of :ref:`MagicFilter <magic-filters>`"
 msgstr "Екземпляром  :ref:`MagicFilter <magic-filters>`"
 
-#: ../../dispatcher/filters/index.rst:38
+#: ../../dispatcher/filters/index.rst:40
 msgid ""
-"and should return bool or dict. If the dictionary is passed as result of filter "
-"- resulted data will be propagated to the next filters and handler as keywords "
-"arguments."
+"and should return bool or dict. If the dictionary is passed as result of "
+"filter - resulted data will be propagated to the next filters and handler"
+" as keywords arguments."
 msgstr ""
-"і має повертати bool або dict. Якщо словник передається як результат фільтра, "
-"отримані дані будуть передані до наступних фільтрів і обробника як аргументи "
-"ключових слів."
+"і має повертати bool або dict. Якщо словник передається як результат "
+"фільтра, отримані дані будуть передані до наступних фільтрів і обробника "
+"як аргументи ключових слів."
 
-#: ../../dispatcher/filters/index.rst:43
+#: ../../dispatcher/filters/index.rst:45
 msgid "Base class for own filters"
 msgstr "Базовий клас для власних фільтрів"
 
 #: aiogram.filters.base.Filter:1 of
 msgid ""
-"If you want to register own filters like builtin filters you will need to write "
-"subclass of this class with overriding the :code:`__call__` method and adding "
-"filter attributes."
+"If you want to register own filters like builtin filters you will need to"
+" write subclass of this class with overriding the :code:`__call__` method"
+" and adding filter attributes."
 msgstr ""
-"Якщо Ви хочете зареєструвати власні фільтри, як вбудовані фільтри, Вам потрібно "
-"буде написати підклас цього класу з заміною методу :code:`__call__` і "
-"додаванням атрибутів фільтра."
+"Якщо Ви хочете зареєструвати власні фільтри, як вбудовані фільтри, Вам "
+"потрібно буде написати підклас цього класу з заміною методу "
+":code:`__call__` і додаванням атрибутів фільтра."
 
 #: aiogram.filters.base.Filter.__call__:1 of
 msgid "This method should be overridden."
@@ -118,72 +121,78 @@ msgstr ":class:`bool` or :class:`Dict[str, Any]`"
 
 #: aiogram.filters.base.Filter.update_handler_flags:1 of
 msgid ""
-"Also if you want to extend handler flags with using this filter you should "
-"implement this method"
+"Also if you want to extend handler flags with using this filter you "
+"should implement this method"
 msgstr ""
-"Крім того, якщо ви хочете розширити маркери обробника (handler) за допомогою "
-"цього фільтра, вам слід реалізувати цей метод"
+"Крім того, якщо ви хочете розширити маркери обробника (handler) за "
+"допомогою цього фільтра, вам слід реалізувати цей метод"
 
 #: aiogram.filters.base.Filter.update_handler_flags of
 msgid "Parameters"
 msgstr "Параметри"
 
-#: aiogram.filters.base.Filter.update_handler_flags:3 of
+#: aiogram.filters.base.Filter.update_handler_flags:4 of
 msgid "existing flags, can be updated directly"
 msgstr "існуючі маркери, можна оновити безпосередньо"
 
-#: ../../dispatcher/filters/index.rst:51
+#: ../../dispatcher/filters/index.rst:53
 msgid "Own filter example"
 msgstr "Приклад власного фільтра"
 
-#: ../../dispatcher/filters/index.rst:53
+#: ../../dispatcher/filters/index.rst:55
 msgid "For example if you need to make simple text filter:"
 msgstr "Наприклад, якщо Вам потрібно створити простий текстовий фільтр:"
 
-#: ../../dispatcher/filters/index.rst:60
+#: ../../dispatcher/filters/index.rst:62
 msgid "Combining Filters"
 msgstr "Комбінування фільтрів"
 
-#: ../../dispatcher/filters/index.rst:62
+#: ../../dispatcher/filters/index.rst:64
 msgid "In general, all filters can be combined in two ways"
 msgstr "Взагалом, усі фільтри можна комбінувати двома способами"
 
-#: ../../dispatcher/filters/index.rst:66
+#: ../../dispatcher/filters/index.rst:68
 msgid "Recommended way"
 msgstr "Рекомендований спосіб"
 
-#: ../../dispatcher/filters/index.rst:68
+#: ../../dispatcher/filters/index.rst:70
 msgid ""
-"If you specify multiple filters in a row, it will be checked with an \"and\" "
-"condition:"
+"If you specify multiple filters in a row, it will be checked with an "
+"\"and\" condition:"
 msgstr ""
-"Якщо Ви вкажете кілька фільтрів поспіль, це буде перевірено умовою \"and\" :"
+"Якщо Ви вкажете кілька фільтрів поспіль, це буде перевірено умовою "
+"\"and\" :"
 
-#: ../../dispatcher/filters/index.rst:75
+#: ../../dispatcher/filters/index.rst:77
 msgid ""
-"Also, if you want to use two alternative ways to run the same handler (\"or\" "
-"condition) you can register the handler twice or more times as you like"
+"Also, if you want to use two alternative ways to run the same handler "
+"(\"or\" condition) you can register the handler twice or more times as "
+"you like"
 msgstr ""
-"Крім того, якщо ви хочете використовувати два альтернативні способи запуску "
-"одного обробника (умова \"or\"), ви можете зареєструвати обробник двічі або "
-"більше разів, як вам подобається"
+"Крім того, якщо ви хочете використовувати два альтернативні способи "
+"запуску одного обробника (умова \"or\"), ви можете зареєструвати обробник"
+" двічі або більше разів, як вам подобається"
 
-#: ../../dispatcher/filters/index.rst:84
+#: ../../dispatcher/filters/index.rst:86
 msgid ""
-"Also sometimes you will need to invert the filter result, for example you have "
-"an *IsAdmin* filter and you want to check if the user is not an admin"
+"Also sometimes you will need to invert the filter result, for example you"
+" have an *IsAdmin* filter and you want to check if the user is not an "
+"admin"
 msgstr ""
-"Також іноді Вам потрібно буде інвертувати результат фільтра, наприклад, у вас є "
-"фільтр *IsAdmin* і ви хочете перевірити, чи користувач не є адміністратором"
+"Також іноді Вам потрібно буде інвертувати результат фільтра, наприклад, у"
+" вас є фільтр *IsAdmin* і ви хочете перевірити, чи користувач не є "
+"адміністратором"
 
-#: ../../dispatcher/filters/index.rst:93
+#: ../../dispatcher/filters/index.rst:95
 msgid "Another possible way"
 msgstr "Інший можливий спосіб"
 
-#: ../../dispatcher/filters/index.rst:95
+#: ../../dispatcher/filters/index.rst:97
 msgid ""
-"An alternative way is to combine using special functions (:func:`and_f`, :func:"
-"`or_f`, :func:`invert_f` from :code:`aiogram.filters` module):"
+"An alternative way is to combine using special functions (:func:`and_f`, "
+":func:`or_f`, :func:`invert_f` from :code:`aiogram.filters` module):"
 msgstr ""
-"Альтернативним способом є об’єднання за допомогою спеціальних функцій (:func:"
-"`and_f`, :func:`or_f`, :func:`invert_f` з модуля :code:`aiogram.filters`):"
+"Альтернативним способом є об’єднання за допомогою спеціальних функцій "
+"(:func:`and_f`, :func:`or_f`, :func:`invert_f` з модуля "
+":code:`aiogram.filters`):"
+
diff --git a/docs/locale/uk_UA/LC_MESSAGES/migration_2_to_3.po b/docs/locale/uk_UA/LC_MESSAGES/migration_2_to_3.po
index 3541ffff..6a8ef6be 100644
--- a/docs/locale/uk_UA/LC_MESSAGES/migration_2_to_3.po
+++ b/docs/locale/uk_UA/LC_MESSAGES/migration_2_to_3.po
@@ -3,303 +3,422 @@
 # This file is distributed under the same license as the aiogram package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
 #
-#, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: aiogram \n"
+"Project-Id-Version: aiogram\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-08-26 23:17+0300\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
+"POT-Creation-Date: 2023-09-14 18:12+0300\n"
+"PO-Revision-Date: 2023-09-14 18:34+0300\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: uk_UA\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\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"
 "Generated-By: Babel 2.12.1\n"
+"X-Generator: Poedit 3.3.2\n"
 
 #: ../../migration_2_to_3.rst:3
 msgid "Migration FAQ (2.x -> 3.0)"
-msgstr ""
+msgstr "FAQ по переходу з версії 2.x на 3.0"
 
 #: ../../migration_2_to_3.rst:7
 msgid "This guide is still in progress."
-msgstr ""
+msgstr "Цей посібник все ще в розробці."
 
 #: ../../migration_2_to_3.rst:9
 msgid ""
-"This version introduces much many breaking changes and architectural "
-"improvements, helping to reduce global variables count in your code, "
-"provides useful mechanisms to separate your code to modules or just make "
-"sharable modules via packages on the PyPi, makes middlewares and filters "
-"more controllable and others."
+"This version introduces numerous breaking changes and architectural "
+"improvements. It helps reduce the count of global variables in your code, "
+"provides useful mechanisms to modularize your code, and enables the creation of "
+"shareable modules via packages on PyPI. It also makes middlewares and filters "
+"more controllable, among other improvements."
 msgstr ""
+"Ця версія містить численні суттєві зміни та архітектурні покращення. Вона "
+"допомагає зменшити кількість глобальних змінних у вашому коді, надає корисні "
+"механізми для модуляризації вашого коду та дозволяє створювати спільні модулі "
+"за допомогою пакетів на PyPI. Крім того, серед інших покращень, він робить "
+"проміжне програмне забезпечення (мідлварі) та фільтри більш контрольованими."
 
-#: ../../migration_2_to_3.rst:14
+#: ../../migration_2_to_3.rst:15
 msgid ""
-"On this page you can read about points that changed corresponding to last"
-" stable 2.x version."
+"On this page, you can read about the changes made in relation to the last "
+"stable 2.x version."
 msgstr ""
+"На цій сторінці ви можете прочитати про зміни, внесені в останню стабільну "
+"версію 2.x."
 
-#: ../../migration_2_to_3.rst:18
+#: ../../migration_2_to_3.rst:19
 msgid ""
-"This page is most like a detailed changelog than a migration guide, but "
-"it will be updated in the future."
+"This page more closely resembles a detailed changelog than a migration guide, "
+"but it will be updated in the future."
 msgstr ""
+"Ця сторінка більше нагадує детальний список змін, ніж посібник з міграції, але "
+"вона буде оновлюватися в майбутньому."
 
-#: ../../migration_2_to_3.rst:21
+#: ../../migration_2_to_3.rst:22
 msgid ""
 "Feel free to contribute to this page, if you find something that is not "
 "mentioned here."
 msgstr ""
+"Не соромтеся зробити свій внесок у цю сторінку, якщо ви знайшли щось, про що "
+"тут не згадано."
 
-#: ../../migration_2_to_3.rst:25
+#: ../../migration_2_to_3.rst:26
 msgid "Dispatcher"
 msgstr ""
 
-#: ../../migration_2_to_3.rst:27
+#: ../../migration_2_to_3.rst:28
 msgid ""
-":class:`Dispatcher` class no longer accepts the `Bot` instance into the "
-"initializer, it should be passed to dispatcher only for starting polling "
-"or handling event from webhook. Also this way adds possibility to use "
-"multiple bot instances at the same time (\"multibot\")"
+"The :class:`Dispatcher` class no longer accepts a `Bot` instance in its "
+"initializer. Instead, the `Bot` instance should be passed to the dispatcher "
+"only for starting polling or handling events from webhooks. This approach also "
+"allows for the use of multiple bot instances simultaneously (\"multibot\")."
 msgstr ""
+"Клас :class:`Dispatcher` більше не приймає екземпляр `Bot` у своєму "
+"ініціалізаторі. Замість цього екземпляр `Bot` слід передавати диспетчеру тільки "
+"для запуску полінгу або обробки подій з вебхуків. Такий підхід також дозволяє "
+"використовувати декілька екземплярів бота одночасно (\"мультибот\")."
 
-#: ../../migration_2_to_3.rst:30
+#: ../../migration_2_to_3.rst:32
 msgid ""
-":class:`Dispatcher` now can be extended with another Dispatcher-like "
-"thing named :class:`Router` (:ref:`Read more » <Nested routers>`). With "
-"routes you can easily separate your code to multiple modules and may be "
-"share this modules between projects."
+":class:`Dispatcher` now can be extended with another Dispatcher-like thing "
+"named :class:`Router` (:ref:`Read more » <Nested routers>`)."
 msgstr ""
+"Клас :class:`Dispatcher` тепер можна розширити ще одним об'єктом на кшталт "
+"диспетчера з назвою :class:`Router` (:ref:`Детальніше » <Nested routers>`)."
 
 #: ../../migration_2_to_3.rst:34
 msgid ""
-"Removed the **_handler** suffix from all event handler decorators and "
-"registering methods. (:ref:`Read more » <Event observers>`)"
+"With routes, you can easily modularize your code and potentially share these "
+"modules between projects."
 msgstr ""
+"За допомогою роутерів ви можете легко модулювати свій код і потенційно "
+"перевикористовувати ці модулі між проектами."
 
-#: ../../migration_2_to_3.rst:36
+#: ../../migration_2_to_3.rst:35
 msgid ""
-"Executor entirely removed, now you can use Dispatcher directly to start "
-"polling or webhook."
+"Removed the **_handler** suffix from all event handler decorators and "
+"registering methods. (:ref:`Read more » <Event observers>`)"
 msgstr ""
+"Видалено суфікс **_handler** з усіх декораторів обробників подій та методів "
+"реєстрації. (:ref:`Детальніше » <Event observers>`)"
 
 #: ../../migration_2_to_3.rst:37
 msgid ""
-"Throttling method is completely removed, now you can use middlewares to "
-"control the execution context and use any throttling mechanism you want."
+"The Executor has been entirely removed; you can now use the Dispatcher directly "
+"to start polling or handle webhooks."
 msgstr ""
+"Executor було повністю вилучено; тепер ви можете використовувати Dispatcher "
+"безпосередньо для запуску полінгу або обробки вебхуків."
 
-#: ../../migration_2_to_3.rst:39
+#: ../../migration_2_to_3.rst:38
 msgid ""
-"Removed global context variables from the API types, Bot and Dispatcher "
-"object, from now if you want to get current bot instance inside handlers "
-"or filters you should accept the argument :code:`bot: Bot` and use it "
-"instead of :code:`Bot.get_current()` Inside middlewares it can be "
-"accessed via :code:`data[\"bot\"]`."
+"The throttling method has been completely removed; you can now use middlewares "
+"to control the execution context and implement any throttling mechanism you "
+"desire."
 msgstr ""
+"Метод дроселювання (Throttling) повністю вилучено; тепер ви можете "
+"використовувати проміжне програмне забезпечення (middleware) для керування "
+"контекстом виконання та реалізовувати будь-який механізм дроселювання за вашим "
+"бажанням."
 
-#: ../../migration_2_to_3.rst:43
+#: ../../migration_2_to_3.rst:40
 msgid ""
-"Now to skip pending updates, you should call the "
-":class:`aiogram.methods.delete_webhook.DeleteWebhook` method directly "
-"instead of passing :code:`skip_updates=True` to start polling method."
+"Removed global context variables from the API types, Bot and Dispatcher object, "
+"From now on, if you want to access the current bot instance within handlers or "
+"filters, you should accept the argument :code:`bot: Bot` and use it instead of :"
+"code:`Bot.get_current()`. In middlewares, it can be accessed via :code:"
+"`data[\"bot\"]`."
 msgstr ""
+"Вилучено глобальні контекстні змінні з типів API, об'єктів Bot та Dispatcher, "
+"Відтепер, якщо ви хочете отримати доступ до поточного екземпляру бота в "
+"обробниках або фільтрах, ви повинні приймати аргумент :code:`bot: Bot` і "
+"використовувати його замість :code:`Bot.get_current()`. У проміжному "
+"програмному забезпеченні (middleware) доступ до нього можна отримати через :"
+"code:`data[\"bot\"]`."
 
-#: ../../migration_2_to_3.rst:47
-msgid "Filtering events"
+#: ../../migration_2_to_3.rst:44
+msgid ""
+"To skip pending updates, you should now call the :class:`aiogram.methods."
+"delete_webhook.DeleteWebhook` method directly, rather than passing :code:"
+"`skip_updates=True` to the start polling method."
 msgstr ""
+"Щоб пропустити очікувані оновлення, тепер вам слід викликати метод :class:"
+"`aiogram.methods.delete_webhook.DeleteWebhook` безпосередньо, а не передавати :"
+"code:`skip_updates=True` до методу запуску полінгу."
 
 #: ../../migration_2_to_3.rst:49
+msgid "Filtering events"
+msgstr "Фільтрація подій"
+
+#: ../../migration_2_to_3.rst:51
 msgid ""
-"Keyword filters can no more be used, use filters explicitly. (`Read more "
-"» <https://github.com/aiogram/aiogram/issues/942>`_)"
+"Keyword filters can no longer be used; use filters explicitly. (`Read more » "
+"<https://github.com/aiogram/aiogram/issues/942>`_)"
 msgstr ""
+"Фільтри за ключовими словами більше не можна використовувати; використовуйте "
+"фільтри явно. (`Детальніше » <https://github.com/aiogram/aiogram/issues/942>`_)"
 
-#: ../../migration_2_to_3.rst:50
+#: ../../migration_2_to_3.rst:52
 msgid ""
-"In due to keyword filters was removed all enabled by default filters "
-"(state and content_type now is not enabled), so you should specify them "
-"explicitly if you want to use. For example instead of using "
-":code:`@dp.message_handler(content_types=ContentType.PHOTO)` you should "
-"use :code:`@router.message(F.photo)`"
+"Due to the removal of keyword filters, all previously enabled-by-default "
+"filters (such as state and content_type) are now disabled. You must specify "
+"them explicitly if you wish to use them. For example instead of using :code:"
+"`@dp.message_handler(content_types=ContentType.PHOTO)` you should use :code:"
+"`@router.message(F.photo)`"
 msgstr ""
+"У зв'язку з вилученням keyword фільтрів, всі раніше ввімкнені за замовчуванням "
+"фільтри (такі як state і content_type) тепер вимкнено. Якщо ви бажаєте їх "
+"використовувати, ви повинні вказати їх явно. Наприклад, замість :code:`@dp."
+"message_handler(content_types=ContentType.PHOTO)` слід використовувати :code:"
+"`@router.message(F.photo)`."
 
-#: ../../migration_2_to_3.rst:54
+#: ../../migration_2_to_3.rst:57
 msgid ""
-"Most of common filters is replaced by \"magic filter\". (:ref:`Read more "
-"» <magic-filters>`)"
+"Most common filters have been replaced by the \"magic filter.\" (:ref:`Read "
+"more » <magic-filters>`)"
 msgstr ""
+"Більшість звичайних фільтрів було замінено на \"магічний фільтр\". (:ref:`Детальніше "
+"далі » <magic-filters>`)"
 
-#: ../../migration_2_to_3.rst:55
+#: ../../migration_2_to_3.rst:58
 msgid ""
-"Now by default message handler receives any content type, if you want "
-"specific one just add the filters (Magic or any other)"
+"By default, the message handler now receives any content type. If you want a "
+"specific one, simply add the appropriate filters (Magic or any other)."
 msgstr ""
+"За замовчуванням обробник повідомлень тепер отримує будь-який тип вмісту. Якщо "
+"вам потрібен певний тип, просто додайте відповідні фільтри (Magic або будь-який "
+"інший)."
 
-#: ../../migration_2_to_3.rst:57
+#: ../../migration_2_to_3.rst:60
 msgid ""
-"State filter now is not enabled by default, that's mean if you using "
-":code:`state=\"*\"` in v2 then you should not pass any state filter in "
-"v3, and vice versa, if the state in v2 is not specified now you should "
-"specify the state."
+"The state filter is no longer enabled by default. This means that if you used :"
+"code:`state=\"*\"` in v2, you should not pass any state filter in v3. "
+"Conversely, if the state was not specified in v2, you will now need to specify "
+"it in v3."
 msgstr ""
+"Фільтр стану більше не вмикається за замовчуванням. Це означає, що якщо ви "
+"використовували :code:`state=\"*\"` у v2, вам не слід передавати фільтр стану у "
+"v3. І навпаки, якщо стан не було вказано у v2, вам потрібно буде вказати його у "
+"v3."
 
-#: ../../migration_2_to_3.rst:60
+#: ../../migration_2_to_3.rst:63
 msgid ""
-"Added possibility to register per-router global filters, that helps to "
-"reduces the number of repetitions in the code and makes easily way to "
-"control for what each router will be used."
+"Added the possibility to register global filters for each router, which helps "
+"to reduce code repetition and provides an easier way to control the purpose of "
+"each router."
 msgstr ""
+"Додано можливість реєстрації глобальних фільтрів для кожного роутера, що "
+"допомагає зменшити повторення коду і полегшує контроль призначення кожного "
+"роутера."
 
-#: ../../migration_2_to_3.rst:66
+#: ../../migration_2_to_3.rst:69
 msgid "Bot API"
 msgstr ""
 
-#: ../../migration_2_to_3.rst:68
+#: ../../migration_2_to_3.rst:71
 msgid ""
-"Now all API methods is classes with validation (via `pydantic "
-"<https://docs.pydantic.dev/>`_) (all API calls is also available as "
-"methods in the Bot class)."
+"All API methods are now classes with validation, implemented via `pydantic "
+"<https://docs.pydantic.dev/>`. These API calls are also available as methods in "
+"the Bot class."
 msgstr ""
+"Всі методи API тепер є класами з валідацією, реалізованими через `pydantic "
+"<https://docs.pydantic.dev/>`. Ці виклики API також доступні як методи в класі "
+"Bot."
 
-#: ../../migration_2_to_3.rst:70
+#: ../../migration_2_to_3.rst:74
 msgid ""
-"Added more pre-defined Enums and moved into `aiogram.enums` sub-package. "
-"For example chat type enum now is :class:`aiogram.enums.ChatType` instead"
-" of :class:`aiogram.types.chat.ChatType`. (:ref:`Read more » <enums>`)"
+"More pre-defined Enums have been added and moved to the `aiogram.enums` sub-"
+"package. For example, the chat type enum is now :class:`aiogram.enums.ChatType` "
+"instead of :class:`aiogram.types.chat.ChatType`."
 msgstr ""
+"Додано більше попередньо визначених enums та переміщено їх до підпакету "
+"`aiogram.enums`. Наприклад, enum типу чату тепер має вигляд :class:`aiogram."
+"enums.ChatType` замість :class:`aiogram.types.chat.ChatType`."
 
-#: ../../migration_2_to_3.rst:73
+#: ../../migration_2_to_3.rst:76
 msgid ""
-"Separated HTTP client session into container that can be reused between "
-"different Bot instances in the application."
+"The HTTP client session has been separated into a container that can be reused "
+"across different Bot instances within the application."
 msgstr ""
+"Клієнтська сесія HTTP була відокремлена в контейнер, який можна повторно "
+"використовувати для різних екземплярів бота в додатку."
 
-#: ../../migration_2_to_3.rst:75
+#: ../../migration_2_to_3.rst:78
 msgid ""
-"API Exceptions is no more classified by specific message in due to "
-"Telegram has no documented error codes. But all errors is classified by "
-"HTTP status code and for each method only one case can be caused with the"
-" same code, so in most cases you should check that only error type (by "
-"status-code) without checking error message. (:ref:`Read more » <error-"
-"types>`)"
+"API Exceptions are no longer classified by specific messages, as Telegram has "
+"no documented error codes. However, all errors are classified by HTTP status "
+"codes, and for each method, only one type of error can be associated with a "
+"given code. Therefore, in most cases, you should check only the error type (by "
+"status code) without inspecting the error message."
 msgstr ""
+"Виключення API більше не класифікуються за конкретними повідомленнями, оскільки "
+"Telegram не має задокументованих кодів помилок. Проте всі помилки "
+"класифікуються за кодами статусу HTTP, і для кожного методу з певним кодом може "
+"бути пов'язаний лише один тип помилки. Тому в більшості випадків слід "
+"перевіряти лише тип помилки (за кодом статусу), не перевіряючи повідомлення про "
+"помилку."
 
-#: ../../migration_2_to_3.rst:82
+#: ../../migration_2_to_3.rst:88
 msgid "Middlewares"
-msgstr ""
+msgstr "Проміжне ПО (Middlewares)"
 
-#: ../../migration_2_to_3.rst:84
+#: ../../migration_2_to_3.rst:90
 msgid ""
-"Middlewares can now control a execution context, e.g. using context "
-"managers (:ref:`Read more » <middlewares>`)"
+"Middlewares can now control an execution context, e.g., using context managers. "
+"(:ref:`Read more » <middlewares>`)"
 msgstr ""
+"Проміжне програмне забезпечення тепер може керувати контекстом виконання, "
+"наприклад, за допомогою менеджерів контексту. (:ref:`Детальніше » "
+"<middlewares>`)"
 
-#: ../../migration_2_to_3.rst:85
+#: ../../migration_2_to_3.rst:92
 msgid ""
-"All contextual data now is shared between middlewares, filters and "
-"handlers to end-to-end use. For example now you can easily pass some data"
-" into context inside middleware and get it in the filters layer as the "
-"same way as in the handlers via keyword arguments."
+"All contextual data is now shared end-to-end between middlewares, filters, and "
+"handlers. For example now you can easily pass some data into context inside "
+"middleware and get it in the filters layer as the same way as in the handlers "
+"via keyword arguments."
 msgstr ""
+"Всі контекстні дані тепер наскрізно використовуються між проміжним програмним "
+"забезпеченням, фільтрами та обробниками. Наприклад, тепер ви можете легко "
+"передати деякі дані в контекст у проміжному програмному забезпеченні і отримати "
+"їх у шарі фільтрів так само, як і в обробниках через аргументи ключових слів."
 
-#: ../../migration_2_to_3.rst:88
+#: ../../migration_2_to_3.rst:95
 msgid ""
-"Added mechanism named **flags**, that helps to customize handler behavior"
-" in conjunction with middlewares. (:ref:`Read more » <flags>`)"
+"Added a mechanism named **flags** that helps customize handler behavior in "
+"conjunction with middlewares. (:ref:`Read more » <flags>`)"
 msgstr ""
+"Додано механізм з назвою **flags**, який допомагає налаштовувати поведінку "
+"обробника у поєднанні з проміжним програмним забезпеченням. (:ref:`Детальніше "
+"про » <flags>`)"
 
-#: ../../migration_2_to_3.rst:93
+#: ../../migration_2_to_3.rst:100
 msgid "Keyboard Markup"
-msgstr ""
+msgstr "Розмітка клавіатури"
 
-#: ../../migration_2_to_3.rst:95
+#: ../../migration_2_to_3.rst:102
 msgid ""
-"Now :class:`aiogram.types.inline_keyboard_markup.InlineKeyboardMarkup` "
-"and :class:`aiogram.types.reply_keyboard_markup.ReplyKeyboardMarkup` has "
-"no methods to extend it, instead you have to use markup builders "
-":class:`aiogram.utils.keyboard.ReplyKeyboardBuilder` and "
-":class:`aiogram.utils.keyboard.KeyboardBuilder` respectively (:ref:`Read "
-"more » <Keyboard builder>`)"
+"Now :class:`aiogram.types.inline_keyboard_markup.InlineKeyboardMarkup` and :"
+"class:`aiogram.types.reply_keyboard_markup.ReplyKeyboardMarkup` no longer have "
+"methods for extension, instead you have to use markup builders :class:`aiogram."
+"utils.keyboard.ReplyKeyboardBuilder` and :class:`aiogram.utils.keyboard."
+"KeyboardBuilder` respectively (:ref:`Read more » <Keyboard builder>`)"
 msgstr ""
+"Тепер :class:`aiogram.types.inline_keyboard_markup.InlineKeyboardMarkup` та :"
+"class:`aiogram.types.reply_keyboard_markup.ReplyKeyboardMarkup` більше не мають "
+"методів для розширення, натомість вам слід використовувати будівники розмітки :"
+"class:`aiogram.utils.keyboard.ReplyKeyboardBuilder` та :class:`aiogram.utils.keyboard.InlineKeyboardBuilder` "
+"відповідно (:ref:`Детальніше » <Keyboard builder>`)"
 
-#: ../../migration_2_to_3.rst:103
+#: ../../migration_2_to_3.rst:110
 msgid "Callbacks data"
 msgstr ""
 
-#: ../../migration_2_to_3.rst:105
+#: ../../migration_2_to_3.rst:112
 msgid ""
-"Callback data factory now is strictly typed via `pydantic "
-"<https://docs.pydantic.dev/>`_ models (:ref:`Read more » <Callback data "
-"factory>`)"
+"The callback data factory is now strictly typed using `pydantic <https://docs."
+"pydantic.dev/>`_ models. (:ref:`Read more » <Callback data factory>`)"
 msgstr ""
+"Фабрику даних зворотного виклику тепер строго типізовано за допомогою моделей "
+"`pydantic <https://docs.pydantic.dev/>`_. (:ref:`Детальніше » <Callback data "
+"factory>`)"
 
-#: ../../migration_2_to_3.rst:110
+#: ../../migration_2_to_3.rst:117
 msgid "Finite State machine"
-msgstr ""
+msgstr "Скінченний автомат"
 
-#: ../../migration_2_to_3.rst:112
+#: ../../migration_2_to_3.rst:119
 msgid ""
-"State filter will no more added to all handlers, you will need to specify"
-" state if you want"
+"State filters will no longer be automatically added to all handlers; you will "
+"need to specify the state if you want to use it."
 msgstr ""
+"Фільтри станів більше не будуть автоматично додаватися до всіх обробників; вам "
+"потрібно буде вказати стан, якщо ви хочете його використати."
 
-#: ../../migration_2_to_3.rst:113
+#: ../../migration_2_to_3.rst:121
 msgid ""
-"Added possibility to change FSM strategy, for example if you want to "
-"control state for each user in chat topics instead of user in chat you "
-"can specify it in the Dispatcher."
+"Added the possibility to change the FSM strategy. For example, if you want to "
+"control the state for each user based on chat topics rather than the user in a "
+"chat, you can specify this in the Dispatcher."
 msgstr ""
+"Додано можливість змінювати стратегію FSM. Наприклад, якщо ви хочете "
+"контролювати стан для кожного користувача на основі топіків чату, а не "
+"користувача в чаті, ви можете вказати це в Диспетчері."
 
-#: ../../migration_2_to_3.rst:115
+#: ../../migration_2_to_3.rst:124
 msgid ""
-"Now :class:`aiogram.fsm.state.State` and "
-":class:`aiogram.fsm.state.StateGroup` don't have helper methods like "
-":code:`.set()`, :code:`.next()`, etc."
+"Now :class:`aiogram.fsm.state.State` and :class:`aiogram.fsm.state.StateGroup` "
+"don't have helper methods like :code:`.set()`, :code:`.next()`, etc."
 msgstr ""
+"Тепер :class:`aiogram.fsm.state.State` та :class:`aiogram.fsm.state.StateGroup` "
+"не мають допоміжних методів, таких як :code:`.set()`, :code:`.next()` тощо."
 
-#: ../../migration_2_to_3.rst:118
+#: ../../migration_2_to_3.rst:127
 msgid ""
-"Instead of this you should set states by passing them directly to "
-":class:`aiogram.fsm.context.FSMContext` (:ref:`Read more » <Finite State "
-"Machine>`)"
+"Instead, you should set states by passing them directly to :class:`aiogram.fsm."
+"context.FSMContext` (:ref:`Read more » <Finite State Machine>`)"
 msgstr ""
+"Замість цього вам слід встановлювати стани, передаючи їх безпосередньо до :"
+"class:`aiogram.fsm.context.FSMContext` (:ref:`Детальніше » <Finite State "
+"Machine>`)"
 
-#: ../../migration_2_to_3.rst:120
+#: ../../migration_2_to_3.rst:129
 msgid ""
-"State proxy is deprecated, you should update the state data by calling "
-":code:`state.set_data(...)` and :code:`state.get_data()` respectively."
+"The state proxy is deprecated; you should update the state data by calling :"
+"code:`state.set_data(...)` and :code:`state.get_data()` respectively."
 msgstr ""
+"Проксі стану є застарілим; вам слід оновити дані стану, викликавши :code:`state."
+"set_data(...)` та :code:`state.get_data()` відповідно."
 
-#: ../../migration_2_to_3.rst:125
+#: ../../migration_2_to_3.rst:134
 msgid "Sending Files"
-msgstr ""
+msgstr "Надсилання файлів"
 
-#: ../../migration_2_to_3.rst:127
+#: ../../migration_2_to_3.rst:136
 msgid ""
-"From now you should wrap sending files into InputFile object before send "
-"instead of passing IO object directly to the API method. (:ref:`Read more"
-" » <sending-files>`)"
+"From now on, you should wrap files in an InputFile object before sending them, "
+"instead of passing the IO object directly to the API method. (:ref:`Read more » "
+"<sending-files>`)"
 msgstr ""
+"Відтепер перед відправкою файлів слід обертати їх в об'єкт InputFile замість "
+"того, щоб передавати об'єкт вводу-виводу безпосередньо до методу API. (:ref:"
+"`Детальніше » <sending-files>`)"
 
-#: ../../migration_2_to_3.rst:132
+#: ../../migration_2_to_3.rst:141
 msgid "Webhook"
 msgstr ""
 
-#: ../../migration_2_to_3.rst:134
-msgid "Simplified aiohttp web app configuration"
+#: ../../migration_2_to_3.rst:143
+msgid "The aiohttp web app configuration has been simplified."
+msgstr "Спрощено налаштування веб-застосунку aiohttp."
+
+#: ../../migration_2_to_3.rst:144
+msgid ""
+"By default, the ability to upload files has been added when you use the reply "
+"function in a webhook."
 msgstr ""
+"За замовчуванням додана можливість завантажувати файли, коли ви використовуєте "
+"функцію відповіді у вебхук."
+
+#: ../../migration_2_to_3.rst:148
+msgid "Telegram API Server"
+msgstr "Сервер Telegram API"
 
-#: ../../migration_2_to_3.rst:135
+#: ../../migration_2_to_3.rst:150
 msgid ""
-"By default added possibility to upload files when you use reply into "
-"webhook"
+"The `server` parameter has been moved from the `Bot` instance to `api` in "
+"`BaseSession`."
 msgstr ""
+"Параметр `server` було перенесено з екземпляра `Bot` до `api` в `BaseSession`."
 
-#~ msgid ""
-#~ "Callback data factory now is strictly"
-#~ " typed via `pydantic "
-#~ "<https://docs.pydantic.dev/>`_ models (:ref:`Read "
-#~ "more » <callback-data-factory>`)"
-#~ msgstr ""
+#: ../../migration_2_to_3.rst:151
+msgid ""
+"The constant `aiogram.bot.api.TELEGRAM_PRODUCTION` has been moved to `aiogram."
+"client.telegram.PRODUCTION`."
+msgstr ""
+"Константа `aiogram.bot.api.TELEGRAM_PRODUCTION` була переміщена на `aiogram."
+"client.telegram.PRODUCTION`."
diff --git a/docs/locale/uk_UA/LC_MESSAGES/utils/media_group.po b/docs/locale/uk_UA/LC_MESSAGES/utils/media_group.po
new file mode 100644
index 00000000..adf8c7d2
--- /dev/null
+++ b/docs/locale/uk_UA/LC_MESSAGES/utils/media_group.po
@@ -0,0 +1,291 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2023, aiogram Team
+# This file is distributed under the same license as the aiogram package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: aiogram \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-09-14 17:21+0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.12.1\n"
+
+#: ../../utils/media_group.rst:3
+msgid "Media group builder"
+msgstr ""
+
+#: ../../utils/media_group.rst:5
+msgid ""
+"This module provides a builder for media groups, it can be used to build "
+"media groups for "
+":class:`aiogram.types.input_media_photo.InputMediaPhoto`, "
+":class:`aiogram.types.input_media_video.InputMediaVideo`, "
+":class:`aiogram.types.input_media_document.InputMediaDocument` and "
+":class:`aiogram.types.input_media_audio.InputMediaAudio`."
+msgstr ""
+
+#: ../../utils/media_group.rst:11
+msgid ""
+":class:`aiogram.types.input_media_animation.InputMediaAnimation` is not "
+"supported yet in the Bot API to send as media group."
+msgstr ""
+
+#: ../../utils/media_group.rst:16
+msgid "Usage"
+msgstr ""
+
+#: ../../utils/media_group.rst:30
+msgid ""
+"To send media group use "
+":meth:`aiogram.methods.send_media_group.SendMediaGroup` method, but when "
+"you use :class:`aiogram.utils.media_group.MediaGroupBuilder` you should "
+"pass ``media`` argument as ``media_group.build()``."
+msgstr ""
+
+#: ../../utils/media_group.rst:34
+msgid ""
+"If you specify ``caption`` in "
+":class:`aiogram.utils.media_group.MediaGroupBuilder` it will be used as "
+"``caption`` for first media in group."
+msgstr ""
+
+#: ../../utils/media_group.rst:43
+msgid "References"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add:1 of
+msgid "Add a media object to the media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video of
+msgid "Parameters"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add:3 of
+msgid ""
+"Keyword arguments for the media object. The available keyword arguments "
+"depend on the media type."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video
+#: aiogram.utils.media_group.MediaGroupBuilder.build of
+msgid "Returns"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add:5
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:22
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:27
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:17
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:30 of
+msgid "None"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:1 of
+msgid "Add an audio file to the media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:3
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:3 of
+msgid ""
+"File to send. Pass a file_id to send a file that exists on the Telegram "
+"servers (recommended), pass an HTTP URL for Telegram to get a file from "
+"the Internet, or pass 'attach://<file_attach_name>' to upload a new one "
+"using multipart/form-data under <file_attach_name> name.  :ref:`More "
+"information on Sending Files » <sending-files>`"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:3
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:3 of
+msgid ""
+"File to send. Pass a file_id to send a file that exists on the Telegram "
+"servers (recommended), pass an HTTP URL for Telegram to get a file from "
+"the Internet, or pass 'attach://<file_attach_name>' to upload a new one "
+"using multipart/form-data under <file_attach_name> name."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:7
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:7 of
+msgid ":ref:`More information on Sending Files » <sending-files>`"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:8 of
+msgid ""
+"*Optional*. Thumbnail of the file sent; can be ignored if thumbnail "
+"generation for the file is supported server-side. The thumbnail should be"
+" in JPEG format and less than 200 kB in size. A thumbnail's width and "
+"height should not exceed 320."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:12 of
+msgid ""
+"*Optional*. Caption of the audio to be sent, 0-1024 characters after "
+"entities parsing"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:14 of
+msgid ""
+"*Optional*. Mode for parsing entities in the audio caption. See "
+"`formatting options <https://core.telegram.org/bots/api#formatting-"
+"options>`_ for more details."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:17
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:22
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:13
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:21 of
+msgid ""
+"*Optional*. List of special entities that appear in the caption, which "
+"can be specified instead of *parse_mode*"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:19 of
+msgid "*Optional*. Duration of the audio in seconds"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:20 of
+msgid "*Optional*. Performer of the audio"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_audio:21 of
+msgid "*Optional*. Title of the audio"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:1 of
+msgid "Add a document to the media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:3
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:3 of
+msgid ""
+"File to send. Pass a file_id to send a file that exists on the Telegram "
+"servers (recommended), pass an HTTP URL for Telegram to get a file from "
+"the Internet, or pass 'attach://<file_attach_name>' to upload a new one "
+"using multipart/form-data under <file_attach_name> name. :ref:`More "
+"information on Sending Files » <sending-files>`"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:8
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:8 of
+msgid ""
+"*Optional*. Thumbnail of the file sent; can be ignored if thumbnail "
+"generation for the file is supported server-side. The thumbnail should be"
+" in JPEG format and less than 200 kB in size. A thumbnail's width and "
+"height should not exceed 320. Ignored if the file is not uploaded using "
+"multipart/form-data. Thumbnails can't be reused and can be only uploaded "
+"as a new file, so you can pass 'attach://<file_attach_name>' if the "
+"thumbnail was uploaded using multipart/form-data under "
+"<file_attach_name>. :ref:`More information on Sending Files » <sending-"
+"files>`"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:17 of
+msgid ""
+"*Optional*. Caption of the document to be sent, 0-1024 characters after "
+"entities parsing"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:19 of
+msgid ""
+"*Optional*. Mode for parsing entities in the document caption. See "
+"`formatting options <https://core.telegram.org/bots/api#formatting-"
+"options>`_ for more details."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_document:24 of
+msgid ""
+"*Optional*. Disables automatic server-side content type detection for "
+"files uploaded using multipart/form-data. Always :code:`True`, if the "
+"document is sent as part of an album."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:1 of
+msgid "Add a photo to the media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:8 of
+msgid ""
+"*Optional*. Caption of the photo to be sent, 0-1024 characters after "
+"entities parsing"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:10 of
+msgid ""
+"*Optional*. Mode for parsing entities in the photo caption. See "
+"`formatting options <https://core.telegram.org/bots/api#formatting-"
+"options>`_ for more details."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_photo:15 of
+msgid ""
+"*Optional*. Pass :code:`True` if the photo needs to be covered with a "
+"spoiler animation"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:1 of
+msgid "Add a video to the media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:16 of
+msgid ""
+"*Optional*. Caption of the video to be sent, 0-1024 characters after "
+"entities parsing"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:18 of
+msgid ""
+"*Optional*. Mode for parsing entities in the video caption. See "
+"`formatting options <https://core.telegram.org/bots/api#formatting-"
+"options>`_ for more details."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:23 of
+msgid "*Optional*. Video width"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:24 of
+msgid "*Optional*. Video height"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:25 of
+msgid "*Optional*. Video duration in seconds"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:26 of
+msgid ""
+"*Optional*. Pass :code:`True` if the uploaded video is suitable for "
+"streaming"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.add_video:28 of
+msgid ""
+"*Optional*. Pass :code:`True` if the video needs to be covered with a "
+"spoiler animation"
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.build:1 of
+msgid "Builds a list of media objects for a media group."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.build:3 of
+msgid "Adds the caption to the first media object if it is present."
+msgstr ""
+
+#: aiogram.utils.media_group.MediaGroupBuilder.build:5 of
+msgid "List of media objects."
+msgstr ""
+
diff --git a/docs/migration_2_to_3.rst b/docs/migration_2_to_3.rst
index 93228c4d..0eeef2bf 100644
--- a/docs/migration_2_to_3.rst
+++ b/docs/migration_2_to_3.rst
@@ -6,16 +6,17 @@ Migration FAQ (2.x -> 3.0)
 
     This guide is still in progress.
 
-This version introduces much many breaking changes and architectural improvements,
-helping to reduce global variables count in your code, provides useful mechanisms
-to separate your code to modules or just make sharable modules via packages on the PyPi,
-makes middlewares and filters more controllable and others.
+This version introduces numerous breaking changes and architectural improvements. 
+It helps reduce the count of global variables in your code, provides useful mechanisms 
+to modularize your code, and enables the creation of shareable modules via packages on PyPI. 
+It also makes middlewares and filters more controllable, among other improvements.
 
-On this page you can read about points that changed corresponding to last stable 2.x version.
+
+On this page, you can read about the changes made in relation to the last stable 2.x version.
 
 .. note::
 
-    This page is most like a detailed changelog than a migration guide,
+    This page more closely resembles a detailed changelog than a migration guide, 
     but it will be updated in the future.
 
     Feel free to contribute to this page, if you find something that is not mentioned here.
@@ -24,68 +25,74 @@ On this page you can read about points that changed corresponding to last stable
 Dispatcher
 ==========
 
-- :class:`Dispatcher` class no longer accepts the `Bot` instance into the initializer,
-  it should be passed to dispatcher only for starting polling or handling event from webhook.
-  Also this way adds possibility to use multiple bot instances at the same time ("multibot")
+- The :class:`Dispatcher` class no longer accepts a `Bot` instance in its initializer. 
+  Instead, the `Bot` instance should be passed to the dispatcher only for starting polling 
+  or handling events from webhooks. This approach also allows for the use of multiple bot 
+  instances simultaneously ("multibot").
 - :class:`Dispatcher` now can be extended with another Dispatcher-like
   thing named :class:`Router` (:ref:`Read more » <Nested routers>`).
-  With routes you can easily separate your code to multiple modules
-  and may be share this modules between projects.
+- With routes, you can easily modularize your code and potentially share these modules between projects.
 - Removed the **_handler** suffix from all event handler decorators and registering methods.
   (:ref:`Read more » <Event observers>`)
-- Executor entirely removed, now you can use Dispatcher directly to start polling or webhook.
-- Throttling method is completely removed, now you can use middlewares to control
-  the execution context and use any throttling mechanism you want.
+- The Executor has been entirely removed; you can now use the Dispatcher directly to start poll the API or handle webhooks from it.
+- The throttling method has been completely removed; you can now use middlewares to control 
+  the execution context and implement any throttling mechanism you desire.
 - Removed global context variables from the API types, Bot and Dispatcher object,
-  from now if you want to get current bot instance inside handlers or filters you should
-  accept the argument :code:`bot: Bot` and use it instead of :code:`Bot.get_current()`
-  Inside middlewares it can be accessed via :code:`data["bot"]`.
-- Now to skip pending updates, you should call the :class:`aiogram.methods.delete_webhook.DeleteWebhook` method directly instead of passing :code:`skip_updates=True` to start polling method.
+  From now on, if you want to access the current bot instance within handlers or filters, 
+  you should accept the argument :code:`bot: Bot` and use it instead of :code:`Bot.get_current()`. 
+  In middlewares, it can be accessed via :code:`data["bot"]`.
+- To skip pending updates, you should now call the :class:`aiogram.methods.delete_webhook.DeleteWebhook` method directly, rather than passing :code:`skip_updates=True` to the start polling method.
+
 
 
 Filtering events
 ================
 
-- Keyword filters can no more be used, use filters explicitly. (`Read more » <https://github.com/aiogram/aiogram/issues/942>`_)
-- In due to keyword filters was removed all enabled by default filters (state and content_type now is not enabled),
-  so you should specify them explicitly if you want to use.
+- Keyword filters can no longer be used; use filters explicitly. (`Read more » <https://github.com/aiogram/aiogram/issues/942>`_)
+- Due to the removal of keyword filters, all previously enabled-by-default filters 
+  (such as state and content_type) are now disabled. 
+  You must specify them explicitly if you wish to use them.
   For example instead of using :code:`@dp.message_handler(content_types=ContentType.PHOTO)`
   you should use :code:`@router.message(F.photo)`
-- Most of common filters is replaced by "magic filter". (:ref:`Read more » <magic-filters>`)
-- Now by default message handler receives any content type,
-  if you want specific one just add the filters (Magic or any other)
-- State filter now is not enabled by default, that's mean if you using :code:`state="*"` in v2
-  then you should not pass any state filter in v3, and vice versa,
-  if the state in v2 is not specified now you should specify the state.
-- Added possibility to register per-router global filters, that helps to reduces
-  the number of repetitions in the code and makes easily way to control
-  for what each router will be used.
+- Most common filters have been replaced with the "magic filter." (:ref:`Read more » <magic-filters>`)
+- By default, the message handler now receives any content type. 
+  If you want a specific one, simply add the appropriate filters (Magic or any other).
+- The state filter is no longer enabled by default. This means that if you used :code:`state="*"` 
+  in v2, you should not pass any state filter in v3. 
+  Conversely, if the state was not specified in v2, you will now need to specify it in v3.
+- Added the possibility to register global filters for each router, which helps to reduce code 
+  repetition and provides an easier way to control the purpose of each router.
+
 
 
 Bot API
 =======
 
-- Now all API methods is classes with validation (via `pydantic <https://docs.pydantic.dev/>`_)
-  (all API calls is also available as methods in the Bot class).
-- Added more pre-defined Enums and moved into `aiogram.enums` sub-package. For example chat type enum now is
-  :class:`aiogram.enums.ChatType` instead of :class:`aiogram.types.chat.ChatType`.
-  (:ref:`Read more » <enums>`)
-- Separated HTTP client session into container that can be reused between different
-  Bot instances in the application.
-- API Exceptions is no more classified by specific message in due to Telegram has no documented error codes.
-  But all errors is classified by HTTP status code and for each method only one case can be caused with the same code,
-  so in most cases you should check that only error type (by status-code) without checking error message.
-  (:ref:`Read more » <error-types>`)
+- All API methods are now classes with validation, implemented via 
+  `pydantic <https://docs.pydantic.dev/>`. 
+  These API calls are also available as methods in the Bot class.
+- More pre-defined Enums have been added and moved to the `aiogram.enums` sub-package. 
+  For example, the chat type enum is now :class:`aiogram.enums.ChatType` instead of :class:`aiogram.types.chat.ChatType`.
+- The HTTP client session has been separated into a container that can be reused 
+  across different Bot instances within the application.
+- API Exceptions are no longer classified by specific messages, 
+  as Telegram has no documented error codes. 
+  However, all errors are classified by HTTP status codes, and for each method, 
+  only one type of error can be associated with a given code. 
+  Therefore, in most cases, you should check only the error type (by status code) 
+  without inspecting the error message.
+
 
 
 Middlewares
 ===========
 
-- Middlewares can now control a execution context, e.g. using context managers (:ref:`Read more » <middlewares>`)
-- All contextual data now is shared between middlewares, filters and handlers to end-to-end use.
+- Middlewares can now control an execution context, e.g., using context managers. 
+  (:ref:`Read more » <middlewares>`)
+- All contextual data is now shared end-to-end between middlewares, filters, and handlers.
   For example now you can easily pass some data into context inside middleware and
   get it in the filters layer as the same way as in the handlers via keyword arguments.
-- Added mechanism named **flags**, that helps to customize handler behavior
+- Added a mechanism named **flags** that helps customize handler behavior 
   in conjunction with middlewares. (:ref:`Read more » <flags>`)
 
 
@@ -93,7 +100,7 @@ Keyboard Markup
 ===============
 
 - Now :class:`aiogram.types.inline_keyboard_markup.InlineKeyboardMarkup`
-  and :class:`aiogram.types.reply_keyboard_markup.ReplyKeyboardMarkup` has no methods to extend it,
+  and :class:`aiogram.types.reply_keyboard_markup.ReplyKeyboardMarkup` no longer have methods for extension,
   instead you have to use markup builders :class:`aiogram.utils.keyboard.ReplyKeyboardBuilder`
   and :class:`aiogram.utils.keyboard.KeyboardBuilder` respectively
   (:ref:`Read more » <Keyboard builder>`)
@@ -102,41 +109,43 @@ Keyboard Markup
 Callbacks data
 ==============
 
-- Callback data factory now is strictly typed via `pydantic <https://docs.pydantic.dev/>`_ models
+- The callback data factory is now strictly typed using `pydantic <https://docs.pydantic.dev/>`_ models.
   (:ref:`Read more » <Callback data factory>`)
 
 
 Finite State machine
 ====================
 
-- State filter will no more added to all handlers, you will need to specify state if you want
-- Added possibility to change FSM strategy, for example if you want to control state
-  for each user in chat topics instead of user in chat you can specify it in the Dispatcher.
+- State filters will no longer be automatically added to all handlers; 
+  you will need to specify the state if you want to use it.
+- Added the possibility to change the FSM strategy. For example, 
+  if you want to control the state for each user based on chat topics rather than 
+  the user in a chat, you can specify this in the Dispatcher.
 - Now :class:`aiogram.fsm.state.State` and :class:`aiogram.fsm.state.StateGroup` don't have helper
   methods like :code:`.set()`, :code:`.next()`, etc.
 
-  Instead of this you should set states by passing them directly to
+- Instead, you should set states by passing them directly to
   :class:`aiogram.fsm.context.FSMContext` (:ref:`Read more » <Finite State Machine>`)
-- State proxy is deprecated, you should update the state data by calling
+- The state proxy is deprecated; you should update the state data by calling 
   :code:`state.set_data(...)` and :code:`state.get_data()` respectively.
 
 
 Sending Files
 =============
 
-- From now you should wrap sending files into InputFile object before send instead of passing
-  IO object directly to the API method. (:ref:`Read more » <sending-files>`)
+- From now on, you should wrap files in an InputFile object before sending them, 
+  instead of passing the IO object directly to the API method. (:ref:`Read more » <sending-files>`)
 
 
 Webhook
 =======
 
-- Simplified aiohttp web app configuration
-- By default added possibility to upload files when you use reply into webhook
+- The aiohttp web app configuration has been simplified.
+- By default, the ability to upload files has been added when you `make requests in response to updates <https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates>`_ (available for webhook only).
 
 
 Telegram API Server
 ===================
 
-- `server` param was moved from `Bot` instance to `api` in `BaseSession`.
-- `aiogram.bot.api.TELEGRAM_PRODUCTION` was moved to `aiogram.client.telegram.PRODUCTION`.
+- The `server` parameter has been moved from the `Bot` instance to `api` in `BaseSession`.
+- The constant `aiogram.bot.api.TELEGRAM_PRODUCTION` has been moved to `aiogram.client.telegram.PRODUCTION`.
diff --git a/pyproject.toml b/pyproject.toml
index aa31ab34..d43c740a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,7 +42,7 @@ classifiers = [
 dependencies = [
     "magic-filter~=1.0.11",
     "aiohttp~=3.8.5",
-    "pydantic>=2.1.1,<3",
+    "pydantic>=2.1.1,<2.4",
     "aiofiles~=23.1.0",
     "certifi>=2023.7.22",
     "typing-extensions~=4.7.1",
@@ -70,16 +70,16 @@ cli = [
 ]
 test = [
     "pytest~=7.4.0",
-    "pytest-html~=3.2.0",
+    "pytest-html~=4.0.0",
     "pytest-asyncio~=0.21.0",
     "pytest-lazy-fixture~=0.6.3",
-    "pytest-mock~=3.10.0",
+    "pytest-mock~=3.11.0",
     "pytest-mypy~=0.10.0",
-    "pytest-cov~=4.0.0",
+    "pytest-cov~=4.1.0",
     "pytest-aiohttp~=1.0.4",
     "aresponses~=2.1.6",
-    "pytz~=2022.7.1",
-    "pycryptodomex~=3.18",
+    "pytz~=2023.3",
+    "pycryptodomex~=3.19",
 ]
 docs = [
     "Sphinx~=7.1.1",
@@ -154,9 +154,9 @@ features = [
     "test",
     "cli",
 ]
-extra-dependencies = [
-    "butcher @ git+https://github.com/aiogram/butcher.git@v0.1.22",
-]
+#extra-dependencies = [
+#    "butcher @ git+https://github.com/aiogram/butcher.git@v0.1.22",
+#]
 
 [tool.hatch.envs.dev.scripts]
 update = [
@@ -240,6 +240,10 @@ asyncio_mode = "auto"
 testpaths = [
     "tests",
 ]
+filterwarnings = [
+    "error",
+    "ignore::pytest.PytestUnraisableExceptionWarning",
+]
 
 [tool.coverage.run]
 branch = false


Test patch:
diff --git a/tests/test_api/test_client/test_bot.py b/tests/test_api/test_client/test_bot.py
index 1cf94238..d4f2f75f 100644
--- a/tests/test_api/test_client/test_bot.py
+++ b/tests/test_api/test_client/test_bot.py
@@ -15,6 +15,26 @@ from aiogram.types import File, PhotoSize
 from tests.mocked_bot import MockedBot
 
 
+@pytest.fixture()
+async def bot():
+    """Override mocked bot fixture with real bot."""
+    async with Bot("42:TEST").context() as bot:
+        yield bot
+
+
+@pytest.fixture()
+def mocked_bot():
+    """Mocked bot fixture."""
+    return MockedBot()
+
+
+@pytest.fixture()
+async def session():
+    """Override session fixture."""
+    async with AiohttpSession() as session:
+        yield session
+
+
 class TestBot:
     def test_init(self):
         bot = Bot("42:TEST")
@@ -30,9 +50,7 @@ class TestBot:
         assert bot == Bot("42:TEST")
         assert bot != "42:TEST"
 
-    async def test_emit(self):
-        bot = Bot("42:TEST")
-
+    async def test_emit(self, bot: Bot):
         method = GetMe()
 
         with patch(
@@ -42,8 +60,7 @@ class TestBot:
             await bot(method)
             mocked_make_request.assert_awaited_with(bot, method, timeout=None)
 
-    async def test_close(self):
-        session = AiohttpSession()
+    async def test_close(self, session: AiohttpSession):
         bot = Bot("42:TEST", session=session)
         await session.create_session()
 
@@ -56,18 +73,23 @@ class TestBot:
     @pytest.mark.parametrize("close", [True, False])
     async def test_context_manager(self, close: bool):
         with patch(
-            "aiogram.client.session.aiohttp.AiohttpSession.close", new_callable=AsyncMock
+            target="aiogram.client.session.aiohttp.AiohttpSession.close",
+            new_callable=AsyncMock,
         ) as mocked_close:
-            async with Bot("42:TEST", session=AiohttpSession()).context(auto_close=close) as bot:
+            session = AiohttpSession()
+            async with Bot("42:TEST", session=session).context(auto_close=close) as bot:
                 assert isinstance(bot, Bot)
+
             if close:
                 mocked_close.assert_awaited()
             else:
                 mocked_close.assert_not_awaited()
+                await session.close()
 
     async def test_download_file(self, aresponses: ResponsesMockServer):
         aresponses.add(
-            aresponses.ANY, aresponses.ANY, "get", aresponses.Response(status=200, body=b"\f" * 10)
+            method_pattern="get",
+            response=aresponses.Response(status=200, body=b"\f" * 10),
         )
 
         # https://github.com/Tinche/aiofiles#writing-tests-for-aiofiles
@@ -77,30 +99,34 @@ class TestBot:
 
         mock_file = MagicMock()
 
-        bot = Bot("42:TEST")
-        with patch("aiofiles.threadpool.sync_open", return_value=mock_file):
-            await bot.download_file("TEST", "file.png")
-            mock_file.write.assert_called_once_with(b"\f" * 10)
-
-    async def test_download_file_default_destination(self, aresponses: ResponsesMockServer):
-        bot = Bot("42:TEST")
+        async with Bot("42:TEST").context() as bot:
+            with patch("aiofiles.threadpool.sync_open", return_value=mock_file):
+                await bot.download_file("TEST", "file.png")
+                mock_file.write.assert_called_once_with(b"\f" * 10)
 
+    async def test_download_file_default_destination(
+        self,
+        bot: Bot,
+        aresponses: ResponsesMockServer,
+    ):
         aresponses.add(
-            aresponses.ANY, aresponses.ANY, "get", aresponses.Response(status=200, body=b"\f" * 10)
+            method_pattern="get",
+            response=aresponses.Response(status=200, body=b"\f" * 10),
         )
-
         result = await bot.download_file("TEST")
 
         assert isinstance(result, io.BytesIO)
         assert result.read() == b"\f" * 10
 
-    async def test_download_file_custom_destination(self, aresponses: ResponsesMockServer):
-        bot = Bot("42:TEST")
-
+    async def test_download_file_custom_destination(
+        self,
+        bot: Bot,
+        aresponses: ResponsesMockServer,
+    ):
         aresponses.add(
-            aresponses.ANY, aresponses.ANY, "get", aresponses.Response(status=200, body=b"\f" * 10)
+            method_pattern="get",
+            response=aresponses.Response(status=200, body=b"\f" * 10),
         )
-
         custom = io.BytesIO()
 
         result = await bot.download_file("TEST", custom)
@@ -109,19 +135,19 @@ class TestBot:
         assert result is custom
         assert result.read() == b"\f" * 10
 
-    async def test_download(self, bot: MockedBot, aresponses: ResponsesMockServer):
-        bot.add_result_for(
+    async def test_download(self, mocked_bot: MockedBot):
+        mocked_bot.add_result_for(
             GetFile, ok=True, result=File(file_id="file id", file_unique_id="file id")
         )
-        bot.add_result_for(
+        mocked_bot.add_result_for(
             GetFile, ok=True, result=File(file_id="file id", file_unique_id="file id")
         )
 
-        assert await bot.download(File(file_id="file id", file_unique_id="file id"))
-        assert await bot.download("file id")
+        assert await mocked_bot.download(File(file_id="file id", file_unique_id="file id"))
+        assert await mocked_bot.download("file id")
 
         with pytest.raises(TypeError):
-            await bot.download(
+            await mocked_bot.download(
                 [PhotoSize(file_id="file id", file_unique_id="file id", width=123, height=123)]
             )
 
diff --git a/tests/test_api/test_client/test_session/test_aiohttp_session.py b/tests/test_api/test_client/test_session/test_aiohttp_session.py
index d79eb875..bc7aff83 100644
--- a/tests/test_api/test_client/test_session/test_aiohttp_session.py
+++ b/tests/test_api/test_client/test_session/test_aiohttp_session.py
@@ -24,61 +24,53 @@ class BareInputFile(InputFile):
 class TestAiohttpSession:
     async def test_create_session(self):
         session = AiohttpSession()
-
         assert session._session is None
         aiohttp_session = await session.create_session()
         assert session._session is not None
         assert isinstance(aiohttp_session, aiohttp.ClientSession)
+        await session.close()
 
     async def test_create_proxy_session(self):
-        session = AiohttpSession(
-            proxy=("socks5://proxy.url/", aiohttp.BasicAuth("login", "password", "encoding"))
-        )
+        auth = aiohttp.BasicAuth("login", "password", "encoding")
+        async with AiohttpSession(proxy=("socks5://proxy.url/", auth)) as session:
+            assert session._connector_type == aiohttp_socks.ProxyConnector
 
-        assert session._connector_type == aiohttp_socks.ProxyConnector
+            assert isinstance(session._connector_init, dict)
+            assert session._connector_init["proxy_type"] is aiohttp_socks.ProxyType.SOCKS5
 
-        assert isinstance(session._connector_init, dict)
-        assert session._connector_init["proxy_type"] is aiohttp_socks.ProxyType.SOCKS5
-
-        aiohttp_session = await session.create_session()
-        assert isinstance(aiohttp_session.connector, aiohttp_socks.ProxyConnector)
+            aiohttp_session = await session.create_session()
+            assert isinstance(aiohttp_session.connector, aiohttp_socks.ProxyConnector)
 
     async def test_create_proxy_session_proxy_url(self):
-        session = AiohttpSession(proxy="socks4://proxy.url/")
-
-        assert isinstance(session.proxy, str)
+        async with AiohttpSession(proxy="socks4://proxy.url/") as session:
+            assert isinstance(session.proxy, str)
 
-        assert isinstance(session._connector_init, dict)
-        assert session._connector_init["proxy_type"] is aiohttp_socks.ProxyType.SOCKS4
+            assert isinstance(session._connector_init, dict)
+            assert session._connector_init["proxy_type"] is aiohttp_socks.ProxyType.SOCKS4
 
-        aiohttp_session = await session.create_session()
-        assert isinstance(aiohttp_session.connector, aiohttp_socks.ProxyConnector)
+            aiohttp_session = await session.create_session()
+            assert isinstance(aiohttp_session.connector, aiohttp_socks.ProxyConnector)
 
     async def test_create_proxy_session_chained_proxies(self):
-        session = AiohttpSession(
-            proxy=[
-                "socks4://proxy.url/",
-                "socks5://proxy.url/",
-                "http://user:password@127.0.0.1:3128",
-            ]
-        )
-
-        assert isinstance(session.proxy, list)
-
-        assert isinstance(session._connector_init, dict)
-        assert isinstance(session._connector_init["proxy_infos"], list)
-        assert isinstance(session._connector_init["proxy_infos"][0], aiohttp_socks.ProxyInfo)
-
-        assert (
-            session._connector_init["proxy_infos"][0].proxy_type is aiohttp_socks.ProxyType.SOCKS4
-        )
-        assert (
-            session._connector_init["proxy_infos"][1].proxy_type is aiohttp_socks.ProxyType.SOCKS5
-        )
-        assert session._connector_init["proxy_infos"][2].proxy_type is aiohttp_socks.ProxyType.HTTP
-
-        aiohttp_session = await session.create_session()
-        assert isinstance(aiohttp_session.connector, aiohttp_socks.ChainProxyConnector)
+        proxy_chain = [
+            "socks4://proxy.url/",
+            "socks5://proxy.url/",
+            "http://user:password@127.0.0.1:3128",
+        ]
+        async with AiohttpSession(proxy=proxy_chain) as session:
+            assert isinstance(session.proxy, list)
+
+            assert isinstance(session._connector_init, dict)
+
+            proxy_infos = session._connector_init["proxy_infos"]
+            assert isinstance(proxy_infos, list)
+            assert isinstance(proxy_infos[0], aiohttp_socks.ProxyInfo)
+            assert proxy_infos[0].proxy_type is aiohttp_socks.ProxyType.SOCKS4
+            assert proxy_infos[1].proxy_type is aiohttp_socks.ProxyType.SOCKS5
+            assert proxy_infos[2].proxy_type is aiohttp_socks.ProxyType.HTTP
+
+            aiohttp_session = await session.create_session()
+            assert isinstance(aiohttp_session.connector, aiohttp_socks.ChainProxyConnector)
 
     async def test_reset_connector(self):
         session = AiohttpSession()
@@ -93,6 +85,7 @@ class TestAiohttpSession:
         assert session._should_reset_connector
         await session.create_session()
         assert session._should_reset_connector is False
+
         await session.close()
 
     async def test_close_session(self):
@@ -170,54 +163,53 @@ class TestAiohttpSession:
             ),
         )
 
-        session = AiohttpSession()
+        async with AiohttpSession() as session:
 
-        class TestMethod(TelegramMethod[int]):
-            __returning__ = int
-            __api_method__ = "method"
+            class TestMethod(TelegramMethod[int]):
+                __returning__ = int
+                __api_method__ = "method"
 
-        call = TestMethod()
+            call = TestMethod()
 
-        result = await session.make_request(bot, call)
-        assert isinstance(result, int)
-        assert result == 42
+            result = await session.make_request(bot, call)
+            assert isinstance(result, int)
+            assert result == 42
 
     @pytest.mark.parametrize("error", [ClientError("mocked"), asyncio.TimeoutError()])
     async def test_make_request_network_error(self, error):
-        bot = Bot("42:TEST")
-
         async def side_effect(*args, **kwargs):
             raise error
 
-        with patch(
-            "aiohttp.client.ClientSession._request",
-            new_callable=AsyncMock,
-            side_effect=side_effect,
-        ):
-            with pytest.raises(TelegramNetworkError):
-                await bot.get_me()
+        async with Bot("42:TEST").context() as bot:
+            with patch(
+                "aiohttp.client.ClientSession._request",
+                new_callable=AsyncMock,
+                side_effect=side_effect,
+            ):
+                with pytest.raises(TelegramNetworkError):
+                    await bot.get_me()
 
     async def test_stream_content(self, aresponses: ResponsesMockServer):
         aresponses.add(
             aresponses.ANY, aresponses.ANY, "get", aresponses.Response(status=200, body=b"\f" * 10)
         )
 
-        session = AiohttpSession()
-        stream = session.stream_content(
-            "https://www.python.org/static/img/python-logo.png",
-            timeout=5,
-            chunk_size=1,
-            raise_for_status=True,
-        )
-        assert isinstance(stream, AsyncGenerator)
-
-        size = 0
-        async for chunk in stream:
-            assert isinstance(chunk, bytes)
-            chunk_size = len(chunk)
-            assert chunk_size == 1
-            size += chunk_size
-        assert size == 10
+        async with AiohttpSession() as session:
+            stream = session.stream_content(
+                "https://www.python.org/static/img/python-logo.png",
+                timeout=5,
+                chunk_size=1,
+                raise_for_status=True,
+            )
+            assert isinstance(stream, AsyncGenerator)
+
+            size = 0
+            async for chunk in stream:
+                assert isinstance(chunk, bytes)
+                chunk_size = len(chunk)
+                assert chunk_size == 1
+                size += chunk_size
+            assert size == 10
 
     async def test_stream_content_404(self, aresponses: ResponsesMockServer):
         aresponses.add(
@@ -229,29 +221,30 @@ class TestAiohttpSession:
                 body=b"File not found",
             ),
         )
-        session = AiohttpSession()
-        stream = session.stream_content(
-            "https://www.python.org/static/img/python-logo.png",
-            timeout=5,
-            chunk_size=1,
-            raise_for_status=True,
-        )
-
-        with pytest.raises(ClientError):
-            async for _ in stream:
-                ...
+        async with AiohttpSession() as session:
+            stream = session.stream_content(
+                "https://www.python.org/static/img/python-logo.png",
+                timeout=5,
+                chunk_size=1,
+                raise_for_status=True,
+            )
+
+            with pytest.raises(ClientError):
+                async for _ in stream:
+                    ...
 
     async def test_context_manager(self):
-        session = AiohttpSession()
-        assert isinstance(session, AsyncContextManager)
-
-        with patch(
-            "aiogram.client.session.aiohttp.AiohttpSession.create_session",
-            new_callable=AsyncMock,
-        ) as mocked_create_session, patch(
-            "aiogram.client.session.aiohttp.AiohttpSession.close", new_callable=AsyncMock
-        ) as mocked_close:
-            async with session as ctx:
-                assert session == ctx
-            mocked_close.assert_awaited_once()
-            mocked_create_session.assert_awaited_once()
+        async with AiohttpSession() as session:
+            assert isinstance(session, AsyncContextManager)
+
+            with patch(
+                "aiogram.client.session.aiohttp.AiohttpSession.create_session",
+                new_callable=AsyncMock,
+            ) as mocked_create_session, patch(
+                "aiogram.client.session.aiohttp.AiohttpSession.close",
+                new_callable=AsyncMock,
+            ) as mocked_close:
+                async with session as ctx:
+                    assert session == ctx
+                mocked_close.assert_awaited_once()
+                mocked_create_session.assert_awaited_once()
diff --git a/tests/test_api/test_types/test_input_file.py b/tests/test_api/test_types/test_input_file.py
index 28daa92c..e8716f84 100644
--- a/tests/test_api/test_types/test_input_file.py
+++ b/tests/test_api/test_types/test_input_file.py
@@ -68,15 +68,18 @@ class TestInputFile:
 
     async def test_url_input_file(self, aresponses: ResponsesMockServer):
         aresponses.add(
-            aresponses.ANY, aresponses.ANY, "get", aresponses.Response(status=200, body=b"\f" * 10)
+            aresponses.ANY,
+            aresponses.ANY,
+            "get",
+            aresponses.Response(status=200, body=b"\f" * 10),
         )
-        bot = Bot(token="42:TEST")
-        file = URLInputFile("https://test.org/", chunk_size=1)
-
-        size = 0
-        async for chunk in file.read(bot):
-            assert chunk == b"\f"
-            chunk_size = len(chunk)
-            assert chunk_size == 1
-            size += chunk_size
-        assert size == 10
+        async with Bot(token="42:TEST").context() as bot:
+            file = URLInputFile("https://test.org/", chunk_size=1)
+
+            size = 0
+            async for chunk in file.read(bot):
+                assert chunk == b"\f"
+                chunk_size = len(chunk)
+                assert chunk_size == 1
+                size += chunk_size
+            assert size == 10
diff --git a/tests/test_api/test_types/test_reply_keyboard_remove.py b/tests/test_api/test_types/test_reply_keyboard_remove.py
index 984932a5..1d252cc5 100644
--- a/tests/test_api/test_types/test_reply_keyboard_remove.py
+++ b/tests/test_api/test_types/test_reply_keyboard_remove.py
@@ -1,3 +1,5 @@
+from typing import Dict
+
 import pytest
 
 from aiogram.types import ReplyKeyboardRemove
@@ -10,12 +12,12 @@ class TestReplyKeyboardRemove:
 
     def test_remove_keyboard_default_is_true(self):
         assert (
-            ReplyKeyboardRemove.__fields__["remove_keyboard"].default is True
+            ReplyKeyboardRemove.model_fields["remove_keyboard"].default is True
         ), "Remove keyboard has incorrect default value!"
 
     @pytest.mark.parametrize(
         "kwargs,expected",
         [[{}, True], [{"remove_keyboard": True}, True]],
     )
-    def test_remove_keyboard_values(self, kwargs, expected):
+    def test_remove_keyboard_values(self, kwargs: Dict[str, bool], expected: bool):
         assert ReplyKeyboardRemove(**kwargs).remove_keyboard is expected
diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py
index b0cc5b79..b85cfc98 100644
--- a/tests/test_dispatcher/test_dispatcher.py
+++ b/tests/test_dispatcher/test_dispatcher.py
@@ -187,8 +187,9 @@ class TestDispatcher:
     async def test_process_update_empty(self, bot: MockedBot):
         dispatcher = Dispatcher()
 
-        result = await dispatcher._process_update(bot=bot, update=Update(update_id=42))
-        assert not result
+        with pytest.warns(RuntimeWarning, match="Detected unknown update type") as record:
+            result = await dispatcher._process_update(bot=bot, update=Update(update_id=42))
+            assert not result
 
     async def test_process_update_handled(self, bot: MockedBot):
         dispatcher = Dispatcher()
@@ -197,7 +198,8 @@ class TestDispatcher:
         async def update_handler(update: Update):
             pass
 
-        assert await dispatcher._process_update(bot=bot, update=Update(update_id=42))
+        with pytest.warns(RuntimeWarning, match="Detected unknown update type"):
+            assert await dispatcher._process_update(bot=bot, update=Update(update_id=42))
 
     @pytest.mark.parametrize(
         "event_type,update,has_chat,has_user",
@@ -479,9 +481,11 @@ class TestDispatcher:
 
     async def test_listen_unknown_update(self):
         dp = Dispatcher()
-
-        with pytest.raises(SkipHandler):
+        pattern = "Detected unknown update type"
+        with pytest.raises(SkipHandler), pytest.warns(RuntimeWarning, match=pattern) as record:
             await dp._listen_update(Update(update_id=42))
+            if not record:
+                pytest.fail("Expected 'Detected unknown update type' warning.")
 
     async def test_listen_unhandled_update(self):
         dp = Dispatcher()
@@ -608,7 +612,9 @@ class TestDispatcher:
         async def update_handler(update: Update):
             raise Exception("Kaboom!")
 
-        assert await dispatcher._process_update(bot=bot, update=Update(update_id=42))
+        with pytest.warns(RuntimeWarning, match="Detected unknown update type"):
+            assert await dispatcher._process_update(bot=bot, update=Update(update_id=42))
+
         log_records = [rec.message for rec in caplog.records]
         assert len(log_records) == 1
         assert "Cause exception while process update" in log_records[0]
@@ -834,12 +840,17 @@ class TestDispatcher:
         dispatcher = Dispatcher()
         dispatcher.message.register(invalid_message_handler)
 
-        response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=0.1)
-        assert response is None
-        await asyncio.sleep(0.5)
+        pattern = r"Detected slow response into webhook"
+        with pytest.warns(RuntimeWarning, match=pattern) as record:
+            response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=0.1)
+            assert response is None
+            await asyncio.sleep(0.5)
+
+            log_records = [rec.message for rec in caplog.records]
+            assert "Cause exception while process update" in log_records[0]
 
-        log_records = [rec.message for rec in caplog.records]
-        assert "Cause exception while process update" in log_records[0]
+            if not record:
+                pytest.fail("Expected 'Detected slow response into webhook' warning.")
 
     def test_specify_updates_calculation(self):
         def simple_msg_handler() -> None:
diff --git a/tests/test_filters/test_exception.py b/tests/test_filters/test_exception.py
index be056b72..dfc7fa80 100644
--- a/tests/test_filters/test_exception.py
+++ b/tests/test_filters/test_exception.py
@@ -72,4 +72,5 @@ class TestDispatchException:
         async def handler0(event):
             return "Handled"
 
-        assert await dp.feed_update(bot, Update(update_id=0)) == "Handled"
+        with pytest.warns(RuntimeWarning, match="Detected unknown update type"):
+            assert await dp.feed_update(bot, Update(update_id=0)) == "Handled"
diff --git a/tests/test_fsm/storage/test_isolation.py b/tests/test_fsm/storage/test_isolation.py
index 8212c955..60e240ae 100644
--- a/tests/test_fsm/storage/test_isolation.py
+++ b/tests/test_fsm/storage/test_isolation.py
@@ -18,10 +18,11 @@ def create_storage_key(bot: MockedBot):
     ],
 )
 class TestIsolations:
+    @pytest.mark.filterwarnings("ignore::ResourceWarning")
     async def test_lock(
         self,
         isolation: BaseEventIsolation,
         storage_key: StorageKey,
     ):
         async with isolation.lock(key=storage_key):
-            assert True, "You are kidding me?"
+            assert True, "Are you kidding me?"
diff --git a/tests/test_issues/test_1317_state_vs_isolation.py b/tests/test_issues/test_1317_state_vs_isolation.py
new file mode 100644
index 00000000..31613fa4
--- /dev/null
+++ b/tests/test_issues/test_1317_state_vs_isolation.py
@@ -0,0 +1,81 @@
+import asyncio
+from datetime import datetime
+
+from aiogram import Dispatcher
+from aiogram.filters import Command
+from aiogram.fsm.context import FSMContext
+from aiogram.fsm.state import State, StatesGroup
+from aiogram.fsm.storage.memory import SimpleEventIsolation
+from aiogram.types import Chat, Message, Update, User
+from tests.mocked_bot import MockedBot
+
+
+class TestStateVSIsolation:
+    async def test_issue(self, bot: MockedBot):
+        dispatcher = Dispatcher(events_isolation=SimpleEventIsolation())
+        first = 0
+        second = 0
+        third = 0
+        stack = []
+
+        class TestState(StatesGroup):
+            foo = State()
+            bar = State()
+            baz = State()
+
+        @dispatcher.message(Command("test"))
+        async def command_top(message: Message, state: FSMContext):
+            nonlocal first
+            first += 1
+            stack.append("command")
+            await state.set_state(TestState.foo)
+
+        @dispatcher.message(TestState.foo)
+        async def handle_foo(message: Message, state: FSMContext):
+            nonlocal second
+            second += 1
+            stack.append("foo")
+            await state.set_state(TestState.bar)
+
+        @dispatcher.message(TestState.bar)
+        async def handle_bar(message: Message, state: FSMContext):
+            nonlocal third
+            third += 1
+            stack.append("bar")
+            await state.set_state(None)
+
+        @dispatcher.message()
+        async def handle_all(message: Message):
+            stack.append("all")
+
+        await asyncio.gather(
+            *(
+                dispatcher.feed_update(bot, update)
+                for update in [
+                    create_message_update(index=1, text="/test"),
+                    create_message_update(index=2, text="foo"),
+                    create_message_update(index=3, text="bar"),
+                    create_message_update(index=4, text="baz"),
+                ]
+            )
+        )
+
+        # Before bug fix:
+        #   first == 1, second == 3, third == 0, stack == ["command", "foo", "foo", "foo"]
+        assert first == 1
+        assert second == 1
+        assert third == 1
+        assert stack == ["command", "foo", "bar", "all"]
+
+
+def create_message_update(index: int, text: str):
+    return Update(
+        update_id=index,
+        message=Message(
+            message_id=index,
+            date=datetime.now(),
+            chat=Chat(id=42, type="private"),
+            from_user=User(id=42, is_bot=False, first_name="Test", username="test"),
+            text=text,
+        ),
+    )
diff --git a/tests/test_utils/test_chat_action.py b/tests/test_utils/test_chat_action.py
index d6041c37..b5c0a965 100644
--- a/tests/test_utils/test_chat_action.py
+++ b/tests/test_utils/test_chat_action.py
@@ -13,9 +13,9 @@ from tests.mocked_bot import MockedBot
 
 
 class TestChatActionSender:
-    async def test_wait(self, bot: Bot, loop: asyncio.BaseEventLoop):
+    async def test_wait(self, bot: Bot, event_loop: asyncio.BaseEventLoop):
         sender = ChatActionSender.typing(bot=bot, chat_id=42)
-        loop.call_soon(sender._close_event.set)
+        event_loop.call_soon(sender._close_event.set)
         start = time.monotonic()
         await sender._wait(1)
         assert time.monotonic() - start < 1
diff --git a/tests/test_utils/test_deep_linking.py b/tests/test_utils/test_deep_linking.py
index df06c1e5..85a6027b 100644
--- a/tests/test_utils/test_deep_linking.py
+++ b/tests/test_utils/test_deep_linking.py
@@ -1,9 +1,6 @@
 import pytest
 
-from aiogram.utils.deep_linking import (
-    create_start_link,
-    create_startgroup_link,
-)
+from aiogram.utils.deep_linking import create_start_link, create_startgroup_link
 from aiogram.utils.payload import decode_payload, encode_payload
 from tests.mocked_bot import MockedBot
 
diff --git a/tests/test_utils/test_i18n.py b/tests/test_utils/test_i18n.py
index 71fa2eb7..aaf58b9e 100644
--- a/tests/test_utils/test_i18n.py
+++ b/tests/test_utils/test_i18n.py
@@ -175,7 +175,7 @@ class TestConstI18nMiddleware:
 
 
 class TestFSMI18nMiddleware:
-    async def test_middleware(self, i18n: I18n, bot: MockedBot, extra):
+    async def test_middleware(self, i18n: I18n, bot: MockedBot):
         middleware = FSMI18nMiddleware(i18n=i18n)
         storage = MemoryStorage()
         state = FSMContext(storage=storage, key=StorageKey(user_id=42, chat_id=42, bot_id=bot.id))
@@ -185,12 +185,14 @@ class TestFSMI18nMiddleware:
         }
         result = await middleware(next_call, Update(update_id=42), data)
         assert result == "test"
+
         await middleware.set_locale(state, "uk")
         assert i18n.current_locale == "uk"
+
         result = await middleware(next_call, Update(update_id=42), data)
         assert result == "тест"
 
-    async def test_without_state(self, i18n: I18n, bot: MockedBot, extra):
+    async def test_without_state(self, i18n: I18n, bot: MockedBot):
         middleware = FSMI18nMiddleware(i18n=i18n)
         data = {
             "event_from_user": User(id=42, is_bot=False, language_code="it", first_name="Test"),
@@ -198,7 +200,3 @@ class TestFSMI18nMiddleware:
         result = await middleware(next_call, Update(update_id=42), data)
         assert i18n.current_locale == "en"
         assert result == "test"
-
-        assert i18n.current_locale == "en"
-        result = await middleware(next_call, Update(update_id=42), data)
-        assert i18n.current_locale == "en"